GET/v1/shop
Tester la connexion
Renvoie la boutique liée à la clé, sa devise, son fuseau et les droits de la clé.
Créez une clé dans l’app : Boutique › Intégrations. Seul le propriétaire de la boutique peut le faire. Puis testez la connexion :
Votre caisse, votre ERP ou votre boutique en ligne créent des tickets, changent leur état et reçoivent chaque étape par webhook. Les clients reçoivent les mêmes notifications que depuis l’app.
Sur demande pendant le lancement : écrivez-nous et nous activons l’API pour votre boutique.
https://api.tfa9adni.com/v1Toutes les requêtes partent de cette adresse, en HTTPS, avec du JSON dans les deux sens.
Chaque réponse a la même enveloppe : success et data, ou success à false et une error avec un code. Les montants sont en unités mineures (millimes, centimes) avec la devise et le nombre de décimales. Les dates sont en ISO 8601, en UTC.
GET/v1/shop
Renvoie la boutique liée à la clé, sa devise, son fuseau et les droits de la clé.
Créez une clé dans l’app : Boutique › Intégrations. Seul le propriétaire de la boutique peut le faire. Puis testez la connexion :
Envoyez la clé dans l’en-tête Authorization. Une clé ne donne accès qu’à sa boutique.
La clé s’affiche une seule fois à sa création. Gardez-la sur votre serveur ou dans votre caisse, jamais dans une page web ni dans un message. Une clé révoquée cesse de marcher en moins d’une minute. Cinq clés actives au plus par boutique.
tickets:readLire les tickets.tickets:writeCréer des tickets et changer leur état.clients:readRetrouver un client et lire sa carte de fidélité.loyalty:writeAjouter des tampons et utiliser une récompense.« Accès complet » donne les quatre droits, « Lecture seule » donne tickets:read et clients:read.
workshopÀ l’atelier, en cours.readyPrêt, en attente du client.impossiblePas réparable, l’article attend son client.collectedRécupéré. outcome dit s’il était prêt ou impossible.abandonedClôturé : jamais récupéré, ou sans nouvelles de la boutique pendant 60 jours. abandon_reason dit lequel (uncollected, no_news).POST/v1/tickets
Crée un ticket à l’atelier. Son numéro suit ceux de l’app.
L’en-tête Idempotency-Key est obligatoire : si le réseau coupe, renvoyez la même requête avec la même clé, vous recevez le même ticket et replayed à true, jamais un doublon. Le numéro suit ceux de l’app.
En-têtes
Idempotency-Key string requis Une clé unique par tentative : la même requête renvoyée donne le même ticket.
Corps JSON
service string requis Le service, comme dans l’app.
type string optionnel repair (réparation) ou prepare (commande). repair par défaut.
item string optionnel L’article déposé.
description string optionnel Une note pour l’atelier.
items array optionnel Plusieurs articles (2 à 20) : label, service, price_minor.
client_id uuid optionnel Un client qui connaît déjà la boutique.
client_phone string optionnel Le numéro du client : le ticket lui est proposé.
price_minor integer optionnel Le prix, en unités mineures.
deposit_minor integer optionnel L’acompte, en unités mineures.
promised_at datetime optionnel Le jour promis, en ISO 8601.
external_ref string optionnel Votre référence, 64 caractères au plus.
client_phone ne relie personne : le ticket est proposé au compte qui porte ce numéro, qui l’accepte ou bloque la boutique. La réponse est la même qu’un compte existe ou non.
GET/v1/tickets
Filtre, pagine et synchronise les tickets de la boutique.
Filtres : state (plusieurs, séparés par des virgules), number, client_id, phone, external_ref, created_after, created_before, updated_since. limit de 1 à 100 (50 par défaut). Si has_more est vrai, renvoyez next_cursor dans cursor. Avec updated_since, l’ordre suit la date de mise à jour : pratique pour synchroniser.
Paramètres d’URL
state string optionnel Un ou plusieurs états, séparés par des virgules.
number integer optionnel Le numéro du ticket.
phone string optionnel Le numéro saisi sur le ticket.
external_ref string optionnel Votre référence.
updated_since datetime optionnel Seulement ce qui a changé depuis cette date.
limit integer optionnel De 1 à 100, 50 par défaut.
cursor string optionnel Le next_cursor de la page précédente.
GET/v1/tickets/{id}
Un ticket par son id. Celui d’une autre boutique répond 404.
GET/v1/tickets/by-number/{n}
Le ticket ouvert le plus récent qui porte ce numéro.
Les numéros repartent à 1 après 9999 : by-number renvoie le ticket ouvert le plus récent, sinon le plus récent, et meta.others liste les autres.
POST/v1/tickets/{id}/status
Prêt, impossible, retour à l’atelier ou récupéré.
Les mêmes règles que dans l’app : prêt ou impossible depuis l’atelier, récupéré depuis prêt ou impossible, retour à l’atelier depuis prêt ou impossible. Un ticket récupéré peut être rouvert pendant 24 h, ensuite la réponse est 409 avec reason REOPEN_WINDOW. Le client reçoit la même notification, et la fidélité compte comme dans l’app.
Corps JSON
status string requis ready, impossible, workshop ou collected.
POST/v1/tickets/{id}/notified
Vous avez prévenu le client vous-même.
Vous avez prévenu le client vous-même, par SMS ou par téléphone ? Dites-le, la page du client l’affiche.
Corps JSON
channel string requis sms, whatsapp ou call.
Une boutique ne voit que les clients qui la connaissent déjà : un ticket accepté chez elle ou un passage au comptoir. Chercher un numéro inconnu, ou le numéro d’un client qui a bloqué la boutique, donne la même réponse 404.
Le nom affiché respecte le choix du client : s’il cache son nom, vous voyez « Sana B. ».
GET/v1/clients
Par téléphone, parmi les clients qui connaissent déjà la boutique.
Paramètres d’URL
phone string requis Le numéro, au format international.
GET/v1/clients/{id}
Nom affiché, tickets ouverts et au total, carte de fidélité.
GET/v1/clients/{id}/loyalty
Le programme de la boutique et la carte du client.
POST/v1/clients/{id}/loyalty/stamps
Ajoute de 1 à 10 tampons sur la carte.
Tampons et récompenses exigent une Idempotency-Key. 20 tampons au plus par client et par jour et par clé. Si la fidélité est éteinte, la réponse est 409 LOYALTY_OFF.
En-têtes
Idempotency-Key string requis Obligatoire.
Corps JSON
count integer requis De 1 à 10.
POST/v1/clients/{id}/loyalty/redeem
Consomme une récompense disponible sur la carte.
En-têtes
Idempotency-Key string requis Obligatoire.
Donnez une adresse https dans l’app : Tfa9adni y envoie un POST à chaque étape d’un ticket. Un seul webhook par boutique.
ticket.createdUn ticket est créé, dans l’app ou par l’API.ticket.readyLe ticket est prêt.ticket.impossibleLe ticket est marqué impossible.ticket.reopenedLe ticket revient à l’atelier.client.notifiedLe client a été prévenu (WhatsApp, app, SMS, appel).ticket.collectedLe client a récupéré son article.ticket.abandonedLe ticket est clôturé : prêt et jamais récupéré (60 jours), ou resté à l’atelier sans nouvelles (60 jours). reason vaut uncollected ou no_news.quote.acceptedLe client accepte un nouveau prix.client.linkedUn client est relié au ticket.pingLe bouton « Envoyer un test » de l’app.Chaque envoi porte l’en-tête Tfa9adni-Signature : t est l’heure d’envoi en secondes, v1 le HMAC-SHA256 de « t.corps » avec le secret du webhook, en hexadécimal. Calculez-le sur le corps brut, comparez en temps constant, et refusez un t à plus de 5 minutes. Pendant 24 h après un changement de secret, deux v1 sont envoyés.
Sans réponse 2xx, nouvel essai après 1 min, 5 min, 30 min, 2 h, 6 h, 12 h et 24 h, puis l’envoi est abandonné. Après 20 échecs d’affilée sur 72 h, ou une réponse 410, le webhook s’éteint et le propriétaire est prévenu. Les redirections ne sont pas suivies, et les adresses privées sont refusées.
En cas d’erreur, success est à false et error.code dit pourquoi. details.reason précise un 409.
VALIDATION_ERROR Corps ou paramètre invalide, details dit lequel. UNAUTHENTICATED Clé absente, fausse ou révoquée. FORBIDDEN La clé n’a pas ce droit (details.scope). SHOP_NOT_APPROVED La boutique n’est pas encore validée. ACCOUNT_DEACTIVATED La boutique est désactivée. API_DISABLED L’API n’est pas encore ouverte pour cette boutique. NOT_FOUND Introuvable pour cette boutique. CONFLICT Action impossible dans cet état (details.reason). PROFANITY Le texte contient un mot interdit. TOO_MANY_ATTEMPTS Trop de requêtes ou de tickets, réessayez plus tard. Chaque réponse porte les en-têtes RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset. Au-delà, la réponse est 429 : attendez RateLimit-Reset secondes.