# StudEvent API — documentation complète

Version : v1 · stable · 2026-05-23
Base URL : https://studevent-app.com/api/v1
Format : JSON · UTF-8
Auth : header `Authorization: Bearer sk_live_<key>`

StudEvent est une plateforme de billetterie pour BDE. Cette API publique permet d'intégrer la billetterie directement sur le site du BDE.

────────────────────────────────────────────────
## 1. Authentification

Toutes les requêtes doivent porter un header `Authorization` avec une clé API créée depuis le dashboard (Paramètres → API & Webhooks). Chaque clé est rattachée à une organisation (un BDE) et porte un ensemble de scopes qui limitent ce qu'elle peut faire.

### Scopes disponibles

| Scope | Permet |
|-------|--------|
| events:read | Lire les événements et tarifs publiés |
| orders:read | Lire les commandes (statut, total, billets) |
| tickets:read | Lire les billets émis |
| tickets:write | Marquer un billet comme scanné (point d'entrée custom) |
| checkout:write | Créer un checkout — démarrer un paiement Stripe |
| webhooks:manage | CRUD sur les endpoints webhooks via l'API |

### Exemple

```bash
curl https://studevent-app.com/api/v1/events \
  -H "Authorization: Bearer sk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
```

**Important** : les clés `sk_live_…` doivent rester côté serveur. Ne jamais les exposer en JavaScript public.

────────────────────────────────────────────────
## 2. Erreurs

L'API retourne des codes HTTP standards et un body JSON `{ "error": { "type": "...", "message": "..." } }`.

| HTTP | type | Sens |
|------|------|------|
| 400 | invalid_request | Paramètres manquants ou invalides |
| 401 | authentication_required / invalid_api_key | Pas d'Authorization ou clé inconnue/révoquée |
| 403 | permission_denied / insufficient_scope | Clé valide mais scope manquant |
| 404 | resource_not_found | Ressource hors organisation |
| 429 | rate_limited | Réessayer plus tard |
| 500 | internal_error | Erreur serveur |

────────────────────────────────────────────────
## 3. Pagination

Les endpoints de liste retournent : `{ object: "list", data: [...], has_more: boolean, next_cursor: string|null }`. Pour la page suivante, repasser `next_cursor` dans le paramètre `?cursor=…`.

────────────────────────────────────────────────
## 4. Événements

### GET /v1/events

Liste des événements de l'organisation. Scope: `events:read`.

Query :
- `status` (string?) : draft | published (défaut) | closed | archived
- `limit` (int?) : 1 à 100, défaut 20
- `cursor` (string?) : ISO timestamp retourné par `next_cursor`

Réponse :

```json
{
  "object": "list",
  "data": [
    {
      "object": "event",
      "id": "uuid",
      "slug": "gala-2026",
      "name": "Gala 2026",
      "description": "…",
      "location": "Palais des Congrès, Lyon",
      "starts_at": "2026-06-14T19:30:00Z",
      "ends_at": "2026-06-15T03:00:00Z",
      "type": "smart_match",
      "status": "published",
      "cover_url": "https://…",
      "created_at": "…"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

### GET /v1/events/{id_or_slug}

Détail d'un événement. Accepte UUID ou slug. Inclut les `ticket_types` inline avec quotas et disponibilité.

```json
{
  "object": "event",
  "id": "uuid",
  "slug": "gala-2026",
  "ticket_types": [
    {
      "object": "ticket_type",
      "id": "uuid",
      "name": "VIP",
      "price_cents": 4500,
      "currency": "eur",
      "quota": 50,
      "sold_count": 12,
      "available": 38,
      "priority": 0,
      "conditions": {}
    }
  ]
}
```

────────────────────────────────────────────────
## 5. Checkout

### POST /v1/checkout/sessions

Crée une commande en statut `pending` et renvoie l'URL Stripe vers laquelle rediriger l'acheteur. Le billet est émis automatiquement après paiement (webhook Stripe → email + webhook `order.paid` vers le BDE). Scope: `checkout:write`.

Body JSON :

| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| event_id | string | oui | UUID de l'event publié |
| ticket_type_id | string | oui | UUID du tarif choisi |
| email | string | oui | Email acheteur (le billet y sera envoyé) |
| first_name | string | oui | Prénom porteur |
| last_name | string | oui | Nom porteur |
| responses | object | non | Réponses au formulaire custom |

Réponse 201 :

```json
{
  "object": "checkout_session",
  "order_id": "uuid",
  "order_ref": "O-2026-X4F8K2QM",
  "url": "https://buy.stripe.com/test_…?client_reference_id=uuid"
}
```

────────────────────────────────────────────────
## 6. Commandes

### GET /v1/orders/{ref}

Accepte un `display_ref` (`O-2026-XXXXXXXX`) ou un UUID. Renvoie la commande + tickets émis inline. Scope: `orders:read`.

────────────────────────────────────────────────
## 7. Billets

### GET /v1/tickets/{ref}

Récupère un billet par `display_ref` (`T-2026-XXXXXXXX`) ou UUID. Inclut `scanned_at` si déjà validé. Scope: `tickets:read`.

### POST /v1/tickets/{ref}/scan

Marque le billet comme scanné — usage server-to-server (tourniquet custom, app native partenaire). Scope: `tickets:write`.

Body JSON optionnel :
- `qr` (string) : payload QR brut → vérification HMAC
- `gate_id` (string) : UUID porte
- `device_id` (string) : ID device libre

Réponse : `{ "object": "scan_result", "result": "ok"|"duplicate"|"expired"|"bad_signature", "ticket": {…}, "previous_scan_at": "…" }`

────────────────────────────────────────────────
## 8. Webhooks

Le BDE crée un ou plusieurs endpoints HTTPS sur son site qui recevront des POST JSON signés quand un événement survient sur StudEvent.

### Format des requêtes entrantes

```
POST https://mon-bde.fr/api/studevent-webhook
Headers:
  studevent-signature: t=1748044819,v1=<hex_hmac_sha256>
  studevent-event: order.paid
  content-type: application/json

{
  "id": "evt_8KqLm2",
  "type": "order.paid",
  "created": 1748044819,
  "data": { … }
}
```

### Vérification de signature

Le HMAC-SHA256 est calculé avec le `signing_secret` (whsec_…) de l'endpoint, sur la chaîne `${timestamp}.${rawBody}`.

```js
import crypto from "node:crypto";
function verify(headers, body) {
  const parts = Object.fromEntries(headers["studevent-signature"].split(",").map((p) => p.split("=")));
  const expected = crypto.createHmac("sha256", process.env.STUDEVENT_WEBHOOK_SECRET)
    .update(`${parts.t}.${body}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(parts.v1, "hex"));
}
```

Rejeter toute requête où `|now − t| > 300s` (anti-rejeu).

### Types d'événements

| Type | data |
|------|------|
| order.paid | order_id, order_ref, event_id, email, total_cents, currency |
| order.refunded | order_id, order_ref, amount_cents |
| ticket.created | ticket_id, ticket_ref, order_id, order_ref, event_id, ticket_type_id, holder |
| ticket.scanned | ticket_ref, event_id, scanned_at |
| refund.created | order_id, order_ref, ticket_id, amount_cents, reason |

### Gérer les endpoints

`GET /v1/webhook_endpoints` (list), `POST /v1/webhook_endpoints` (create — renvoie `signing_secret` UNE SEULE FOIS), `DELETE /v1/webhook_endpoints/{id}`. Scope: `webhooks:manage`.

────────────────────────────────────────────────
## 9. MCP server pour Claude / Cursor / Windsurf

Le package `@studevent/mcp` expose tous ces endpoints comme tools MCP. Voir https://studevent-app.com/docs#ai-tools pour la config (Claude Desktop, Cursor, Windsurf).

────────────────────────────────────────────────
## 10. Quickstart

```bash
# Lister tes events
curl https://studevent-app.com/api/v1/events \
  -H "Authorization: Bearer $STUDEVENT_KEY"

# Créer un checkout
curl -X POST https://studevent-app.com/api/v1/checkout/sessions \
  -H "Authorization: Bearer $STUDEVENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "<uuid>",
    "ticket_type_id": "<uuid>",
    "email": "leo@example.com",
    "first_name": "Léo",
    "last_name": "Martin"
  }'

# Inscrire un endpoint webhook
curl -X POST https://studevent-app.com/api/v1/webhook_endpoints \
  -H "Authorization: Bearer $STUDEVENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://mon-bde.fr/hook", "enabled_events": ["order.paid","ticket.scanned"]}'
```
