Matchai Docs

Se connecter avec MatchaiBientôt disponible

Laissez un professionnel connecter son compte Matchai à votre application avec OAuth 2.1 et PKCE. Points de terminaison, autorisation, jetons, code TypeScript.

Bientôt disponible. Cette fonctionnalité est documentée avant son lancement pour que vous puissiez planifier votre intégration; elle n’est pas encore offerte.

Se connecter avec Matchai est la façon dont une application obtient l'accès au compte d'un professionnel, avec sa permission. C'est OAuth 2.1 avec le flux de code d'autorisation et PKCE. Le professionnel voit ce que votre application peut faire et qui paie, appuie sur Autoriser, et votre application apparaît sur sa page Connecteurs, où il peut la déconnecter en tout temps.

Utilisez-le pour tout ce que d'autres professionnels utiliseront : un créateur de sites Web, un CRM, l'application d'un partenaire, un assistant IA. Pour votre propre compte, une clé d'API est plus simple.

Chaque connexion est un compte Matchai connecté à une application, avec ses propres jetons. Un créateur de sites Web qui a trois courtiers comme clients détient trois connexions.

Avant de commencer

  • Créez une application Matchai pour obtenir un identifiant client et un secret client, et pour enregistrer vos URL de redirection.
  • Décidez de quelles portées votre application a besoin. Demandez la liste la plus courte qui fonctionne.
  • Ayez un serveur capable de garder le secret client confidentiel. L'échange de jetons se fait là, jamais dans le navigateur.

Points de terminaison

ObjetURL
Autorisation (la page où l'on appuie sur Autoriser)https://matchai.co/oauth/authorize
Jeton et actualisationhttps://api.matchai.co/oauth/token
Enregistrement dynamique des clientshttps://api.matchai.co/oauth/register
Métadonnées du serveur d'autorisationhttps://api.matchai.co/.well-known/oauth-authorization-server
Métadonnées de la ressource protégéehttps://api.matchai.co/.well-known/oauth-protected-resource pour l'API REST, et https://matchai.co/.well-known/oauth-protected-resource pour le serveur MCP

Les trois derniers servent aux clients MCP et aux bibliothèques qui découvrent les points de terminaison par elles-mêmes. Si vous écrivez le flux à la main, vous n'avez besoin que des deux premiers.

PKCE avec S256 est obligatoire. Les codes d'autorisation sont à usage unique et de courte durée, et les jetons d'accès sont de courte durée : utilisez le jeton d'actualisation pour en obtenir de nouveaux.

Le flux

Préparez PKCE et une valeur d'état. Générez un code_verifier aléatoire et conservez-le. Envoyez son hachage SHA-256, encodé en base64url, comme code_challenge. Générez aussi un state aléatoire. Conservez les deux à un endroit que seul ce navigateur peut renvoyer, comme un témoin (cookie) HttpOnly.

Redirigez le professionnel vers Matchai. Envoyez son navigateur vers l'URL d'autorisation avec ces paramètres :

Champ

Type

Authorization URL
https://matchai.co/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fapp.your-website-company.example%2Fmatchai%2Fcallback
  &scope=profile%3Aread%20listings%3Aread%20contacts%3Awrite
  &state=3b1f0c9e7a2d4e68
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

Le professionnel répond. S'il n'est pas connecté à Matchai, il se connecte d'abord. Il voit ensuite la page d'autorisation et appuie sur Autoriser, ou refuse.

Matchai le renvoie vers vous. Il revient à votre redirect_uri avec un code et votre state :

Callback
https://app.your-website-company.example/matchai/callback?code=…&state=3b1f0c9e7a2d4e68

S'il refuse, il y a un error=access_denied et votre state au lieu d'un code. Avant toute autre chose, vérifiez que state correspond à la valeur que vous avez conservée.

Échangez le code contre des jetons. Depuis votre serveur, faites un POST vers le point de terminaison de jeton avec un corps encodé comme un formulaire :

Terminal
curl https://api.matchai.co/oauth/token \
  -d grant_type=authorization_code \
  -d code="$MATCHAI_AUTH_CODE" \
  -d redirect_uri="https://app.your-website-company.example/matchai/callback" \
  -d client_id="$MATCHAI_CLIENT_ID" \
  -d client_secret="$MATCHAI_CLIENT_SECRET" \
  -d code_verifier="$MATCHAI_CODE_VERIFIER"
Response
{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "…",
  "scope": "profile:read listings:read contacts:write"
}

expires_in est la durée de vie du jeton d'accès, en secondes. Lisez-la dans chaque réponse; ne codez pas de durée de vie en dur. Conservez les deux jetons pour ce seul professionnel, chiffrés, et ne les journalisez jamais.

Appelez l'API. Envoyez le jeton d'accès comme jeton de porteur. Commencez par GET /v1/me : il vous indique quel compte vient de se connecter. GET /v1/me/connection vous indique si les actions finales demandent d'abord.

Terminal
curl https://api.matchai.co/v1/me \
  -H "Authorization: Bearer $MATCHAI_ACCESS_TOKEN"

Actualisez le jeton quand il expire. Un jeton d'accès expiré reçoit la réponse 401 avec le code token_expired. Échangez le jeton d'actualisation contre un nouveau jeton d'accès, puis répétez la requête une fois :

Terminal
curl https://api.matchai.co/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token="$MATCHAI_REFRESH_TOKEN" \
  -d client_id="$MATCHAI_CLIENT_ID" \
  -d client_secret="$MATCHAI_CLIENT_SECRET"

Conservez toujours le jeton d'actualisation le plus récent qu'une réponse vous donne. Si l'actualisation échoue, la connexion a pris fin : demandez au professionnel de reconnecter son compte.

La page d'autorisation

La page d'autorisation est le moment où le professionnel décide. Elle est conçue pour qu'il n'ait jamais à deviner :

Ce qu'elle montreD'où ça vient
Le nom, le logo et le site Web de votre application, avec une marque quand Matchai a vérifié l'éditeurVotre application Matchai
Ce que votre application peut faire, chaque portée en mots simples, comme « Voir vos inscriptions » ou « Ajouter et modifier vos contacts »Le scope que vous avez demandé
Si les actions finales demandent d'abordVotre connexion commence sur Me demander. L'envoi, la publication, le paiement et la suppression attendent son approbation.
Qui paie« Inclus avec » votre produit quand votre application est payée par le partenaire, une ligne indiquant que la connexion s'ajoute à sa facture Matchai quand elle est payée par l'utilisateur, et rien du tout en mode test

Appuyer sur Autoriser crée la connexion. À partir de ce moment, elle apparaît sur sa page Connecteurs avec les mêmes détails, et il peut changer la façon dont les actions finales sont gérées, ou appuyer sur Déconnecter.

Pourquoi les portées se lisent comme des phrases

Le professionnel décide s'il laisse le logiciel d'un inconnu agir en son nom. Une liste comme contacts:write messages:send l'oblige à traduire; une phrase lui permet de décider. Demandez les portées que vous pouvez expliquer en une ligne chacune.

Exemple : un créateur de sites Web connecte une courtière

Disons que vous exploitez une entreprise qui crée des sites Web pour des courtiers. Votre produit s'appelle « Votre entreprise de sites Web » dans Matchai, et Camille Tremblay, une courtière, est l'une de vos clientes.

Camille ouvre les réglages de son site Web dans votre produit et appuie sur Connecter Matchai. Votre serveur exécute les étapes 1 et 2 ci-dessus et la redirige.

Elle est déjà connectée à Matchai. La page d'autorisation nomme Votre entreprise de sites Web, énumère ce que l'application peut faire (voir ses inscriptions, ajouter des contacts, être avisée des nouveaux prospects) et indique qui paie. Elle appuie sur Autoriser.

Elle revient dans votre produit. Votre serveur échange le code, appelle GET /v1/me et conserve ses jetons dans son dossier client. Cela lui a pris quelques clics, et aucun mot de passe n'a été partagé.

Votre produit lit ses inscriptions avec GET /v1/listings et les affiche sur son nouveau site Web. Il s'abonne à listing.published, listing.updated et lead.created avec POST /v1/webhook-subscriptions, pour que le site reste à jour sans interrogation périodique.

Un visiteur remplit le formulaire de contact de son site. Votre serveur l'envoie avec POST /v1/enquiries. Un prospect et sa conversation apparaissent dans les contacts Matchai de Camille, et lead.created arrive à votre point de terminaison.

Des mois plus tard, Camille déconnecte votre produit à partir de sa page Connecteurs. Votre appel suivant reçoit la réponse 401, ses abonnements aux webhooks sont désactivés, et votre produit affiche « Reconnecter Matchai ». Rien n'a été supprimé dans son compte Matchai.

Votre client suivant, un deuxième courtier, suit le même flux et obtient un ensemble de jetons distinct. Aucune des deux connexions ne peut voir les données de l'autre courtier.

Un exemple complet

Ce serveur effectue les étapes 1 à 5 pour un client connecté. Il utilise Express. currentCustomerId et saveConnection représentent votre propre système de connexion et votre propre stockage.

server.ts
import { createHash, randomBytes } from 'node:crypto';
import cookieParser from 'cookie-parser';
import express from 'express';

// Your own code: who is signed in to your product, and where tokens are kept.
declare function currentCustomerId(request: express.Request): string;
declare function saveConnection(connection: {
  customerId: string;
  matchaiUserId: string;
  accessToken: string;
  refreshToken: string;
  expiresAt: Date;
}): Promise<void>;

const clientId = process.env.MATCHAI_CLIENT_ID!;
const clientSecret = process.env.MATCHAI_CLIENT_SECRET!;
const redirectUri = 'https://app.your-website-company.example/matchai/callback';
const scopes = ['profile:read', 'listings:read', 'contacts:write', 'webhooks:manage'];

const base64url = (bytes: Buffer) => bytes.toString('base64url');

const app = express();
app.use(cookieParser());

// Steps 1 and 2: send the professional to Matchai.
app.get('/matchai/connect', (request, response) => {
  currentCustomerId(request); // Only a signed-in customer can start a connection.

  const state = base64url(randomBytes(24));
  const verifier = base64url(randomBytes(48));
  const challenge = base64url(createHash('sha256').update(verifier).digest());

  // Keep both values in a cookie that only this browser sends back.
  response.cookie('matchai_oauth', JSON.stringify({ state, verifier }), {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 10 * 60 * 1000,
  });

  const authorize = new URL('https://matchai.co/oauth/authorize');
  authorize.search = new URLSearchParams({
    response_type: 'code',
    client_id: clientId,
    redirect_uri: redirectUri,
    scope: scopes.join(' '),
    state,
    code_challenge: challenge,
    code_challenge_method: 'S256',
  }).toString();

  response.redirect(authorize.toString());
});

// Steps 4 and 5: Matchai sends the professional back here.
app.get('/matchai/callback', async (request, response) => {
  const customerId = currentCustomerId(request);
  const saved = JSON.parse(request.cookies.matchai_oauth ?? 'null') as
    | { state: string; verifier: string }
    | null;
  response.clearCookie('matchai_oauth');

  if (request.query.error === 'access_denied') {
    return response.redirect('/settings/website?matchai=declined');
  }

  const { code, state } = request.query;
  if (!saved || typeof code !== 'string' || state !== saved.state) {
    return response.status(400).send('This connection request is not valid. Please start again.');
  }

  const tokenResponse = await fetch('https://api.matchai.co/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: redirectUri,
      client_id: clientId,
      client_secret: clientSecret,
      code_verifier: saved.verifier,
    }),
  });
  if (!tokenResponse.ok) {
    return response.status(502).send('Matchai did not accept the connection. Please try again.');
  }
  const tokens = await tokenResponse.json();

  // Learn which Matchai account just connected.
  const me = await fetch('https://api.matchai.co/v1/me', {
    headers: { Authorization: `Bearer ${tokens.access_token}` },
  }).then((result) => result.json());

  await saveConnection({
    customerId,
    matchaiUserId: me.user_id,
    accessToken: tokens.access_token,
    refreshToken: tokens.refresh_token,
    expiresAt: new Date(Date.now() + tokens.expires_in * 1000),
  });

  response.redirect('/settings/website?matchai=connected');
});

app.listen(3000);

Ensuite, chaque appel à l'API passe par une seule fonction qui actualise un jeton expiré et détecte une connexion qui a pris fin :

matchai-client.ts
type Tokens = { accessToken: string; refreshToken: string; expiresAt: Date };

// Your own code: tokens are stored per customer.
declare function loadTokens(customerId: string): Promise<Tokens>;
declare function saveTokens(customerId: string, tokens: Tokens): Promise<void>;
declare function markDisconnected(customerId: string): Promise<void>;

async function refresh(refreshToken: string): Promise<Tokens | null> {
  const response = await fetch('https://api.matchai.co/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: process.env.MATCHAI_CLIENT_ID!,
      client_secret: process.env.MATCHAI_CLIENT_SECRET!,
    }),
  });
  if (!response.ok) return null;

  const tokens = await response.json();
  return {
    accessToken: tokens.access_token,
    // Keep the newest refresh token you were given.
    refreshToken: tokens.refresh_token ?? refreshToken,
    expiresAt: new Date(Date.now() + tokens.expires_in * 1000),
  };
}

export async function matchaiFetch(
  customerId: string,
  path: string,
  init: RequestInit & { headers?: Record<string, string> } = {},
): Promise<Response> {
  let tokens = await loadTokens(customerId);

  const call = () =>
    fetch(`https://api.matchai.co/v1${path}`, {
      ...init,
      headers: { ...init.headers, Authorization: `Bearer ${tokens.accessToken}` },
    });

  const response = await call();
  if (response.status !== 401) return response;

  // The access token expired, or the professional disconnected. Refresh once.
  const refreshed = await refresh(tokens.refreshToken);
  if (!refreshed) {
    await markDisconnected(customerId); // Show "Reconnect Matchai" in your product.
    return response;
  }
  await saveTokens(customerId, refreshed);
  tokens = refreshed;
  return call();
}

Actualisez les jetons d'un seul client à la fois. Si deux requêtes actualisent en même temps, seule la première peut utiliser le jeton d'actualisation qu'elle détient.

Clients MCP et enregistrement dynamique des clients

Les assistants IA qui parlent MCP, comme Claude et ChatGPT, ne vous demandent pas d'enregistrer une application. Ils trouvent la connexion par eux-mêmes et s'enregistrent avec l'enregistrement dynamique des clients :

  1. Le client appelle un outil propre à l'utilisateur sur https://matchai.co/mcp sans jeton. Le serveur répond 401 avec un en-tête WWW-Authenticate: Bearer resource_metadata=… qui pointe vers les métadonnées de la ressource protégée.
  2. Le client lit les métadonnées, trouve le serveur d'autorisation et lit https://api.matchai.co/.well-known/oauth-authorization-server.
  3. Il s'enregistre à https://api.matchai.co/oauth/register et obtient un identifiant client.
  4. Il exécute le même flux que ci-dessus. Le professionnel voit la même page d'autorisation, qui nomme l'assistant.

Pour ajouter Matchai à un assistant, il suffit d'entrer l'adresse du serveur. Le guide du serveur MCP couvre les outils publics, qui continuent de fonctionner sans connexion.

Mettre fin à une connexion

  • Le professionnel se déconnecte. Il appuie sur Déconnecter sur la page Connecteurs. Chaque jeton de la connexion cesse de fonctionner, ses abonnements aux webhooks sont désactivés (disabled_reason: connection_ended), et rien n'est supprimé dans son compte.
  • Votre application se déconnecte. Appelez DELETE /v1/me/connection (disconnect) pour obtenir le même effet, par exemple quand un client quitte votre produit.

Dans les deux cas, supprimez les jetons conservés et traitez les réponses 401 qui suivent comme un signal de reconnexion.

Liste de vérification de sécurité

  • Vérifiez state à chaque rappel, et rejetez un rappel que vous n'avez pas lancé.
  • Utilisez PKCE avec S256 dans chaque flux. Matchai refuse un flux qui ne l'utilise pas.
  • Gardez le secret client et chaque jeton sur votre serveur, chiffrés au repos et hors de vos journaux.
  • Enregistrez des URL de redirection exactes, et n'en construisez jamais une à partir d'une saisie de l'utilisateur.
  • Demandez le moins de portées possible. En demander d'autres plus tard veut dire redemander l'accord du professionnel.
  • Gardez un ensemble de jetons par professionnel, et n'utilisez jamais le jeton d'un client pour la requête d'un autre.

Sur cette page