{"openapi":"3.0.3","info":{"title":"TST SMS Partner API","version":"1.0.0","description":"API d'envoi de SMS pour les partenaires TST Digital Products. Authentification par cle API (Bearer). Les cles tst_test_ fonctionnent en bac a sable (aucun envoi reel, aucun debit). 1 credit = 1 segment SMS.","contact":{"name":"TST Digital Products","url":"https://tstdigitalproducts.com"}},"servers":[{"url":"https://tstdigitalproducts.com/v1"}],"security":[{"ApiKeyAuth":[]}],"tags":[{"name":"Messages"},{"name":"Batches"},{"name":"Balance"},{"name":"Webhooks"},{"name":"Meta"}],"paths":{"/ping":{"get":{"tags":["Meta"],"security":[],"summary":"Test de connectivite","responses":{"200":{"description":"OK"}}}},"/openapi.json":{"get":{"tags":["Meta"],"security":[],"summary":"Cette specification","responses":{"200":{"description":"OK"}}}},"/messages":{"post":{"tags":["Messages"],"summary":"Envoyer un SMS","description":"Scope messages:write. Chaque reponse porte X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset et X-Request-Id. 429 => Retry-After.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendMessageRequest"}}}},"responses":{"202":{"description":"Message accepte (queued ou scheduled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}},"get":{"tags":["Messages"],"summary":"Lister les messages (pagination par curseur)","description":"Scope messages:read.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"status","in":"query","schema":{"type":"string","enum":["scheduled","queued","sending","sent","delivered","failed","canceled"]}},{"name":"batch_id","in":"query","schema":{"type":"string"}},{"name":"to","in":"query","schema":{"type":"string"}},{"name":"created_after","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"created_before","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/messages/bulk":{"post":{"tags":["Messages"],"summary":"Envoyer un lot de SMS (variables par destinataire)","description":"Scope messages:write. Le lot est refuse en entier si un destinataire est invalide (details par index).","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMessageRequest"}}}},"responses":{"202":{"description":"Lot accepte","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Batch"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/messages/{id}":{"get":{"tags":["Messages"],"summary":"Consulter un message","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}},"delete":{"tags":["Messages"],"summary":"Annuler un message planifie (credits rembourses)","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Message annule","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/batches":{"get":{"tags":["Batches"],"summary":"Lister les lots","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Batch"}},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true}}}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/batches/{id}":{"get":{"tags":["Batches"],"summary":"Consulter un lot (compteurs par statut)","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Batch"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/balance":{"get":{"tags":["Balance"],"summary":"Solde de credits","description":"Scope balance:read.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"Lister les endpoints","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true}}}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"}}},"post":{"tags":["Webhooks"],"summary":"Enregistrer un endpoint (verification par challenge immediate)","description":"Scope webhooks:manage. TST POSTe un evenement endpoint.verification signe ; votre URL doit repondre 2xx avec {\"challenge\": \"<valeur recue>\"}. URL HTTPS publique uniquement (IP privees refusees, redirections refusees). Le secret (whsec_...) n'est affiche qu'une fois.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointRequest"}}}},"responses":{"201":{"description":"Cree","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"}}}},"/webhooks/signing-secret":{"post":{"tags":["Webhooks"],"summary":"Creer / remplacer le secret de signature du compte (URL par message ou par lot)","responses":{"201":{"description":"Secret affiche une seule fois","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"type":"string"},"secret":{"type":"string"},"created_at":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"}}}},"/webhooks/{id}":{"get":{"tags":["Webhooks"],"summary":"Consulter un endpoint","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}},"patch":{"tags":["Webhooks"],"summary":"Modifier un endpoint (un changement d'URL relance la verification)","parameters":[{"$ref":"#/components/parameters/Id"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointRequest"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"}}},"delete":{"tags":["Webhooks"],"summary":"Supprimer un endpoint","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Supprime"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/webhooks/{id}/verify":{"post":{"tags":["Webhooks"],"summary":"Relancer la verification (reactive un endpoint desactive)","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/webhooks/{id}/rotate-secret":{"post":{"tags":["Webhooks"],"summary":"Regenerer le secret de l'endpoint","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Nouveau secret affiche une seule fois","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpoint"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"tst_live_... | tst_test_..."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255},"description":"Cle unique (UUID recommande). Meme cle + meme corps pendant 24 h => reponse rejouee (en-tete Idempotent-Replayed: true). Meme cle + corps different => 409."},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Valeur next_cursor de la page precedente."},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"Id":{"name":"id","in":"path","required":true,"schema":{"type":"string"}}},"responses":{"Error":{"description":"Erreur","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","permission_error","insufficient_credits_error","rate_limit_error","api_error","service_unavailable_error"]},"code":{"type":"string","example":"invalid_phone_number"},"message":{"type":"string"},"param":{"type":"string","nullable":true},"details":{"type":"array","items":{"type":"object"},"nullable":true}}},"request_id":{"type":"string","example":"req_01j8..."}}},"SendMessageRequest":{"type":"object","required":["to","body"],"properties":{"to":{"type":"string","example":"+242061234567","description":"Numero E.164."},"body":{"type":"string","maxLength":2000,"example":"Bonjour {{name}}, votre code est {{code}}."},"from":{"type":"string","maxLength":11,"example":"TST","description":"Sender ID alphanumerique (1-11) ou numero. Doit etre autorise chez Wirepick."},"variables":{"type":"object","additionalProperties":true,"example":{"name":"Awa","code":"4821"}},"scheduled_at":{"type":"string","format":"date-time","nullable":true,"description":"Envoi differe (>= 30 s, <= 30 jours)."},"webhook_url":{"type":"string","format":"uri","nullable":true,"description":"URL HTTPS notifiee pour ce message (signee avec le secret de compte)."},"metadata":{"type":"object","additionalProperties":true,"nullable":true}}},"BulkMessageRequest":{"type":"object","required":["body","recipients"],"properties":{"body":{"type":"string","example":"Bonjour {{name}} !"},"from":{"type":"string","example":"TST"},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"webhook_url":{"type":"string","format":"uri","nullable":true},"metadata":{"type":"object","nullable":true},"recipients":{"type":"array","minItems":1,"maxItems":1000,"items":{"type":"object","required":["to"],"properties":{"to":{"type":"string","example":"+242061234567"},"variables":{"type":"object","additionalProperties":true},"metadata":{"type":"object","nullable":true}}}}}},"Message":{"type":"object","properties":{"id":{"type":"string","example":"msg_01j8..."},"object":{"type":"string","enum":["message"]},"mode":{"type":"string","enum":["test","live"]},"status":{"type":"string","enum":["scheduled","queued","sending","sent","delivered","failed","canceled"]},"to":{"type":"string"},"from":{"type":"string"},"body":{"type":"string"},"encoding":{"type":"string","enum":["gsm7","ucs2"]},"segments":{"type":"integer"},"credits_charged":{"type":"integer"},"batch_id":{"type":"string","nullable":true},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"delivered_at":{"type":"string","format":"date-time","nullable":true},"failed_at":{"type":"string","format":"date-time","nullable":true},"error":{"type":"object","nullable":true,"properties":{"code":{"type":"string"},"message":{"type":"string"}}},"provider_message_id":{"type":"string","nullable":true},"webhook_url":{"type":"string","nullable":true},"metadata":{"type":"object"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"Batch":{"type":"object","properties":{"id":{"type":"string","example":"batch_01j8..."},"object":{"type":"string","enum":["batch"]},"mode":{"type":"string"},"status":{"type":"string","enum":["processing","completed"]},"from":{"type":"string"},"body":{"type":"string"},"total":{"type":"integer"},"counts":{"type":"object","properties":{"scheduled":{"type":"integer"},"queued":{"type":"integer"},"sent":{"type":"integer"},"delivered":{"type":"integer"},"failed":{"type":"integer"},"canceled":{"type":"integer"}}},"credits_charged":{"type":"integer"},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"webhook_url":{"type":"string","nullable":true},"metadata":{"type":"object"},"created_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"Balance":{"type":"object","properties":{"object":{"type":"string","enum":["balance"]},"mode":{"type":"string"},"credits":{"type":"integer","nullable":true,"description":"null en mode test"},"unit":{"type":"string","enum":["sms_segment"]},"low_balance_threshold":{"type":"integer"},"sandbox":{"type":"boolean"}}},"WebhookEndpointRequest":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","example":"https://partner.example.com/tst/webhooks"},"events":{"type":"array","items":{"type":"string","enum":["*","message.sent","message.delivered","message.failed","message.received","balance.low"]}},"description":{"type":"string","nullable":true}}},"WebhookEndpoint":{"type":"object","properties":{"id":{"type":"string","example":"whe_01j8..."},"object":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["pending_verification","active","disabled"]},"secret":{"type":"string","description":"Affiche uniquement a la creation et a la rotation."},"verification":{"type":"object","properties":{"ok":{"type":"boolean"},"reason":{"type":"string","nullable":true}}},"verified_at":{"type":"string","format":"date-time","nullable":true},"consecutive_failures":{"type":"integer"},"last_success_at":{"type":"string","format":"date-time","nullable":true},"last_failure_at":{"type":"string","format":"date-time","nullable":true},"disabled_at":{"type":"string","format":"date-time","nullable":true},"disabled_reason":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}}},"WebhookEvent":{"type":"object","description":"Corps envoye a votre URL. En-tete TST-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, \"<t>.<corps brut>\")>.","properties":{"id":{"type":"string","example":"evt_01j8..."},"object":{"type":"string","enum":["event"]},"type":{"type":"string","enum":["message.sent","message.delivered","message.failed","message.received","balance.low","endpoint.verification"]},"mode":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","description":"message.* => {message: Message} ; balance.low => {balance, threshold} ; message.received => {inbound_message} ; endpoint.verification => {challenge, endpoint_id}"}}},"Scopes":{"type":"string","enum":["messages:write","messages:read","webhooks:manage","balance:read"]}}}}