Aller au contenu principal
Certyneo
API publique v1

Intégrez la signature électronique dans votre stack

Envoyez des enveloppes, suivez les signatures, recevez des webhooks. API REST simple, OpenAPI 3.0, exemples curl/Node/Python — tout pour brancher Certyneo sur votre HRIS, CRM ou logiciel métier en quelques heures.

Démarrage rapide

Trois étapes : créez une clé API depuis les paramètres, encodez votre PDF en base64, envoyez. La réponse contient le `signUrl` que vous pouvez partager directement avec le destinataire.

cURLbash
# 1. Upload the PDF (multipart) and capture the returned document id.
DOC_ID=$(curl -s -X POST https://certyneo.com/api/v1/documents \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@contrat.pdf" | jq -r .id)

# 2. Create a DRAFT envelope referencing the uploaded document.
ENV_ID=$(curl -s -X POST https://certyneo.com/api/v1/envelopes \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d "{
    \"subject\": \"Contrat de prestation\",
    \"documentIds\": [\"$DOC_ID\"],
    \"recipients\": [
      { \"email\": \"client@example.com\", \"name\": \"Marie Dubois\", \"role\": \"SIGNER\" }
    ]
  }" | jq -r .id)

# 3. Dispatch the envelope — this sends the invitation email/SMS.
curl -X POST https://certyneo.com/api/v1/envelopes/$ENV_ID/send \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
JavaScript / Nodets
// npm install @certyneo/sdk  (or call fetch directly)
const auth = { Authorization: `Bearer ${process.env.CERTYNEO_API_KEY}` };

// 1. Upload the PDF (multipart).
const fd = new FormData();
fd.append("file", new Blob([pdfBuffer], { type: "application/pdf" }), "contrat.pdf");
const doc = await fetch("https://certyneo.com/api/v1/documents", {
  method: "POST", headers: auth, body: fd,
}).then((r) => r.json());

// 2. Create the DRAFT envelope.
const envelope = await fetch("https://certyneo.com/api/v1/envelopes", {
  method: "POST",
  headers: { ...auth, "Content-Type": "application/json" },
  body: JSON.stringify({
    subject: "Contrat de prestation",
    documentIds: [doc.id],
    recipients: [
      { email: "client@example.com", name: "Marie Dubois", role: "SIGNER" },
    ],
  }),
}).then((r) => r.json());

// 3. Dispatch — this triggers the invitation channel for every recipient.
await fetch(`https://certyneo.com/api/v1/envelopes/${envelope.id}/send`, {
  method: "POST", headers: auth,
});
console.log(envelope.id);
Pythonpython
import os, requests

auth = {"Authorization": f"Bearer {os.environ['CERTYNEO_API_KEY']}"}

# 1. Upload the PDF (multipart).
with open("contrat.pdf", "rb") as f:
    doc = requests.post(
        "https://certyneo.com/api/v1/documents",
        headers=auth,
        files={"file": ("contrat.pdf", f, "application/pdf")},
    ).json()

# 2. Create the DRAFT envelope.
envelope = requests.post(
    "https://certyneo.com/api/v1/envelopes",
    headers={**auth, "Content-Type": "application/json"},
    json={
        "subject": "Contrat de prestation",
        "documentIds": [doc["id"]],
        "recipients": [
            {"email": "client@example.com", "name": "Marie Dubois", "role": "SIGNER"},
        ],
    },
).json()

# 3. Dispatch — this triggers the invitation channel for every recipient.
requests.post(
    f"https://certyneo.com/api/v1/envelopes/{envelope['id']}/send",
    headers=auth,
)
print(envelope["id"])

Essayer l'API depuis vos outils

La collection Postman et la fiche RapidAPI sont générées depuis la spécification OpenAPI qui documente aussi cette page. Les trois restent donc alignées sur l'API réelle, endpoint par endpoint, plutôt que de diverger au premier ajout.

Collection Postman

Les 22 requêtes rangées par domaine — enveloppes, documents, modèles, cachets, webhooks — avec un exemple de corps et de réponse pour chacune. Collez votre clé dans la variable apiKey de la collection, puis lancez GET /health : elle ne demande aucune authentification et confirme que votre configuration est bonne avant le premier appel authentifié.

Fiche RapidAPI

Le même catalogue d'endpoints, essayable directement depuis le navigateur. Le banc d'essai attend deux en-têtes distincts : la clé RapidAPI que la plateforme vous attribue, et votre clé Certyneo dans Authorization — c'est la seconde qui autorise réellement l'appel.

Enveloppes

Création, envoi, suivi d'état, annulation. Une enveloppe peut contenir plusieurs documents et plusieurs signataires (parallèle ou séquentiel).

Webhooks

Tous les événements d'enveloppe et de destinataire (`envelope.sent`, `recipient.signed`, `envelope.completed`…) livrés sur l'URL de votre choix — liste complète sur /developers/webhooks. HMAC SHA-256 sur chaque payload pour vérifier l'origine.

Authentification simple

Bearer token. Une clé par environnement (test / prod). Révocable instantanément. Limite 100 req/min/clé, burst de 200, 429 propre avec en-tête Retry-After.

Points de terminaison disponibles

12 routes couvrant le cycle complet : enveloppes, documents, webhooks, clés API. Toutes les routes acceptent un Bearer token et renvoient JSON.

MethodPathDescription
GET/api/v1/account/meIdentity of the authenticated caller (id, email, plan) — scope-less credential probe
POST/api/v1/documentsUpload a PDF (multipart) — returns document id
GET/api/v1/documentsList documents
GET/api/v1/documents/{id}Fetch document metadata
DELETE/api/v1/documents/{id}Delete document
GET/api/v1/envelopesList envelopes (filter with ?status= and ?limit=)
POST/api/v1/envelopesCreate envelope (status: DRAFT) — from a templateId, or from documentIds with an optional fields array
GET/api/v1/envelopes/{id}Fetch envelope state
PATCH/api/v1/envelopes/{id}Update DRAFT envelope
DELETE/api/v1/envelopes/{id}Void / delete DRAFT envelope
POST/api/v1/envelopes/{id}/sendDispatch DRAFT — sends invitations
GET/api/v1/envelopes/{id}/audit-trailDownload eIDAS audit-trail PDF
GET/api/v1/envelopes/{id}/signed-documentDownload signed PDF (once COMPLETED)
GET/api/v1/envelopes/bulkList your bulk-send jobs
POST/api/v1/envelopes/bulkBulk send: create N envelopes from one template + a CSV (Standard/Business only)
GET/api/v1/envelopes/bulk/{id}Bulk-send job progress: counts, per-row failures, created envelopes
GET/api/v1/templatesList reusable envelope templates
POST/api/v1/templatesCreate a template (documents, roles, positioned fields)
POST/api/v1/sepa-mandatesGenerate a SEPA mandate PDF and its DRAFT envelope, fields already placed
POST/api/v1/payroll-adapters/normalizeNormalise a payroll CSV (Silae, Sage Paie, PayFit, Lucca) into the bulk-send shape
POST/api/v1/ag-coproprieteCreate a condominium general-meeting envelope (resolutions + ownership shares)
POST/api/v1/ag-copropriete/{envelopeId}/votesRecord a co-owner's votes on the meeting resolutions
POST/api/v1/ag-copropriete/{envelopeId}/tallyTally the meeting - per-resolution result weighted by ownership shares
GET/api/v1/videos/{videoId}Download a stored identity video - GDPR art. 15 access path
GET/api/v1/webhooksList webhooks
POST/api/v1/webhooksRegister webhook — returns the signing secret once
GET/api/v1/webhooks/{id}Fetch webhook subscription
PATCH/api/v1/webhooks/{id}Update url / events / active state
DELETE/api/v1/webhooks/{id}Unregister
POST/api/v1/sealsApply a qualified electronic seal to a document
GET/api/v1/seals/{id}Fetch seal status
GET/api/v1/seals/{id}/certificateDownload the seal certificate
GET/api/v1/keysList API keys
POST/api/v1/keysCreate API key — the secret is shown once
PATCH/api/v1/keys/{id}Rename / revoke key
DELETE/api/v1/keys/{id}Delete key
GET/api/v1/billing/usageCurrent period usage and projected cost
GET/api/v1/statusService status
GET/api/v1/openapiMachine-readable OpenAPI specification

Modèles d'enveloppe

Un modèle enregistre une fois pour toutes le PDF, les rôles et l'emplacement des champs de signature, puis se réutilise à chaque envoi : passez son identifiant dans templateId au lieu de documentIds, et les champs positionnés sont recopiés sur l'enveloppe créée.

  • Les modèles se créent depuis le tableau de bord (Modèles → Nouveau modèle), où vous déposez le document puis positionnez les champs à la souris — c'est le chemin le plus simple. L'API permet aussi de les créer par POST /api/v1/templates, en fournissant les documents, les rôles et les champs ; attention, les champs y sont positionnés en coordonnées absolues (page, x, y, largeur, hauteur), il faut donc connaître la mise en page du PDF.
  • Sur un compte qui n'a encore enregistré aucun modèle, GET /api/v1/templates renvoie une liste vide. C'est le comportement normal, pas un défaut d'authentification.
  • La liste ne contient que les modèles appartenant à l'utilisateur propriétaire de la clé API. Un modèle créé par un collègue n'y figure pas, même au sein d'un espace de travail partagé : générez la clé depuis le compte qui possède le modèle.
  • templateId et documentIds s'excluent mutuellement : envoyez l'un ou l'autre, jamais les deux ni aucun des deux.
  • Fournissez au moins autant de destinataires SIGNER que le modèle compte de rôles signataires, sinon la création est refusée en 400. Le champ signerCount renvoyé par la liste indique le nombre attendu.
  • Le niveau de signature du modèle est hérité par l'enveloppe, sauf si la requête passe explicitement signatureLevel.
cURLbash
# 1. Discover the templates saved on this account.
curl -s https://certyneo.com/api/v1/templates \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx"

# {
#   "data": [
#     { "id": "cmdq7f4k80001s6y2h1xa9pl3", "name": "Contrat de prestation",
#       "signerCount": 2, "documentCount": 1, "signatureLevel": "SIMPLE" }
#   ],
#   "pagination": { "page": 1, "pageSize": 20, "total": 1, "pages": 1 }
# }
#
# An empty "data" array means no template exists on this account yet —
# create one from the dashboard, it is not an authentication problem.

# 2. Create the envelope FROM the template: no documentIds and no field
#    coordinates, both are carried by the template. Pass one SIGNER
#    recipient per signer role, in the template's role order.
curl -X POST https://certyneo.com/api/v1/envelopes \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Contrat de prestation",
    "templateId": "cmdq7f4k80001s6y2h1xa9pl3",
    "recipients": [
      { "email": "client@example.com", "name": "Marie Dubois", "role": "SIGNER" },
      { "email": "legal@example.com", "name": "Paul Martin", "role": "SIGNER" }
    ]
  }'

# 3. The envelope is DRAFT at this point — POST /envelopes/{id}/send
#    dispatches it, exactly as in the quick-start above.

Depuis Power Automate ou Zapier

L'action « Créer une enveloppe » de nos connecteurs no-code ne couvre que le chemin par modèle : le champ Modèle y est obligatoire. Pour un document ad hoc qui change à chaque exécution, utilisez l'action « Téléverser un document » puis une action HTTP brute vers POST /api/v1/envelopes en passant le documentIds retourné.

Envoi du document : deux formes acceptées

POST /api/v1/documents accepte le fichier de deux façons, au choix. Les mêmes contrôles s'appliquent dans les deux cas : types autorisés, plafond de 50 Mo, vérification de la signature binaire et analyse antivirus.

  • En multipart/form-data, avec une partie nommée file. C'est la forme classique, celle de curl -F et de la plupart des bibliothèques.
  • En corps brut : les octets du fichier constituent le corps de la requête, et l'en-tête Content-Type en donne le type (application/pdf par exemple). Utile depuis un outil qui transmet le contenu tel quel, sans envelopper la requête — c'est ce que fait le connecteur Power Automate.
  • En corps brut, le nom du fichier n'a pas de place dans le corps : indiquez-le via l'en-tête X-File-Name ou le paramètre ?fileName=. Sans lui, le document est nommé d'après son type.
  • Un type non pris en charge répond 415 en nommant les deux formes acceptées, et un corps annoncé comme multipart mais illisible répond 400. Ni l'un ni l'autre n'est une erreur serveur.
cURLbash
# a. multipart/form-data — the classic shape.
curl -X POST https://certyneo.com/api/v1/documents \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@contrat.pdf;type=application/pdf"

# b. raw body — the file bytes ARE the body, typed by Content-Type.
#    The filename has nowhere to live in the body, so pass it as a header
#    (or ?fileName=). Without it the document is named after its type.
curl -X POST https://certyneo.com/api/v1/documents \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/pdf" \
  -H "X-File-Name: contrat.pdf" \
  --data-binary "@contrat.pdf"

# Both return the same 201 with the document id to pass as documentIds.

Placement des champs de signature

Sans modèle, une enveloppe créée à partir de documentIds n'a aucun champ pré-positionné : le signataire reçoit le document sans emplacement où signer. Le tableau fields, transmis dans le même appel de création, place chaque champ au point près — c'est l'équivalent, côté API, de ce qu'un modèle enregistre une fois pour toutes.

  • Les coordonnées sont en points PDF, origine en haut à gauche de la page et axe Y vers le bas (une page A4 mesure 595 × 842 points). x et y désignent le coin supérieur gauche du champ, width et height sa taille.
  • pageNumber commence à 1, documentIndex commence à 0. Un numéro de page au-delà du document n'est pas rejeté à la création : le champ est ignoré au moment de la signature et n'apparaît nulle part — c'est la première chose à vérifier lorsqu'un champ manque à l'appel.
  • recipientEmail doit correspondre à l'un des destinataires du même appel, sans distinction de casse. Sinon la création échoue en listant toutes les lignes fautives, ce qui évite de les corriger une par une.
  • fields et templateId s'excluent mutuellement : un modèle porte déjà sa propre mise en page. Le tableau fields ne s'utilise donc qu'avec documentIds.
  • Types acceptés : SIGNATURE, INITIALS, DATE_SIGNED, TEXT, CHECKBOX et RADIO_GROUP. required vaut true par défaut ; placeholder et dateFormat sont facultatifs, et options n'est retenu que pour RADIO_GROUP.
  • Une enveloppe accepte au maximum 100 champs, 20 documents et 50 destinataires — les limites de votre plan pouvant être inférieures.
cURLbash
# Ad-hoc envelope WITH pre-placed fields — no template involved.
# Coordinates are PDF points, origin TOP-LEFT of the page, +Y downwards
# (A4 = 595 x 842 pt). x / y are the field box's top-left corner.
#
# The last field is positioned by ANCHOR instead of by eye: the server
# locates "Signature du client" in the PDF and computes the spot. x / y
# stay required there — they are the fallback if the text is not found.
curl -X POST https://certyneo.com/api/v1/envelopes \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Contrat de prestation",
    "documentIds": ["cmdq7f4k80001s6y2h1xa9pl3"],
    "recipients": [
      { "email": "client@example.com", "name": "Marie Dubois", "role": "SIGNER" }
    ],
    "fields": [
      { "recipientEmail": "client@example.com", "documentIndex": 0,
        "pageNumber": 2, "fieldType": "SIGNATURE",
        "x": 90, "y": 640, "width": 180, "height": 44 },
      { "recipientEmail": "client@example.com", "documentIndex": 0,
        "pageNumber": 2, "fieldType": "DATE_SIGNED",
        "x": 320, "y": 640, "width": 140, "height": 30,
        "dateFormat": "DD/MM/YYYY" },
      { "recipientEmail": "client@example.com", "documentIndex": 0,
        "pageNumber": 2, "fieldType": "TEXT",
        "x": 90, "y": 700, "width": 200, "height": 30,
        "placeholder": "Fonction", "required": false },
      { "recipientEmail": "client@example.com", "documentIndex": 0,
        "pageNumber": 2, "fieldType": "SIGNATURE",
        "anchorText": "Signature du client", "anchorPlacement": "below",
        "anchorIndex": 0,
        "x": 90, "y": 640, "width": 180, "height": 44 }
    ]
  }'

# The envelope is DRAFT at this point — POST /envelopes/{id}/send
# dispatches it, exactly as in the quick-start above.

Ancres textuelles : placer un champ sans connaître les coordonnées

Plutôt que des coordonnées, un champ peut citer un texte imprimé dans le document : anchorText le repère dans le PDF et le serveur calcule la position à la création. C'est le mode à privilégier lorsque le document est régénéré à chaque envoi — publipostage, générateur de contrats — car la mise en page bouge tandis que la mention « Signature du client » reste. anchorPlacement indique de quel côté du texte se pose le champ (right par défaut, sinon below, above ou left) et anchorIndex choisit l'occurrence lorsque le texte apparaît plusieurs fois.

x et y restent obligatoires même avec une ancre : ils servent de repli. Une ancre introuvable ne fait pas échouer la création — le champ garde la position littérale que vous avez fournie, sans erreur ni avertissement dans la réponse. Indiquez donc un repli plausible plutôt que 0,0, et vérifiez le rendu sur un premier envoi.

Authentification

Chaque appel porte une clé API dans l'en-tête Authorization. Les clés se génèrent depuis Réglages → Clés API et ne sont affichées qu'une seule fois.

HTTPhttp
GET /api/v1/account/me HTTP/1.1Host: certyneo.com
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx

# 200 OK
{ "data": { "id": "usr_…", "email": "you@example.com", "plan": "BUSINESS", "environment": "live" } }
  • Format : sk_live_… en production, sk_test_… pour le bac à sable. En-tête : Authorization: Bearer <clé>.
  • Portées : envelopes, documents, webhooks, seals — en lecture (:read) ou écriture (:write). L'écriture implique la lecture ; la portée * donne tous les droits.
  • Les clés sk_test_ créent des ressources en bac à sable, exclues du quota et de la facturation.
  • Erreurs : 401 clé invalide, 403 portée insuffisante, 429 limite de débit dépassée, 402 quota mensuel atteint.

Forme des réponses

Un point à connaître avant d'écrire votre client : les collections sont encapsulées dans un objet data, alors que les ressources unitaires sont renvoyées à plat. Lire response.data.data sur une ressource unitaire renvoie donc undefined.

Collection — encapsuléejson
// GET /api/v1/envelopes
// Collections are WRAPPED in a "data" array.
{
  "data": [
    { "id": "env_abc123", "subject": "Contrat", "status": "SENT" }
  ]
}
Ressource unitaire — à platjson
// GET /api/v1/envelopes/{id}
// Single resources are returned FLAT — no "data" envelope.
{
  "id": "env_abc123",
  "subject": "Contrat",
  "status": "COMPLETED",
  "recipients": [ /* … */ ]
}

Limites de débit

Les limites garantissent une qualité de service stable pour tous les clients. Si vous avez besoin de plus, contactez-nous.

  • 100 requêtes par minute par clé API
  • Burst toléré jusqu'à 200 requêtes en moins de 10 s
  • Réponse 429 avec en-tête Retry-After indiquant le délai en secondes