Documentation API

KonnectID est un provider OAuth2 qui permet aux utilisateurs de contrôler finement quelles données ils partagent avec votre app. L'API est exposée via tRPC sous /api/trpc/*.

Base URL

https://konnectid.me

Enveloppe tRPC — toutes les réponses sont encapsulées par tRPC. Les corps de réponse montrés dans cette doc correspondent à result.data dans la réponse JSON réelle : { "result": { "data": { … } } }

Authentification

Flow OAuth2 Authorization Code + PKCE recommandé:

  1. Redirigez l'utilisateur vers /oauth/authorize
  2. L'utilisateur accepte (ou révoque) les scopes demandés
  3. Récupérez le code sur votre redirect_uri
  4. Échangez le code contre des tokens via /api/trpc/oauth2.token
  5. Appelez /api/trpc/oauth2.userInfo avec l'access token

1. Authorize

Redirigez le navigateur de l'utilisateur vers cette URL. L'utilisateur doit être connecté à KonnectID.

GET /oauth/authorize

Query params

ParamTypeRequisDescription
client_idstringouiClient ID de votre app
redirect_uristringouiDoit matcher une URI enregistrée
scopestringouiScopes séparés par espaces
statestringCSRFToken anti-CSRF retourné tel quel
code_challengestringPKCEBase64url SHA-256, max 128 chars
code_challenge_method"S256" | "plain"PKCES256 recommandé

Réponse

Après approbation par l'utilisateur, redirection vers votre redirect_uri :

https://yourapp.com/callback?code=abc...&state=xyz

Le code expire après 10 minutes.

2. Token Exchange

Échange le code contre access_token + refresh_token.

POST /api/trpc/oauth2.token

Body

{
  "grantType": "authorization_code",
  "code": "auth_code_returned_by_authorize",
  "clientId": "app_xxxxxxxxxxxxxxxxxxxxxx",
  "clientSecret": "secret_xxxxxxxxxxxxxxxxxxxxxx",
  "redirectUri": "https://yourapp.com/callback",
  "codeVerifier": "PKCE_verifier_max_128_chars"
}

Réponse

{
  "access_token": "at_...",
  "refresh_token": "rt_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "email profile"
}

Le client secret est hashé en DB. KonnectID le retourne une seule fois à la création/régénération via le dashboard dev.

3. UserInfo

Retourne les fields consentis par l'utilisateur.

GET /api/trpc/oauth2.userInfo?input={"accessToken":"..."}

Réponse

{
  "sub": 42,
  "email": "user@example.com",
  "first_name": "Marie",
  "ville": "Lyon",
  "google_email": "marie@gmail.com",
  "discord_connections": ["github", "spotify"]
}

sub (numeric user id KonnectID) toujours présent. Le reste dépend strictement des consents accordés.

4. Refresh Token

POST /api/trpc/oauth2.token
{
  "grantType": "refresh_token",
  "refreshToken": "rt_...",
  "clientId": "app_...",
  "clientSecret": "secret_..."
}

Nouvelle paire renvoyée. L'ancien refresh est révoqué. Refresh expire après 30 jours.

Accès de test (sandbox)

Pour tester votre intégration sans repasser par la connexion passkey d'un humain à chaque essai (utile en développement, en CI, ou avec un outil de dev piloté par IA), générez un accès de test depuis votre dashboard : /dashboard/consents → onglet « Mes Applications » → bouton « Accès de test ».

Cela vous donne un dev_token valable de 1 à 30 jours (7 par défaut), qui ne fonctionne que pour le client_id pour lequel il a été généré. Ce token n'est lié à aucun vrai utilisateur — c'est un fixture technique, pas une identité simulée avec un profil pré-rempli. Par défaut il ne renvoie donc aucune donnée : c'est vous qui définissez, dans l'URL elle-même, les valeurs que userInfo doit retourner.

GET /api/oauth/dev-authorize

Query params

ParamTypeRequisDescription
client_idstringouiClient ID de votre app
redirect_uristringouiDoit matcher une URI enregistrée
dev_tokenstringouiGénéré depuis le dashboard
scopestringnonScopes séparés par espaces (défaut vide)
statestringCSRFToken anti-CSRF retourné tel quel
code_challenge / code_challenge_methodstringPKCEComme pour /oauth/authorize
tout autre nomstringnonValeur du champ correspondant (voir Scopes disponibles) pour cet appel

Définir les données retournées

N'importe quel paramètre dont le nom correspond à un scope (email, first_name, phone, un champ custom...) fixe la valeur que userInfo renverra pour ce champ, que vous l'ayez demandé explicitement dans scope ou non — le passer suffit à le rendre visible. Rejouez l'appel avec d'autres valeurs pour tester différents profils, sans régénérer de token.

GET /api/oauth/dev-authorize
    ?client_id=app_xxx
    &redirect_uri=https://yourapp.com/callback
    &scope=profile email
    &dev_token=dev_xxx
    &email=jean.dupont@test.com
    &first_name=Jean
    &last_name=Dupont

Réponse

Redirection HTTP 302 immédiate vers votre redirect_uri avec un code valide — exactement comme après une vraie connexion. Aucun changement côté votre serveur : échangez ce code normalement via /api/trpc/oauth2.token, puis appelez oauth2.userInfo.

https://yourapp.com/callback?code=abc...&state=xyz

Réservé au développement : révoquez l'accès depuis le dashboard une fois vos tests terminés. Le fixture technique sous-jacent n'apparaît jamais dans vos utilisateurs connectés réels ni dans vos exports.

Scopes disponibles

Les scopes correspondent aux data_fields configurés par l'admin KonnectID. Chaque field a un basePrice qui détermine le revenu par vente de donnée.

Champs standard

ScopeLabelPrix / vente10% dev Catégorie
Aucun scope publié.

① Lorsqu'un utilisateur invité via votre app revend ses données, vous percevez 10 % de chaque transaction en tant que créateur de l'app qui lui a permis de rejoindre KonnectID.

Champs provider (OAuth chaîné)

Préfixés par le nom du provider, ex: google_email, discord_connections. Disponibles uniquement si l'utilisateur a lié le compte correspondant via /dashboard/trust.

Champs requis (login bloqué)

Préfixez un scope par required: pour le rendre obligatoire. L'utilisateur ne pourra pas désélectionner ce champ et devra le renseigner (saisie possible directement sur l'écran de consentement) avant d'autoriser. Sans cela, l'autorisation est refusée.

scope=required:email required:phone profile

Ici email et phone sont obligatoires, tandis que profile reste facultatif. Le préfixe est retiré côté serveur (les consentements sont tracés par field).

Coffre de documents

Déposez des documents officiels (factures, justificatifs, ordonnances...) pour un utilisateur, et récupérez ceux que vous avez émis ou que l'utilisateur a explicitement acceptés. Trois règles : votre app voit toujours ce qu'elle a émis ; tout accès à un document émis par une autre app passe par une demande que l'utilisateur accepte, met en attente ou refuse ; rien n'est jamais partagé automatiquement.

1. Déclarer votre scope

Avant de pouvoir déposer ou demander un type de document, enregistrez le scope depuis votre dashboard (documentScopes.request) :

doc_upload_tag_facture       // émettre des documents "Facture"
doc_download_tag_facture     // demander l'accès aux "Facture" émises par d'autres apps
doc_download_app_42          // demander l'accès aux documents émis par l'app #42

Les catégories non sensibles s'approuvent automatiquement. Une catégorie marquée sensible (ex: données médicales) ou un scope ciblant une app précise passent par une revue admin.

2. Faire consentir l'utilisateur

Ajoutez le scope à votre scope OAuth2 comme n'importe quel autre champ — il apparaît sur l'écran de consentement :

scope=email required:doc_upload_tag_facture

3. Déposer un document

POST /api/documents
Authorization: Bearer <access_token>
Content-Type: multipart/form-data

tagSlug=facture
title=Facture janvier 2026
file=@facture.pdf

4. Télécharger un document

GET /api/documents/:id
Authorization: Bearer <access_token>

Autorisé si votre app est l'émettrice, ou si l'utilisateur a accepté une demande d'accès pour ce document.

5. Demander l'accès à un document d'une autre app

POST /api/documents/access-requests
Authorization: Bearer <access_token>
Content-Type: application/json

{ "tagSlug": "facture", "purpose": "Vérification de domiciliation" }

Ceci crée une demande "en attente" — rien n'est accessible tant que l'utilisateur n'a pas explicitement accepté depuis /dashboard/documents. Un refus impose un délai avant nouvelle tentative.

Webhooks

Si vous configurez une webhookUrl sur votre app, KonnectID notifie chaque première autorisation utilisateur, ainsi que les décisions d'accès aux documents (document.access_accepted, document.access_refused, document.access_revoked, document.deleted).

Payload (POST)

{
  "event": "user.authorized",
  "appId": 12,
  "userId": 42,
  "scope": "email profile",
  "grantedAt": "2026-05-11T08:42:00.000Z"
}

SSRF guard côté serveur: refus de toute URL loopback / IPs privées (RFC 1918 / CGNAT) / link-local / metadata cloud. Use HTTPS public uniquement. redirect: 'manual' côté fetch.

Erreurs

Codes tRPC retournés via TRPCError:

CodeSens
BAD_REQUESTInput invalide / Zod schema rejet
UNAUTHORIZEDToken manquant ou expiré
FORBIDDENApp désactivée / scope révoqué / cross-user
NOT_FOUNDClient ID inconnu / consent introuvable
CONFLICTIdentité non sellable / collision
INTERNAL_SERVER_ERRORErreur serveur inattendue

PKCE

Recommandé pour tous les clients publics (SPA, mobile, etc.).

# 1. Générer le verifier (43-128 chars URL-safe)
verifier = base64url(random(32))

# 2. Calculer le challenge
challenge = base64url(sha256(verifier))

# 3. Envoyer dans authorize:
codeChallenge = challenge
codeChallengeMethod = "S256"

# 4. Au token exchange, envoyer le verifier:
codeVerifier = verifier

Consentements granulaires

Chaque scope est tracé individuellement dans consents: {userId, appId, fieldId | providerScope, granted, grantedAt, revokedAt}.

L'utilisateur peut révoquer un scope à tout moment depuis /dashboard/consents. Effet immédiat: tokens marqués isRevoked: true, userInfo ne retourne plus le field.

Côté dev: votre app peut révoquer un user via apps.disconnectUser({ appId, userId }) (vérifie ownership).