API
Affichez vos albums publics sur votre propre site, ou gérez vos albums depuis un autre outil : création, envoi de photos, prix, destinataires, publication. Chaque clé n'a que les permissions choisies à sa création. Les fichiers originaux ne sont jamais exposés.
Authentification
Créez une clé dans votre espace (Clés API), puis envoyez-la dans l'en-tête Authorization. Limite par défaut : 60 requêtes par minute et par clé. Les corps de requête sont en JSON (Content-Type: application/json), les réponses aussi : { "data": … }, avec total pour les listes.
curl "https://obturo.app/api/v1/photos?tag=plage&limit=10" \ -H "Authorization: Bearer pk_live_…"
Permissions
public:read | Lecture publique. Albums publics publiés, leurs photos (aperçus) et leurs prix. Pour afficher vos albums sur un autre site. |
albums:read | Lecture de tous vos albums. Aussi les brouillons, albums sur lien et privés (jamais les fichiers originaux ni le lien secret). |
albums:write | Gestion des albums. Créer, modifier, supprimer, publier et dépublier. Inclut la lecture de tous vos albums et leurs liens. |
photos:write | Gestion des photos. Envoyer des photos, changer leur accès (offerte, aperçu payant, exclusive), les supprimer. |
prices:write | Gestion des prix. Ajouter et retirer les formats de prix d'un album. |
recipients:write | Gestion des destinataires. Lister et retirer les destinataires et invités. En ajouter donne accès à l'album (lien envoyé par email, invitation) : il faut aussi « Gestion des albums ». |
Une route appelée sans la permission nécessaire répond 403 missing_scope. Les écritures appliquent exactement les règles de votre espace photographe et ne concernent que vos propres albums. Les clés créées avant l'arrivée des permissions ont public:read.
Depuis un navigateur (CORS)
Lecture : acceptée depuis tous les domaines si la liste des domaines autorisés de la clé est vide, sinon seulement depuis ceux de la liste. Écriture (POST, PATCH, DELETE) : acceptée seulement depuis un domaine explicitement listé ; sans liste, les modifications se font depuis un serveur (sans en-tête Origin). Une clé avec des permissions de gestion ne doit jamais apparaître dans le code d'une page publique.
Albums
/api/v1/albums?tag=&visibility=&status=&limit=&offset=Permission : public:read ou albums:readAlbums paginés (20 par défaut, 100 max). public:read : albums publics publiés. albums:read : tous vos albums, filtrables par visibility (public, unlisted, private) et status (draft, published, expired).
curl "https://obturo.app/api/v1/albums?status=draft&limit=10" \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/albums/:idPermission : public:read ou albums:readDétail d'un album avec ses formats de prix. En albums:read : nombre de photos et ce qui empêche la publication (publish_blockers). Pour un album déjà publié, la liste montre ce qui empêche la vente (ex. photos payantes sans format de prix).
curl https://obturo.app/api/v1/albums/ALBUM_ID \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/albumsPermission : albums:writeCrée un album en brouillon. Champs : title (obligatoire, 120 caractères max), visibility (obligatoire : public, unlisted ou private), description (2000 max), tags (10 hashtags max), expires_at (AAAA-MM-JJ ou date ISO 8601, dans le futur). Réponse 201.
curl -X POST https://obturo.app/api/v1/albums \
-H "Authorization: Bearer $OBTURO_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Mariage Julie et Tom","visibility":"private","tags":["mariage"],"expires_at":"2027-06-30"}'/api/v1/albums/:idPermission : albums:writeModifie les champs envoyés : title, description, tags (remplace la liste), expires_at (null pour l'enlever). Le type d'album (visibility) ne change pas après la création.
curl -X PATCH https://obturo.app/api/v1/albums/ALBUM_ID \
-H "Authorization: Bearer $OBTURO_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Mariage Julie & Tom","description":null,"tags":["mariage","ete"]}'/api/v1/albums/:idPermission : albums:writeSupprime l'album, ses photos et leurs fichiers. Refusé (409 has_orders) si une photo a été vendue.
curl -X DELETE https://obturo.app/api/v1/albums/ALBUM_ID \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/albums/:id/publishPermission : albums:writePublie l'album, avec les mêmes règles que votre espace : compte actif, au moins une photo, et si des photos sont payantes, au moins un format de prix (sinon 409 publish_blocked, détail dans details). Le compte Stripe n'est pas exigé : sans lui, les photos payantes s'affichent sans bouton d'achat. Les destinataires pas encore prévenus reçoivent l'email ; emails_sent vaut null si l'envoi n'est pas configuré. Ces règles ne bloquent que la publication : ensuite, une photo payante sans format de prix actif ou sans compte Stripe qui encaisse reste affichée, sans bouton d'achat.
curl -X POST https://obturo.app/api/v1/albums/ALBUM_ID/publish \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/albums/:id/unpublishPermission : albums:writeRepasse l'album en brouillon : il n'est plus visible, ses liens ne fonctionnent plus jusqu'à la prochaine publication.
curl -X POST https://obturo.app/api/v1/albums/ALBUM_ID/unpublish \ -H "Authorization: Bearer $OBTURO_KEY"
Photos
Envoi en trois étapes : demander une URL d'envoi par fichier, envoyer le fichier sur cette URL (PUT, valable 15 minutes), puis finaliser. La taille déclarée fait partie de la signature : le stockage refuse un fichier d'une autre taille. La finalisation génère l'aperçu (filigrané sauf photo offerte) et la miniature floutée, sans métadonnées EXIF/GPS, puis range l'original hors de portée de l'URL d'envoi. Un envoi jamais finalisé peut être supprimé au bout de 24 heures. Formats acceptés : JPEG, PNG, WebP, TIFF, 80 Mo maximum. L'envoi sur l'URL se fait depuis un serveur : le stockage n'accepte pas les envois directs depuis le navigateur d'un autre site.
/api/v1/albums/:id/uploadsPermission : photos:writeDemande les URL d'envoi (1 à 50 fichiers : name, type, size = taille exacte en octets). Chaque fichier reçoit key, upload_url, method, headers (Content-Type et Content-Length à envoyer) et expires_at, ou un message error s'il est refusé.
curl -X POST https://obturo.app/api/v1/albums/ALBUM_ID/uploads \
-H "Authorization: Bearer $OBTURO_KEY" \
-H "Content-Type: application/json" \
-d '{"files":[{"name":"IMG_0001.jpg","type":"image/jpeg","size":5242880}]}'
# puis, pour chaque fichier (même Content-Type et même taille que déclarés) :
curl -X PUT "UPLOAD_URL" -H "Content-Type: image/jpeg" --data-binary @IMG_0001.jpg/api/v1/albums/:id/photosPermission : photos:writeFinalise un envoi : key (reçue à l'étape 1), access (free, preview ou paywall ; preview par défaut). Réponse 201 avec la photo. 422 upload_missing si le fichier n'a pas été envoyé, 409 already_finalized si la clé est déjà enregistrée.
curl -X POST https://obturo.app/api/v1/albums/ALBUM_ID/photos \
-H "Authorization: Bearer $OBTURO_KEY" \
-H "Content-Type: application/json" \
-d '{"key":"KEY","access":"preview"}'/api/v1/albums/:id/photos?limit=&offset=Permission : public:read ou albums:readPhotos d'un album, dans l'ordre de l'album.
curl "https://obturo.app/api/v1/albums/ALBUM_ID/photos?limit=50" \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/photos?tag=&limit=&offset=Permission : public:read ou albums:readVos photos, les plus récentes d'abord, filtrables par hashtag d'album.
curl "https://obturo.app/api/v1/photos?tag=plage&limit=10" \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/photos/:idPermission : public:read ou albums:readDétail d'une photo.
curl https://obturo.app/api/v1/photos/PHOTO_ID \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/photos/:idPermission : photos:writeChange l'accès : free (offerte, aperçu sans filigrane), preview (aperçu payant filigrané) ou paywall (exclusive, floutée). L'aperçu est régénéré quand la photo devient ou cesse d'être offerte. Comme dans votre espace, aucun format de prix n'est exigé ici (seulement à la publication) : sans format actif, une photo payante s'affiche sans bouton d'achat.
curl -X PATCH https://obturo.app/api/v1/photos/PHOTO_ID \
-H "Authorization: Bearer $OBTURO_KEY" \
-H "Content-Type: application/json" \
-d '{"access":"free"}'/api/v1/photos/:idPermission : photos:writeSupprime la photo et ses fichiers. Refusé (409 has_orders) si elle a été vendue.
curl -X DELETE https://obturo.app/api/v1/photos/PHOTO_ID \ -H "Authorization: Bearer $OBTURO_KEY"
Prix
Formats proposés à l'achat pour les photos payantes et exclusives d'un album : un nom et un prix, fichier original livré.
/api/v1/albums/:id/pricesPermission : public:read, albums:read ou prices:writeFormats actifs de l'album.
curl https://obturo.app/api/v1/albums/ALBUM_ID/prices \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/albums/:id/prices/:priceIdPermission : public:read, albums:read ou prices:writeUn format actif (adresse renvoyée dans l'en-tête Location après sa création).
curl https://obturo.app/api/v1/albums/ALBUM_ID/prices/PRICE_ID \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/albums/:id/pricesPermission : prices:writeAjoute un format : name (40 caractères max), price_cents (entier en centimes, de 0 à 1 000 000). Réponse 201.
curl -X POST https://obturo.app/api/v1/albums/ALBUM_ID/prices \
-H "Authorization: Bearer $OBTURO_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Fichier HD","price_cents":1500}'/api/v1/albums/:id/prices/:priceIdPermission : prices:writeRetire un format. S'il figure dans une commande, il est désactivé (deactivated: true) plutôt que supprimé ; les commandes gardent de toute façon son nom et son prix. Retirer le dernier format d'un album publié ne le dépublie pas : ses photos payantes s'affichent alors sans bouton d'achat.
curl -X DELETE https://obturo.app/api/v1/albums/ALBUM_ID/prices/PRICE_ID \ -H "Authorization: Bearer $OBTURO_KEY"
Destinataires
Albums sur lien : les destinataires reçoivent le lien et le QR code par email. Albums privés : seuls les invités peuvent ouvrir l'album, après connexion avec leur email. Les albums publics n'ont pas de destinataires. Ajouter une adresse lui ouvre donc l'album : il faut recipients:write et albums:write, la permission qui donne aussi le lien secret. Une adresse désinscrite des emails d'un album le reste, même retirée puis ajoutée de nouveau.
/api/v1/albums/:id/recipientsPermission : recipients:writeDestinataires de l'album, avec leur état : pending (pas encore prévenu), notified (lien envoyé), unsubscribed (désinscrit des emails).
curl https://obturo.app/api/v1/albums/ALBUM_ID/recipients \ -H "Authorization: Bearer $OBTURO_KEY"
/api/v1/albums/:id/recipientsPermission : recipients:write et albums:writeAjoute une adresse (201). Album publié : l'email part tout de suite, sinon à la publication. Le champ notification de la réponse vaut sent, failed (envoi refusé), not_configured (emails non configurés), on_publish, ou unsubscribed (adresse désinscrite des emails de cet album : rien n'est envoyé). 409 duplicate si l'adresse est déjà enregistrée. 200 ajouts réussis par heure maximum, espace photographe compris (429 rate_limited).
curl -X POST https://obturo.app/api/v1/albums/ALBUM_ID/recipients \
-H "Authorization: Bearer $OBTURO_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"invite@exemple.fr"}'/api/v1/albums/:id/recipients/:recipientIdPermission : recipients:writeRetire le destinataire (album privé : il perd l'accès). Sa désinscription éventuelle est conservée.
curl -X DELETE https://obturo.app/api/v1/albums/ALBUM_ID/recipients/RECIPIENT_ID \ -H "Authorization: Bearer $OBTURO_KEY"
Hashtags
/api/v1/hashtagsPermission : public:read ou albums:readHashtags de vos albums lisibles par la clé, avec le nombre d'albums.
curl https://obturo.app/api/v1/hashtags \ -H "Authorization: Bearer $OBTURO_KEY"
Objets
Photo :
{
"id": "…",
"album_id": "…",
"width": 1600, "height": 2000,
"access": "preview", // free | preview | paywall
"preview_url": "https://…", // null pour une photo exclusive
"thumb_url": "https://…",
"watermarked": true,
"buy_url": "https://obturo.app/albums/…#photo-…", // ou null, voir ci-dessous
"created_at": "…"
}buy_url suit le lien de l'album : /albums/… (public), /album/… (privé, réservé aux invités connectés) ou /a/… (sur lien). Il vaut null pour une photo d'un album sur lien lue par une clé sans albums:write, qui n'a pas le lien secret.
Album. Clé public:read seule : id, slug, title, description, published_at, expires_at, url, tags (et formats dans le détail). Avec albums:read : tous les champs ci-dessous.
{
"id": "…", "slug": "…", "title": "…", "description": null,
"visibility": "unlisted", // public | unlisted | private
"status": "draft", // draft | published | expired
"published_at": null, "expires_at": null,
"url": "https://obturo.app/a/…", // album sur lien : fourni seulement aux clés albums:write
"tags": ["plage"],
"cover_photo_id": "…", "created_at": "…", "updated_at": "…",
"formats": [{ "id": "…", "name": "Fichier HD", "price_cents": 1500, "currency": "EUR", … }],
"photo_count": 12, // détail d'un album
"publish_blockers": [] // ce qui empêche la publication (album publié : la vente)
}Erreurs
Toute erreur répond en JSON avec un message en français (error), un code stable (code) et, selon le cas, le champ en cause (field) ou des précisions (details) :
HTTP/1.1 409 Conflict
{
"error": "Publication impossible. Il reste à ajouter un format de prix (3 photos sont payantes), ou passer les photos en « Offerte ».",
"code": "publish_blocked",
"details": [{ "code": "no_price", "message": "ajouter un format de prix (3 photos sont payantes), ou passer les photos en « Offerte »" }]
}| HTTP | code | Cas |
|---|---|---|
400 | bad_request, unknown_field | JSON invalide, champ inconnu, paramètre de recherche invalide, aucun champ à modifier |
401 | unauthorized | Clé absente, invalide ou révoquée |
403 | missing_scope | La clé n'a pas la permission demandée par la route (details liste les permissions attendues) |
403 | origin_not_allowed | Appel depuis un navigateur d'un domaine non autorisé (voir ci-dessus) |
403 | account_suspended | Compte photographe suspendu : aucune modification ni lecture de ses albums (une clé public:read reçoit des listes vides) |
404 | not_found | Ressource inexistante, d'un autre photographe, ou non lisible avec cette clé |
405 | Méthode non prise en charge par la route | |
409 | publish_blocked, duplicate, has_orders, already_finalized, public_album | Conflit avec l'état de la ressource |
413 | payload_too_large | Corps de requête de plus de 64 Ko |
415 | unsupported_media_type | Corps envoyé sans Content-Type: application/json |
422 | invalid_field, upload_missing, file_too_large, unreadable_image | Valeur refusée (field indique le champ en cause) |
429 | rate_limited | Limite de requêtes dépassée (en-tête Retry-After) |
500 | server_error | Erreur interne : réessayez |