Retour à la docAPI v1 · OAuth2 · OpenAPI 3.1

Référence API

L’API partenaire permet à un CRM tiers de lancer des consultations juridiques de 20 minutes au nom de ses clients. Délégation OAuth2, facturation par portefeuille, suivi par webhooks signés.

URL de base

https://avocat20min.be

Version

v1 · OpenAPI 3.1

Format

application/json

Authentification

OAuth2 · Bearer

Modèle de délégation

Votre CRM est une application OAuth2. Chaque client final relie son compte Avocat20min via le flux Authorization Code + PKCE ; vous agissez ensuite en son nom avec son access token. Les 20 € de mise en relation sont débités de son portefeuille.

Authentification

Tous les appels /api/v1 exigent un access token Bearer obtenu via OAuth2. L’access token est un JWT de courte durée (15 min) ; le refresh token est rotatif.

curl -X POST https://avocat20min.be/api/oauth/token \
  -d grant_type=authorization_code \
  -d code=$CODE \
  -d redirect_uri=$REDIRECT_URI \
  -d client_id=$AV20_CLIENT_ID \
  -d client_secret=$AV20_CLIENT_SECRET \
  -d code_verifier=$CODE_VERIFIER

Sécurité des jetons

Le refresh token est à usage unique : chaque rafraîchissement en renvoie un nouveau et invalide l’ancien. Stockez toujours le dernier reçu. Rejouer un refresh token déjà utilisé révoque toute la session du client.

Erreurs

Les erreurs renvoient un statut HTTP approprié et un corps JSON { code, message }. Les erreurs de validation incluent un champ issues détaillé.

Exemple d’erreur
{
  "code": "INSUFFICIENT_FUNDS",
  "message": "Solde du portefeuille insuffisant pour lancer la consultation."
}
  • 400Requête malformée (en-tête ou paramètre manquant).
  • 401Access token absent, invalide ou expiré.
  • 403Scope insuffisant pour cette opération.
  • 404Ressource introuvable ou hors de votre périmètre.
  • 409Conflit métier (solde insuffisant, clé d’idempotence, etc.).
  • 422Corps de requête invalide — voir issues.
  • 429Quota dépassé — voir l’en-tête Retry-After.

Idempotence

Les requêtes POST de création acceptent — et POST /v1/consultations exige — un en-tête Idempotency-Key. Un rejeu portant la même clé renvoie la réponse mémorisée, sans réexécuter l’opération.

En-tête
Idempotency-Key: 1f8e2c4a-9b07-4d3e-a210-7c6b5a4e3d2f
Utilisez un UUID unique par opération métier. Une même clé réutilisée avec un corps différent renvoie 409 IDEMPOTENCY_CONFLICT. La rétention est de 24 h.

Limites de débit

Les appels sont limités par application et par client. Au dépassement, l’API renvoie 429 avec un en-tête Retry-After et des en-têtes X-RateLimit-*.

En-têtes (429)
X-RateLimit-Remaining: 0
Retry-After: 30

Démarrer l’autorisation

GET/api/oauth/authorize

Point d’entrée navigateur. Redirigez le client ici pour qu’il se connecte à Avocat20min et consente aux scopes. Au retour, Avocat20min redirige vers redirect_uri avec un code à usage unique.

Paramètres de requête

  • response_typestringRequis
    code

    Doit valoir « code ».

  • client_idstringRequis

    Identifiant public de votre application.

  • redirect_uristringRequis

    URI de retour — doit correspondre exactement à une URI enregistrée.

  • scopestringRequis

    Scopes demandés, séparés par des espaces.

  • statestringRequis

    Valeur anti-CSRF renvoyée telle quelle.

  • code_challengestringRequis

    Challenge PKCE (base64url du SHA-256 du verifier).

  • code_challenge_methodstringRequis
    S256

    Doit valoir « S256 ».

Réponses

  • 302Redirection vers redirect_uri avec code + state (ou error).
text
GET https://avocat20min.be/api/oauth/authorize
  ?response_type=code
  &client_id=$AV20_CLIENT_ID
  &redirect_uri=https://moncrm.be/callbacks/avocat20min
  &scope=profile:read wallet:read consultations:write offline_access
  &state=$RANDOM
  &code_challenge=$CHALLENGE
  &code_challenge_method=S256

Obtenir / rafraîchir des jetons

POST/api/oauth/token

Échange un code d’autorisation contre des jetons (grant_type=authorization_code) ou renouvelle l’access token (grant_type=refresh_token). Corps en application/x-www-form-urlencoded.

Corps de la requête

  • grant_typestringRequis
    authorization_coderefresh_token

    Type de flux.

  • codestringOptionnel

    Code d’autorisation (grant authorization_code).

  • redirect_uristringOptionnel

    redirect_uri utilisée à l’étape authorize.

  • code_verifierstringOptionnel

    Verifier PKCE (grant authorization_code).

  • refresh_tokenstringOptionnel

    Refresh token (grant refresh_token).

  • client_idstringRequis

    Identifiant public de l’application.

  • client_secretstringRequis

    Secret de l’application (applications confidentielles).

Réponses

  • 200Jetons émis.
  • 400invalid_grant / invalid_request.
  • 401invalid_client — identifiants d’application invalides.
curl -X POST https://avocat20min.be/api/oauth/token \
  -d grant_type=authorization_code \
  -d code=$CODE \
  -d redirect_uri=$REDIRECT_URI \
  -d client_id=$AV20_CLIENT_ID \
  -d client_secret=$AV20_CLIENT_SECRET \
  -d code_verifier=$CODE_VERIFIER

Révoquer un jeton

POST/api/oauth/revoke

Révoque un refresh token (RFC 7009). Renvoie 200 que le jeton ait existé ou non.

Corps de la requête

  • tokenstringRequis

    Le refresh token à révoquer.

  • client_idstringRequis

    Identifiant public de l’application.

  • client_secretstringOptionnel

    Secret de l’application.

Réponses

  • 200Révocation effectuée.

Profil du client

GET/api/v1/meScope profile:read

Renvoie le profil du client connecté. phoneVerified doit être vrai pour pouvoir lancer une consultation.

Réponses

  • 200Profil renvoyé.
  • 401Jeton absent ou invalide.
200 OK
{
  "id": "usr_9a2b...",
  "name": "Marie Lambert",
  "email": "marie@example.be",
  "phoneVerified": true
}

Solde du portefeuille

GET/api/v1/walletScope wallet:read

Renvoie le solde du portefeuille. availableCents = balanceCents − réservations actives ; une consultation coûte 2000 cents.

Réponses

  • 200Solde renvoyé.
200 OK
{
  "balanceCents": 6000,
  "availableCents": 4000,
  "heldCents": 2000,
  "currency": "EUR"
}

Historique du portefeuille

GET/api/v1/wallet/transactionsScope wallet:read

Liste paginée des mouvements du portefeuille, du plus récent au plus ancien.

Paramètres de requête

  • cursorstringOptionnel

    Curseur de pagination (nextCursor de la page précédente).

  • limitintegerOptionneldéfaut 20

    Taille de page (1–50).

Réponses

  • 200Liste paginée { items, nextCursor }.

Créer une recharge

POST/api/v1/wallet/topup-sessionScope wallet:topup

Crée une session Stripe Checkout hébergée. Ouvrez checkoutUrl dans un navigateur pour permettre au client de recharger son portefeuille.

Corps de la requête

  • amountCentsintegerRequis

    Montant à créditer, en cents (≥ 2000).

  • returnUrlstringRequis

    URL de retour après le paiement.

Réponses

  • 200Session créée { checkoutUrl, expiresAt }.
  • 422Montant ou URL invalides.
200 OK
{
  "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_...",
  "expiresAt": "2026-05-20T15:30:00.000Z"
}

Domaines de droit

GET/api/v1/catalog/competences

Liste des domaines de droit acceptés par le champ competence d’une consultation.

Réponses

  • 200Liste { competences: [{ code, label }] }.

Langues

GET/api/v1/catalog/languages

Liste des langues de consultation acceptées par le champ language.

Réponses

  • 200Liste { languages: [{ code, label }] }.

Disponibilité des avocats

GET/api/v1/coverageScope consultations:read

Indique si des avocats sont joignables maintenant pour un couple compétence / langue. À appeler avant de lancer une consultation pour fixer les attentes du client.

Paramètres de requête

  • competencestringRequis

    Domaine de droit.

  • languagestringRequis

    Langue souhaitée.

Réponses

  • 200{ available, lawyersOnline, estimatedWaitSeconds }.
200 OK
{
  "available": true,
  "lawyersOnline": 3,
  "estimatedWaitSeconds": null
}

Lancer une consultation

POST/api/v1/consultationsScope consultations:write

Crée une consultation, réserve 20 € sur le portefeuille du client et déclenche la recherche d’avocat. Le client est ensuite rappelé sur son téléphone vérifié. En-tête Idempotency-Key obligatoire.

Corps de la requête

  • competencestringRequis

    Domaine de droit (voir le catalogue).

  • languagestringRequisdéfaut FR

    Langue de la consultation.

  • subjectstringRequis

    Objet court de la consultation (3 à 200 caractères).

  • descriptionstringOptionnel

    Description détaillée (≤ 2000 caractères).

  • recordingConsentbooleanOptionneldéfaut false

    Consentement du client à l’enregistrement de l’appel.

  • withdrawalWaiverAcknowledgedbooleanRequis

    Le client reconnaît le démarrage immédiat de la prestation et renonce à son droit de rétractation. À recueillir auprès du client.

  • externalRefstringOptionnel

    Référence interne de votre CRM, renvoyée dans les webhooks (≤ 100 caractères).

Réponses

  • 201Consultation créée, recherche d’avocat en cours.
  • 403Scope consultations:write manquant.
  • 409INSUFFICIENT_FUNDS, PHONE_REQUIRED ou IDEMPOTENCY_CONFLICT.
  • 422Corps invalide ou renonciation non recueillie.
curl -X POST https://avocat20min.be/api/v1/consultations \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: 1f8e...a90" \
  -H "Content-Type: application/json" \
  -d '{
    "competence": "LABOR_LAW",
    "language": "FR",
    "subject": "Rupture de contrat — calcul du préavis",
    "recordingConsent": false,
    "withdrawalWaiverAcknowledged": true,
    "externalRef": "TICKET-4821"
  }'

Lister les consultations

GET/api/v1/consultationsScope consultations:read

Liste paginée des consultations lancées par votre application pour le client connecté.

Paramètres de requête

  • statusstringOptionnel
    MATCHINGASSIGNEDCOMPLETEDCANCELLED

    Filtre par statut.

  • cursorstringOptionnel

    Curseur de pagination.

  • limitintegerOptionneldéfaut 20

    Taille de page (1–50).

Réponses

  • 200Liste paginée { items, nextCursor }.

Détail d’une consultation

GET/api/v1/consultations/{id}Scope consultations:read

Renvoie une consultation lancée par votre application.

Paramètres de chemin

  • idstringRequis

    Identifiant de la consultation.

Réponses

  • 200Consultation renvoyée.
  • 404Introuvable ou hors de votre périmètre.

Annuler une consultation

POST/api/v1/consultations/{id}/cancelScope consultations:write

Annule une consultation tant que l’appel n’a pas démarré et libère les fonds réservés sur le portefeuille.

Paramètres de chemin

  • idstringRequis

    Identifiant de la consultation.

Réponses

  • 200Consultation annulée.
  • 409NOT_CANCELLABLE — l’appel a déjà démarré.

Aperçu de la note

GET/api/v1/consultations/{id}/noteScope notes:read

Renvoie l’aperçu de la note rédigée par l’avocat. Le contenu intégral, couvert par le secret professionnel, n’est jamais exposé via l’API.

Paramètres de chemin

  • idstringRequis

    Identifiant de la consultation.

Réponses

  • 200{ consultationId, preview, submittedAt }.
  • 404Consultation introuvable.

Webhooks — événements

Chaque transition de statut déclenche un POST JSON vers l’URL webhook de votre application. Répondez 2xx sous 5 secondes ; en cas d’échec, la livraison est réessayée avec un backoff exponentiel.

  • consultation.createdconsultation créée (DRAFT)
  • consultation.payment_authorizedportefeuille débité, recherche d’avocat lancée
  • consultation.matchingrecherche d’un avocat en cours
  • consultation.assignedun avocat a pris la consultation
  • consultation.call_startedl’appel téléphonique a démarré
  • consultation.call_endedl’appel s’est terminé
  • consultation.completedconsultation terminée, note disponible
  • consultation.note_availablela note de consultation est disponible
  • consultation.pending_callbackaucun avocat sous 30 min — rappel garanti sous 24 h
  • consultation.cancelledconsultation annulée
  • consultation.refundedportefeuille recrédité
  • consultation.expiredconsultation expirée
Exemple de payload
{
  "id": "evt_3kf9...b21",
  "type": "consultation.completed",
  "createdAt": "2026-05-20T14:55:12.000Z",
  "data": {
    "id": "cls7x2a9b0001",
    "status": "COMPLETED",
    "competence": "LABOR_LAW",
    "externalRef": "TICKET-4821",
    "durationSec": 1180
  }
}

Webhooks — signature & vérification

Chaque livraison porte les en-têtes X-Avocat20min-Event, X-Avocat20min-Delivery (identifiant unique, pour l’idempotence) et X-Avocat20min-Signature. Vérifiez la signature HMAC-SHA256 et l’horodatage avant de traiter l’événement.

javascript
import crypto from 'node:crypto'

export function verifyAvocat20min(req, secret) {
  // En-tête : t=<timestamp>,v1=<hmac-sha256-hex>
  const header = req.headers['x-avocat20min-signature'] ?? ''
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${req.rawBody}`)
    .digest('hex')

  const ok = crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1 ?? ''),
  )
  // Anti-rejeu : refuser au-delà de 5 minutes
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
  if (!ok || !fresh) throw new Error('Signature invalide')
}

Idempotence côté récepteur

Mémorisez l’en-tête X-Avocat20min-Delivery : en cas de réessai, vous recevrez le même identifiant — ignorez les doublons.