Documentation / Référence API / v1
Spécification du protocoleRéférence API
Documentation / Référence API / v1
STABLE · DEPUIS V1

Référence API

Endpoints, paramètres, codes de réponse et exemples pour intégrer le protocole de vérification des billets Praticable.

Spécification du protocole

Cette page couvre l'utilisation pratique de l'API. Pour comprendre le fonctionnement du protocole (cryptographie, format de payload, flux de vérification), consultez la spécification du protocole →

§ 01

Vue d'ensemble

L'API expose trois endpoints pour les applications scanner, servis par chaque instance issuer. L'URL de base est l'iss du déploiement client (ex : https://tickets.example.com).

Endpoint Description
POST /api/v1/scanners/enroll

Échange un token d'enrollment contre une clé API

POST /api/v1/tickets/check-in

Marque un billet comme utilisé à l'entrée

GET /api/v1/tickets/{tid}/status

Consulte l'état courant d'un billet sans le modifier

Un endpoint de découverte public est aussi disponible à {iss}/.well-known/ticket-issuer.json (sans authentification). Sa structure est décrite dans la spécification du protocole.

§ 02

Authentification

Les endpoints check-in et status nécessitent un Bearer token. La clé API est obtenue via l'échange d'enrollment (ci-dessous). L'endpoint d'enrollment lui-même ne requiert pas de Bearer ; il utilise le token d'enrollment comme credential.

En-tête d'authentification
Authorization: Bearer sk_live_01HXXX...long-opaque-key
  • Chaque clé API est scopée à un événement (et optionnellement une porte).
  • Les clés expirent au timestamp expires_at retourné lors de l'enrollment.
  • Une clé invalide, expirée ou révoquée retourne 401 UNAUTHORIZED.
  • Les clés DOIVENT être stockées dans le stockage sécurisé de la plateforme (Keychain, Keystore).
§ 03

Enroll scanner

POST /api/v1/scanners/enroll pas de Bearer requis

Échange un token d'enrollment (provenant d'un QR de setup) contre une clé API longue durée. Le token est à usage unique : une seconde tentative avec le même token retourne INVALID_TOKEN.

curl
curl -X POST https://tickets.example.com/api/v1/scanners/enroll \
  -H "Content-Type: application/json" \
  -d '{
    "token": "st_01HXXX...opaque-enrollment-token",
    "scanner_id": "550e8400-e29b-41d4-a716-446655440000",
    "scanner_name": "iPhone 14 - Entrée Nord",
    "scanner_platform": "ios-18.2"
  }'

Paramètres de requête

Champ Description
token
stringrequis

Token d'enrollment du QR de setup. Opaque, ≥ 128 bits d'entropie, usage unique.

scanner_id
stringrequis

Identifiant unique et stable généré par l'app scanner (UUID recommandé). Utilisé pour l'identification cross-enrollment et l'audit.

scanner_name
stringoptionnel

Nom lisible affiché dans l'interface admin (ex : nom de l'appareil).

scanner_platform
stringoptionnel

Informations de plateforme pour le support et le suivi de compatibilité.

Réponse · 200 OK

Réponse JSON
{
  "api_key": "sk_live_01HXXX...long-opaque-key",
  "api_key_id": "sck_01HXXX...ULID",
  "scope": {
    "eid": "evt_01HXXX...ULID",
    "event_name": "Gala Concert",
    "event_date": "2026-06-15T20:00:00Z",
    "gate_id": "north",
    "expires_at": "2026-06-16T02:00:00Z"
  },
  "issuer": {
    "name": "Festival des Arts",
    "iss": "https://tickets.example.com"
  }
}
Champ Description
api_key
string

Clé API longue durée. Stocker dans le Keychain/Keystore. Ne jamais logger, inclure dans les rapports de crash ou persister en clair.

api_key_id
string

Identifiant de clé, sûr à logger. Utilisé pour les pistes d'audit.

scope
object

Scope de la clé : eid, event_name, event_date, gate_id (optionnel, null si toutes les portes), expires_at.

issuer
object

Informations de l'issuer : name (nom commercial) et iss (URL de base).

Erreurs d'enrollment

Code Signification
INVALID_TOKEN
400

Le token est malformé, expiré ou a déjà été utilisé.

TOKEN_REVOKED
403

Le token a été révoqué par l'organisateur avant utilisation.

PROTOCOL_VERSION_UNSUPPORTED
400

L'issuer ne supporte pas la version de protocole demandée.

RATE_LIMITED
429

Trop de tentatives d'enrollment depuis cette IP.

INTERNAL
500

Erreur serveur. Réessayer avec backoff exponentiel.

§ 04

Check-in

POST /api/v1/tickets/check-in Bearer requis

Marque un billet comme utilisé. Idempotent : rejouer la même requête avec le même idempotency_key retourne le même résultat. Une seconde requête avec le même tid mais un idempotency_key différent est traitée comme double-scan et retourne already_used.

Requête

curl
curl -X POST https://tickets.example.com/api/v1/tickets/check-in \
  -H "Authorization: Bearer sk_live_01HXXX..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "tid": "01HXXX...ULID",
    "eid": "evt_01HXXX...ULID",
    "scanned_at": "2026-06-15T19:42:13Z",
    "scanner_id": "gate-north-01",
    "gate_id": "north",
    "idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
  }'
Champ Description
tid
stringrequis

Identifiant du billet.

eid
stringrequis

Identifiant de l'événement.

scanned_at
stringrequis

Timestamp du scan (ISO 8601 UTC). L'issuer DOIT accepter des timestamps jusqu'à 48h dans le passé pour les scans hors ligne en file d'attente.

ex : 2026-06-15T19:42:13Z
scanner_id
stringrequis

Identifiant unique et stable de l'appareil.

gate_id
stringoptionnel

Identifiant de porte, inclus à des fins d'audit.

idempotency_key
stringrequis

UUID v4 par tentative de scan. Déduplique les retries. Peut aussi être passé via l'en-tête Idempotency-Key.

Réponse · 200 OK

La réponse est toujours 200 avec un champ status indiquant le résultat. Cela permet aux scanners de distinguer les différents cas sans parser les codes HTTP.

ADMIS 200 · status: valid
{
  "status": "valid",
  "ticket": {
    "tid": "01HXXX...ULID",
    "eid": "evt_01HXXX...ULID",
    "holder_name": "J. Smith",
    "ticket_type": "VIP",
    "checked_in_at": "2026-06-15T19:42:13Z"
  }
}
REFUSÉ 200 · status: already_used
{
  "status": "already_used",
  "ticket": {
    "tid": "01HXXX...ULID",
    "eid": "evt_01HXXX...ULID",
    "holder_name": "J. Smith",
    "ticket_type": "VIP",
    "checked_in_at": "2026-06-15T19:30:00Z"
  }
}

Valeurs de statut

Statut Signification
valid
200

Billet valide, check-in effectué. Premier scan réussi.

already_used
200

Billet déjà utilisé. La réponse inclut le checked_in_at original.

revoked
200

Billet révoqué (remboursement, fraude, etc.).

wrong_event
200

Billet valide mais pour un autre événement.

expired
200

La date d'expiration du billet est passée.

invalid_signature
400

Le scanner signale que la signature n'a pas pu être vérifiée. Loggé pour audit côté issuer.

§ 05

Statut du billet

GET /api/v1/tickets/{tid}/status Bearer requis

Retourne l'état courant d'un billet sans le modifier. Utile pour les pré-vérifications des scanners, les outils admin ou la réconciliation post-événement.

curl
curl https://tickets.example.com/api/v1/tickets/01HXXX...ULID/status \
  -H "Authorization: Bearer sk_live_01HXXX..."
Réponse · 200 OK
{
  "tid": "01HXXX...ULID",
  "eid": "evt_01HXXX...ULID",
  "status": "valid",
  "holder_name": "J. Smith",
  "ticket_type": "VIP",
  "issued_at": "2026-05-01T10:00:00Z",
  "checked_in_at": null
}
Statut Signification
valid

Billet émis, non utilisé, non expiré.

checked_in

Billet déjà utilisé. checked_in_at contient le timestamp.

revoked

Billet révoqué (remboursement, fraude).

expired

La date d'expiration est passée.

Si le tid n'existe pas, l'issuer retourne 404 NOT_FOUND.

§ 06

Idempotence

L'endpoint de check-in utilise une clé d'idempotence pour dédupliquer les retries. Générez un UUID v4 unique par tentative de scan et passez-le soit dans le body (idempotency_key), soit dans l'en-tête HTTP (Idempotency-Key).

Même tid + même clé

Retry du même scan. Le serveur retourne le résultat d'origine (idempotent).

Même tid + clé différente

Double-scan détecté. Le serveur retourne already_used.

Même clé + body différent

Conflit d'idempotence. Le serveur retourne 409 IDEMPOTENCY_CONFLICT.

Mode hors ligne

En cas d'échec réseau, le scanner met la requête en file d'attente avec son idempotency_key et accepte le billet de manière optimiste. Quand la connexion revient, les requêtes en file sont vidées. L'idempotence de l'issuer garantit l'absence d'incohérence. Voir la spécification du mode hors ligne.

§ 07

Codes d'erreur

Toutes les réponses non-2xx utilisent une enveloppe d'erreur standard :

Enveloppe d'erreur
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "API key is missing, expired, or revoked.",
    "details": {}
  }
}
Code Type Description
400 INVALID_REQUEST Payload malformé ou champs requis manquants.
400 INVALID_TOKEN Token d'enrollment malformé, expiré ou déjà utilisé.
400 PROTOCOL_VERSION_UNSUPPORTED L'issuer ne supporte pas la version de protocole du payload.
401 UNAUTHORIZED Clé API manquante, invalide, expirée ou révoquée.
403 FORBIDDEN Clé API valide mais sans permission pour cette ressource.
403 TOKEN_REVOKED Token d'enrollment révoqué par l'organisateur avant utilisation.
404 NOT_FOUND La ressource demandée n'existe pas.
409 IDEMPOTENCY_CONFLICT Même clé d'idempotence utilisée avec un payload différent.
429 RATE_LIMITED Trop de requêtes. L'en-tête Retry-After indique le délai.
500 INTERNAL Erreur serveur. Réessayer avec backoff exponentiel.
§ 08

Rate limiting

Les issuers DOIVENT limiter le débit de chaque endpoint par credential scanner. Quand la limite est atteinte, le serveur retourne 429 RATE_LIMITED avec un en-tête Retry-After indiquant le nombre de secondes à attendre.

  • Endpoint d'enrollment : rate-limit par IP et par token pour prévenir le brute-force.
  • Endpoint de check-in : rate-limit par credential scanner.
  • Endpoint de statut : rate-limit par credential scanner.

Les scanners DEVRAIENT implémenter un backoff exponentiel sur les réponses 429 et respecter la valeur de Retry-After.

Dernière mise à jour : 20 mai 2026