Promoteurs

Intégrez Sellio via HTTP ou connectez votre agent IA

Une API REST simple et un serveur MCP natif, avec la même clé, le même périmètre de tenant et la même limite de débit. Découvrez les champs de n’importe quel objet et commencez à lire et à écrire des enregistrements en quelques minutes.

Démarrage rapide

De zéro à votre premier appel en trois étapes :

  1. Dans le CRM, ouvrez Paramètres → API & développeurs et générez une clé API. Conservez-la en lieu sûr ; elle ne sera plus affichée.
  2. Utilisez la clé dans l’en-tête Authorization: Bearer. Elle identifie le tenant propriétaire, si bien que tout est déjà cadré sur lui.
  3. Effectuez votre premier appel et listez vos objets.
curl -H "Authorization: Bearer sk_live_..." \
  https://www.selliocrm.com/api/v1/objects

Authentification

Chaque requête transporte une clé API dans l’en-tête Authorization: Bearer. Cette clé identifie le tenant propriétaire, si bien que tout est automatiquement cadré sur lui, avec RLS et RBAC. N’exposez jamais la clé dans le navigateur ; utilisez-la uniquement côté serveur. Vous générez et révoquez les clés dans Paramètres → API & développeurs.

Portées de la clé

Lorsque vous générez une clé, vous choisissez sa portée et n’accordez à chaque intégration que l’accès dont elle a besoin. Les limites s’appliquent de la même façon en REST et en MCP, car les deux surfaces utilisent la même clé et le même contrôle. Une clé sans portée définie (par défaut) dispose d’un accès complet en lecture et écriture à tous les objets, comme auparavant.

  • Lecture seule : la clé lit les données mais ne peut ni créer, ni modifier, ni supprimer. Elle bloque POST, PATCH et DELETE en REST ainsi que les outils create_record, update_record et delete_record en MCP, avec un 403 et { "error": "errors.apiKeyReadOnly" }.
  • Limitée à des objets : choisissez les objets auxquels la clé peut accéder. Tout objet hors de la liste renvoie un 403 avec { "error": "errors.apiKeyObjectDenied" }. La liste des objets arrive déjà filtrée selon la portée, et les activités comptent comme l’objet activity.
  • Autoriser la suppression : par défaut, une clé ne supprime JAMAIS (même avec l’écriture activée). Activez cette option lors de la génération de la clé pour permettre DELETE /{object}/{id} et l’outil delete_record. Sans elle, la suppression renvoie un 403 avec { "error": "errors.apiKeyDeleteDenied" }. La suppression est réversible : les enregistrements vont à la corbeille et peuvent être restaurés.

API REST v1

Enregistrements de n’importe quel objet, en envoyant et recevant du JSON. {object} est l’apiName (par exemple contact, lead, opportunity) et {id} est l’uuid de l’enregistrement. Les objets personnalisés utilisent les mêmes endpoints génériques.

GET/objectsLister les objets du tenant.
GET/objects/{apiName}Champs de l’objet (découverte).
GET/{object}Lister et rechercher des enregistrements (filtres riches, tri, pagination).
GET/{object}/{id}Un enregistrement par id.
POST/{object}Créer un enregistrement.
POST/{object}/bulkCréation en masse (jusqu’à 500 ; succès partiel par élément).
PATCH/{object}/{id}Mettre à jour des champs (partiel).
PATCH/{object}/bulkMise à jour en masse ({ updates: [{ id, ...fields }] }).
DELETE/{object}/{id}Supprimer (réversible → corbeille). Nécessite la portée de suppression sur la clé.
GET/data/{object}Enregistrements aplatis en lignes (BI/automatisation).
GET/activitiesLister les activités (utilisez ?recordId pour un seul enregistrement).
POST/activitiesCréer une activité liée à un enregistrement.
GET/line-itemsProduct line-items of an opportunity (?opportunityId), plus the total.
POST/line-itemsAdd a line-item; recomputes totals and the opportunity amount.
PATCH/line-itemsUpdate quantity, price, discount, tax or term of one line.
DELETE/line-itemsRemove a line-item (?id) and recompute the totals.
GET/attachmentsFiles attached to a record (?recordId and ?objectApiName).
POST/attachments/upload-urlUpload step 1: returns the signed upload URL.
POST/attachmentsUpload step 3: registers the uploaded file as an attachment.
GET/attachments/{id}Signed download URL (5 min). There is never a public URL.
DELETE/attachments/{id}Delete the attachment. Requires the delete scope on the key.

Paramètres de liste (GET /{object}) : limit (de 1 à 500, par défaut 50), offset (décalage de pagination, par défaut 0 ; le total figure dans le champ total), search (recherche par nom, insensible aux accents et à la casse), order (champ avec :asc ou :desc) et filter. Filtres riches : répétez filter=field:operator:value pour en combiner plusieurs (par exemple filter=amount:gte:1000&filter=stage:eq:won). Opérateurs : eq, neq, contains, gt, lt, gte, lte, empty, not_empty (empty et not_empty ne prennent pas de valeur). Les filtres se combinent avec un ET logique (il n’y a pas de OU) et jusqu’à 12 par requête. L’ancienne forme filter=field:value (sans opérateur) signifie toujours une égalité exacte.

# Lister (limit, offset, search, filter, order)
curl -H "Authorization: Bearer sk_live_..." \
  "https://www.selliocrm.com/api/v1/opportunity?limit=20&order=updated_at:desc&filter=stage:won"

# Filtres riches : répétez filter=field:operator:value (ET entre eux)
curl -H "Authorization: Bearer sk_live_..." \
  "https://www.selliocrm.com/api/v1/opportunity?filter=amount:gte:1000&filter=stage:eq:won&filter=name:contains:acme"

# Un enregistrement
curl -H "Authorization: Bearer sk_live_..." \
  https://www.selliocrm.com/api/v1/contact/8f3c...

# Créer
curl -X POST -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Ana Souza","email":"ana@acme.com","source":"site"}' \
  https://www.selliocrm.com/api/v1/contact

# Création en masse (jusqu’à 500 ; réponse avec succès partiel par élément)
curl -X POST -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"records":[{"name":"Ana"},{"name":"Bruno"}]}' \
  https://www.selliocrm.com/api/v1/contact/bulk
# → { "results": [ { "index": 0, "ok": true, "id": "..." },
#                  { "index": 1, "ok": false, "error": "..." } ],
#     "created": 1, "failed": 1 }

# Mettre à jour (partiel)
curl -X PATCH -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"source":"referral"}' \
  https://www.selliocrm.com/api/v1/contact/8f3c...

# Supprimer (réversible, va à la corbeille ; nécessite la portée de suppression sur la clé)
curl -X DELETE -H "Authorization: Bearer sk_live_..." \
  https://www.selliocrm.com/api/v1/contact/8f3c...

Découverte des objets et des champs

Avant de créer ou de mettre à jour un enregistrement, découvrez les champs de l’objet : ce qui existe, ce qui est obligatoire et quelles valeurs un select accepte. Sans deviner, et sans avoir à inspecter un enregistrement existant. GET /objects/{apiName} renvoie l’objet et ses champs, avec required, options (pour select), targetObject (pour lookup), unique et readOnly (champs de type formule ou rollup, qui ne sont pas modifiables).

GET https://www.selliocrm.com/api/v1/objects/contact

# → {
#   "apiName": "contact",
#   "label": "Contact",
#   "fields": [
#     { "apiName": "name",  "label": "Name",  "type": "text",  "required": true },
#     { "apiName": "email", "label": "Email", "type": "email", "required": false },
#     { "apiName": "source", "label": "Source", "type": "select", "required": true,
#       "options": [ { "value": "site", "label": "Website" },
#                    { "value": "referral", "label": "Referral" } ] },
#     { "apiName": "company", "label": "Company", "type": "lookup",
#       "required": false, "targetObject": "company" }
#   ]
# }

En MCP, l’outil describe_object renvoie les mêmes champs à l’agent.

Données pour la BI et l’automatisation

GET /data/{object} renvoie les enregistrements aplatis en lignes tabulaires stables (colonnes fixes id, created_at, updated_at, owner_id, plus une par champ), triées par updated_at desc. Il accepte la pagination par page et pageSize (ou limit et offset), les filtres incrémentaux updated_since et created_since (ISO 8601), ainsi que filter=field:value. Conçu pour Power BI, Tableau, Looker et pour le polling depuis Zapier, Make et n8n.

curl -H "Authorization: Bearer sk_live_..." \
  "https://www.selliocrm.com/api/v1/data/opportunity?pageSize=100&updated_since=2026-01-01T00:00:00Z"

# → { "object": "opportunity",
#     "columns": [ { "name": "id", "type": "id" }, ... ],
#     "page": 1, "pageSize": 100, "total": 42,
#     "rows": [ { "id": "...", "created_at": "...", "amount": 1200, ... } ] }

Serveur MCP pour agents IA

Sellio exposes a native MCP (Model Context Protocol) server so your AI agent can read and write the CRM safely. It is the same API key, the same rate limit, the same validations and the same RBAC. The server URL is https://www.selliocrm.com/api/mcp. Available tools:

  • list_objects: Lister les objets du tenant.
  • describe_object: Les champs d’un objet (obligatoires, types, options).
  • list_records: Lister et rechercher des enregistrements.
  • get_record: Un enregistrement par id.
  • create_record: Créer un enregistrement.
  • update_record: Mettre à jour des champs.
  • delete_record: Supprimer (réversible → corbeille). Nécessite la portée de suppression sur la clé.

Les clients qui prennent en charge le MCP distant sur HTTP utilisent l’URL avec l’en-tête Authorization: Bearer. Sur les clients sans HTTP distant natif, utilisez le pont mcp-remote :

{
  "mcpServers": {
    "sellio-crm": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://www.selliocrm.com/api/mcp",
        "--header", "Authorization: Bearer sk_live_..."
      ]
    }
  }
}

OAuth et applications connectées

Pour intégrer AU NOM DE L’UTILISATEUR (sans que le client colle une clé d’administration), utilisez le flux OAuth 2.0 Authorization Code avec PKCE. L’utilisateur voit un écran de consentement présentant les portées demandées, approuve, et votre application reçoit un access token qui ne peut faire que ce qui a été consenti. Les applications sont soumises à une curation : enregistrez et publiez l’application sur la marketplace pour obtenir un client_id et un client_secret. Le flux en quatre étapes :

  1. Redirigez l’utilisateur vers /api/oauth/authorize avec client_id, redirect_uri (correspondance exacte avec la liste blanche de l’application), scope, state et PKCE (code_challenge avec code_challenge_method=S256). PKCE est obligatoire.
  2. L’utilisateur connecté voit l’écran de consentement et approuve. Le CRM le redirige vers votre redirect_uri avec code et le même state.
  3. Sur votre serveur, échangez le code auprès de POST /api/oauth/token (avec code_verifier, client_id, client_secret et redirect_uri) contre un access token (Bearer, environ 1 heure) et un refresh token.
  4. Appelez l’API REST v1 et le serveur MCP avec Authorization: Bearer at_..., cadré sur le consentement. Renouvelez avec grant_type=refresh_token ; le refresh est rotatif (l’ancien expire dès son utilisation) et sa réutilisation révoque toute la famille de tokens.
GET/api/oauth/authorizeConsentement de l’utilisateur (nécessite une session). Émet le code.
POST/api/oauth/tokenÉchanger le code contre des tokens ; et renouveler (refresh_token).
POST/api/oauth/revokeRévoquer un access token ou un refresh token.
# 1) Envoyez l’utilisateur vers le consentement (PKCE S256 + state)
GET https://www.selliocrm.com/api/oauth/authorize
  ?response_type=code
  &client_id=app_...
  &redirect_uri=https://your-app.example.com/callback   # correspondance EXACTE depuis la liste blanche
  &scope=records:read%20records:write:opportunity%20activities:read
  &state=<random>
  &code_challenge=<base64url(sha256(code_verifier))>
  &code_challenge_method=S256

# → l’utilisateur approuve → 302 redirect_uri?code=<authcode>&state=<...>

# 2) Échangez le code contre des tokens (côté serveur ; envoyez le client_secret)
curl -X POST https://www.selliocrm.com/api/oauth/token \
  -d grant_type=authorization_code \
  -d code=<authcode> \
  -d code_verifier=<code_verifier> \
  -d client_id=app_... \
  -d client_secret=secret_... \
  -d redirect_uri=https://your-app.example.com/callback

# → { "access_token": "at_...", "token_type": "Bearer",
#     "expires_in": 3600, "refresh_token": "rt_...",
#     "scope": "records:read records:write:opportunity activities:read" }

# 3) Appelez l’API v1 / MCP avec l’access token (cadré sur le consentement)
curl -H "Authorization: Bearer at_..." https://www.selliocrm.com/api/v1/contact

# 4) Renouvelez avec le refresh token (rotatif : l’ancien rt_ expire dès son utilisation)
curl -X POST https://www.selliocrm.com/api/oauth/token \
  -d grant_type=refresh_token -d refresh_token=rt_... \
  -d client_id=app_... -d client_secret=secret_...

Les portées OAuth correspondent au même contrôle que les clés (lecture, écriture et par objet). Demandez le minimum nécessaire ; l’utilisateur en accorde un sous-ensemble à l’installation et le token n’opère que dans ce cadre.

  • records:read et records:write : lire et écrire les enregistrements de tous les objets (granularité large).
  • records:read:contact et records:write:opportunity : affinage par objet (apiName), lorsque l’application n’a besoin que de certains objets.
  • activities:read et activities:write : lire et écrire les activités.
  • objects:read : découvrir la structure des objets et des champs.

Widgets intégrés

Votre application peut intégrer son propre écran (un widget) au sein du CRM : sur le détail d’un enregistrement ou sur le tableau de bord. Le widget s’exécute dans un iframe en bac à sable, servi depuis votre propre origine (figurant sur la liste blanche de l’application). L’hôte n’envoie au widget que du contexte d’interface via postMessage : recordId, objectApiName, locale et theme. Aucune donnée métier, aucun token ni secret ne transite dans ce message. Pour lire ou écrire des données, votre widget utilise l’API OAuth avec son propre token (les portées accordées par le tenant à l’installation).

  1. Déclarez le widget dans votre application (key, title, emplacement record ou dashboard et l’embedUrl) et ajoutez l’origine de l’embedUrl à la liste blanche des origines de l’application.
  2. Dans votre widget, écoutez les messages de l’hôte et n’acceptez que ceux provenant de l’origine de l’hôte (event.origin). L’hôte envoie sellio:widget:context (version 1) avec { recordId, objectApiName, locale, theme, installId }.
  3. Votre widget ne peut renvoyer que sellio:widget:ready (pour recevoir à nouveau le contexte) et sellio:widget:resize avec { height } (pour ajuster sa hauteur, plafonnée). Tout autre message est ignoré.
  4. Pour les données, appelez l’API OAuth (Bearer) avec le token de l’application. L’hôte ne transmet jamais de token ni de données via postMessage.
// À l’intérieur de VOTRE widget (la page sur https://widgets.yourapp.com exécutée dans l’iframe).
// L’hôte n’envoie QUE du contexte d’interface. Pour les DONNÉES, utilisez l’API OAuth avec VOTRE token.
const HOST = 'https://www.selliocrm.com'; // Validez TOUJOURS l’origine de l’hôte

window.addEventListener('message', (event) => {
  if (event.origin !== HOST) return;            // n’accepter que l’origine de l’hôte
  const msg = event.data;
  if (!msg || msg.version !== 1) return;
  if (msg.type === 'sellio:widget:context') {
    const { recordId, objectApiName, locale, theme, installId } = msg.payload;
    render(recordId, objectApiName, locale, theme);
    // Récupérez les données avec VOTRE token OAuth (n’arrive jamais via postMessage) :
    // fetch(HOST + '/api/v1/' + objectApiName + '/' + recordId,
    //       { headers: { Authorization: 'Bearer ' + accessToken } })
  }
});

// Demandez le contexte au chargement et ajustez la hauteur à votre contenu :
parent.postMessage({ type: 'sellio:widget:ready', version: 1 }, HOST);
parent.postMessage({ type: 'sellio:widget:resize', version: 1,
  payload: { height: document.body.scrollHeight } }, HOST);

Sécurité : l’iframe est en bac à sable et cross-origin, de sorte que le widget ne peut atteindre ni le DOM ni les cookies de l’hôte. L’hôte valide l’origine exacte à chaque postMessage (entrant et sortant) et l’embedUrl est toujours vérifiée par rapport à la liste blanche de l’application. Demandez la portée widgets:embed pour placer des widgets.

Webhooks sortants

Recevez les événements du CRM en temps réel. Enregistrez une URL de destination dans les Paramètres et choisissez les événements auxquels vous vous abonnez. Chaque livraison est un POST avec un corps JSON, l’en-tête x-sellio-event contenant le nom de l’événement, et l’en-tête x-sellio-signature contenant la signature HMAC-SHA256 du corps. Le secret (whsec_...) est affiché lors de la création du webhook.

Catalogue d’événements

record.createdrecord.updatedflow.enrolledflow.message_sentflow.repliedflow.step_completedflow.completed

Exemple de payload

POST https://your-server.example.com/webhook
x-sellio-event: record.created
x-sellio-signature: sha256=<hmac-hex>
Content-Type: application/json

{
  "event": "record.created",
  "tenantId": "…",
  "objectApiName": "lead",
  "recordId": "8f3c…",
  "data": { "name": "Ana Souza", "email": "ana@acme.com" },
  "timestamp": "2026-07-23T12:00:00.000Z"
}

Les événements de campagne et de flow (flow.*) comportent des champs d’inscription supplémentaires :

{
  "event": "flow.replied",
  "tenantId": "…",
  "flowId": "…",
  "flowName": "Outbound Q3",
  "recordId": "8f3c…",
  "enrollmentId": "…",
  "objectApiName": "lead",
  "data": { "name": "Ana Souza", "email": "ana@acme.com" },
  "meta": {},
  "timestamp": "2026-07-23T12:00:00.000Z"
}

Comment vérifier la signature

Calculez le HMAC-SHA256 du corps BRUT (exactement tel que reçu, sans re-sérialiser) à l’aide de votre secret whsec_..., sous la forme sha256=<hex>, et comparez-le à l’en-tête x-sellio-signature en temps constant. Rejetez lorsqu’il ne correspond pas.

import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody = le corps reçu EXACT (chaîne), sans re-sérialiser.
function isValid(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(signatureHeader || '');
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

// Express : utilisez express.raw({ type: 'application/json' }) pour obtenir le corps brut.
app.post('/webhook', (req, res) => {
  const ok = isValid(req.body.toString('utf8'), req.header('x-sellio-signature'), process.env.SELLIO_WHSEC);
  if (!ok) return res.status(401).end();
  const event = req.header('x-sellio-event');
  // ... traiter l’événement
  res.status(200).end();
});

Limite de débit

Chaque clé dispose d’une limite par minute (par défaut 120, configurable). Les réponses incluent les en-têtes X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset. En cas de dépassement, l’API renvoie un 429 avec l’en-tête Retry-After.

Format d’erreur

Les erreurs renvoient du JSON sous la forme { "error": "message" } avec le statut HTTP correspondant :

{ "error": "required field: source" }   # HTTP 400
  • 200 et 201 : succès (201 lors d’une création).
  • 400 : validation ou règle métier.
  • 401 : clé manquante ou invalide.
  • 403 : pas de permission (RBAC) pour l’objet, ou bloqué par la portée de la clé (lecture seule, objet hors de la portée, ou suppression non activée).
  • 404 : objet ou enregistrement introuvable (y compris DELETE/PATCH d’un id inexistant).
  • 429 : limite de débit dépassée (voir l’en-tête Retry-After).

Limitations connues

  • Les filtres se combinent avec un ET logique (tous vrais en même temps) ; il n’y a pas de OU entre les filtres.
  • Les mises à jour sont partielles (PATCH) : vous n’envoyez que les champs à modifier. Il n’y a pas de PUT pour un remplacement complet de l’enregistrement.

Spécification et collection

Importez la spécification OpenAPI 3.1 dans Swagger, Insomnia ou Postman, ou téléchargez la collection Postman prête à l’emploi contenant tous les endpoints ainsi que les variables apiKey et baseUrl.

Créez votre compte et générez la première clé

Créer un compte gratuit
Promoteurs · Sellio