Skip to main content
Certyneo
Public API v1

Integrate Electronic Signatures into Your Stack

Send envelopes, track signatures, receive webhooks. Simple REST API, OpenAPI 3.0, curl/Node/Python examples — everything to plug Certyneo into your HRIS, CRM, or business software in hours.

Quick Start

Three steps: create an API key from settings, encode your PDF in base64, send. The response contains the `signUrl` that you can share directly with the recipient.

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
// Plain fetch, no SDK to install.
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"])

Try the API from your tools

The Postman collection and the RapidAPI listing are both generated from the OpenAPI specification that documents this page. All three therefore stay aligned with the real API, endpoint by endpoint, instead of drifting apart the first time one is added.

Postman collection

All 25 requests grouped by area — envelopes, documents, templates, seals, webhooks — each with an example body and an example response. Paste your key into the collection's apiKey variable, then run GET /health: it needs no authentication and confirms your setup is sound before the first authenticated call.

RapidAPI listing

The same endpoint catalogue, runnable straight from the browser. The playground expects two distinct headers: the RapidAPI key the platform issues you, and your Certyneo key in Authorization — the second one is what actually authorises the call.

Envelopes

Create, send, track status, cancel. An envelope can contain multiple documents and multiple signers (parallel or sequential).

Webhooks

Every envelope and recipient event (`envelope.sent`, `recipient.signed`, `envelope.completed`…) delivered to the URL of your choice — full list at /developers/webhooks. HMAC SHA-256 on each payload to verify the origin.

Simple Authentication

Bearer token. One key per environment (test / prod). Instantly revocable. Limit 100 req/min/key, burst up to 200, clean 429 with Retry-After header.

Available Endpoints

52 public routes: account, documents, envelopes, templates, webhooks, electronic seals, API keys and billing. Every route accepts a Bearer token and returns JSON.

MethodPathDescription
GET/api/v1/account/meIdentity of the authenticated caller (id, email, plan) — scope-less credential probe
GET/api/v1/sandboxCount the data created with test keys (deleted automatically after 30 days)
DELETE/api/v1/sandboxDelete all data created with test keys, without touching real data
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}Delete DRAFT envelope (409 once sent — void it instead)
POST/api/v1/envelopes/{id}/sendDispatch DRAFT — sends invitations
POST/api/v1/envelopes/{id}/voidVoid a sent envelope not yet fully signed — pending signers are notified
GET/api/v1/envelopes/{id}/audit-trailDownload eIDAS audit-trail PDF
POST/api/v1/envelopes/{id}/embed-urlReturns a recipient's signing URL together with an embedding authorization for the origin you ask for. Call it at click time: the authorization lasts two hours.
GET/api/v1/audit-anchors/{root}Public: metadata of an anchored audit batch, or the .ots proof file with ?format=ots (no auth)
POST/api/v1/audit-anchors/verifyPublic: verify an audit entry + proof against its anchored Merkle root (no auth, reveals nothing)
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)
GET/api/v1/workspacesList the workspaces you can create envelopes in
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/payslips/workspacesPayslips: the workspaces this key delivers for - owned, or delegated by a client (accounting firms, payroll bureaus)
GET/api/v1/payslips/employeesPayslips: the payroll roster, with who opposed electronic delivery (paper payslip due)
PUT/api/v1/payslips/employeesPayslips: add or update roster employees (never removes anyone)
POST/api/v1/payslipsDeliver one payslip (PDF) to an employee's safe - sealed, kept 50 years, retry-safe
GET/api/v1/payslipsList delivered payslips with their consultation proof
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/healthHealth check - database, queue, and object storage
GET/api/v1/openapiMachine-readable OpenAPI specification

Envelope templates

A template stores the PDF, the roles and the position of every signature field once, then gets reused on every send: pass its id as templateId instead of documentIds, and the positioned fields are copied onto the envelope you create.

  • • Templates are created from the dashboard (Templates → New template), where you drop the document and place the fields with the mouse — that is the simplest path. The API can also create them through POST /api/v1/templates, taking the documents, the roles and the fields; note that fields are positioned in absolute coordinates (page, x, y, width, height), so you need to know the PDF layout.
  • • On an account that has no template saved yet, GET /api/v1/templates returns an empty list. That is the normal behaviour, not an authentication problem.
  • • The list only contains templates owned by the user the API key belongs to. A template created by a colleague will not appear, even inside a shared workspace: generate the key from the account that owns the template.
  • • templateId and documentIds are mutually exclusive: send one or the other, never both and never neither.
  • • Supply at least as many SIGNER recipients as the template has signer roles, otherwise creation is rejected with a 400. The signerCount field returned by the list tells you how many are expected.
  • • The envelope inherits the template's signature level, unless the request passes signatureLevel explicitly.
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.

From Power Automate or Zapier

The "Create envelope" action in our no-code connectors only covers the template path — the Template field is required there. For an ad-hoc document that changes on every run, use the "Upload document" action then a raw HTTP action against POST /api/v1/envelopes, passing the returned documentIds.

Sending the document: two accepted shapes

POST /api/v1/documents accepts the file in either of two ways. The same checks apply to both: allowed types, the 50 MB cap, magic-byte verification and the virus scan.

  • • As multipart/form-data, with a part named file. This is the classic shape, the one curl -F and most libraries send.
  • • As a raw body: the file bytes are the request body, and the Content-Type header gives the type (application/pdf, for instance). Useful from a tool that forwards the content as-is without wrapping the request — this is what the Power Automate connector does.
  • • In raw mode the filename has nowhere to live in the body: pass it with the X-File-Name header or the ?fileName= query parameter. Without it, the document is named after its type.
  • • An unsupported type answers 415 naming both accepted shapes, and a body announced as multipart but unreadable answers 400. Neither is a server error.
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.

Signature field placement

Without a template, an envelope created from documentIds has no pre-placed field: the signer receives the document with nowhere to sign. The fields array, sent in the same create call, positions every field to the point — the API-side equivalent of what a template stores once and for all.

  • • Coordinates are in PDF points, with the origin at the TOP-LEFT of the page and the Y axis pointing down (an A4 page is 595 × 842 points). x and y are the field box's top-left corner, width and height its size.
  • • pageNumber starts at 1, documentIndex starts at 0. A page number beyond the document is not rejected at creation: the field is skipped at signing time and appears nowhere — the first thing to check when a field goes missing.
  • • recipientEmail must match one of the recipients in the same call, case-insensitively. Otherwise creation fails listing every offending row, so you fix them in one round-trip instead of one at a time.
  • • fields and templateId are mutually exclusive: a template already carries its own layout. The fields array is therefore only used together with documentIds.
  • • Accepted types: SIGNATURE, INITIALS, DATE_SIGNED, TEXT, CHECKBOX and RADIO_GROUP. required defaults to true; placeholder and dateFormat are optional, and options is only kept for RADIO_GROUP.
  • • An envelope accepts at most 100 fields, 20 documents and 50 recipients — your plan's own limits may be lower.
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.

Text anchors: place a field without knowing the coordinates

Instead of coordinates, a field can quote a string printed in the document: anchorText locates it in the PDF and the server computes the position at creation time. This is the mode to prefer when the document is regenerated on every send — mail merge, contract generators — because the layout moves while the words "Signature du client" stay put. anchorPlacement sets which side of the text the field lands on (right by default, otherwise below, above or left) and anchorIndex picks the occurrence when the text appears several times.

x and y stay required even with an anchor: they are the fallback. A missed anchor does not fail the creation — the field keeps the literal position you supplied, with no error or warning in the response. So give it a sensible fallback rather than 0,0, and check the result on a first send.

Embedded signing: sign without leaving your application

Your customer reviews and signs the document from inside your own interface. The ceremony opens in a window on top of your page, and they come back to you automatically once it is done. Nothing changes server-side: create the envelope as usual with a redirectUrl, then hand the recipient's signing URL to the SDK.

  • • Call Certyneo.sign() directly in your click handler and pass your server call through getUrl. An await before sign() leaves the user gesture behind, and Safari and Firefox then block the window. The SDK opens it immediately and loads it once your promise resolves.
  • • redirectUrl is the return channel. We send the signer back there with certyneo_status (completed or declined) and certyneo_envelope appended. Load the same script on that return page: it is what notifies the portal window still open behind it.
  • • In embedded mode our own calls to action are suppressed. Your customer is never invited to create a Certyneo account.
  • • If the browser refuses the window, the SDK falls back to a full-page redirect on its own. The journey is identical for the signer and your return page receives exactly the same parameters.
  • • There is deliberately no "the user gave up" callback: the window's state is not readable from your page, so such a callback would fire wrongly. A signature that does not arrive stays pending until the webhook says otherwise.
JavaScriptjavascript
// 1. Your server creates and sends the envelope, then hands the browser the
//    recipient's signing URL. Nothing new on the API side:
//      POST /api/v1/envelopes        { …, "redirectUrl": "https://your-app.com/quote/42/signed" }
//      POST /api/v1/envelopes/{id}/send   → recipients[].accessToken
//      signingUrl = "https://certyneo.com/sign/" + accessToken

// 2. On your quote page — load the SDK once:
//      <script src="https://certyneo.com/embed.js"></script>

document.querySelector("#sign").addEventListener("click", () => {
  Certyneo.sign({
    // Called AFTER the window is already open, so no popup blocker ever
    // sees a delay between the click and window.open.
    getUrl: () =>
      fetch("/api/quote/42/signature", { method: "POST" })
        .then((r) => r.json())
        .then((d) => d.signingUrl),

    onCompleted: (e) => showConfirmation(e.envelopeId),
    onDeclined: () => showDeclined(),
    onError: (e) => showError(e),
  });
});

// 3. On https://your-app.com/quote/42/signed — your return page — load the
//    same script and render a short confirmation. It relays the outcome to
//    the portal window behind it, then closes itself.

The browser event is not proof of signature

onCompleted is a display signal emitted by a browser, so anyone can forge it from their own console. Never move a record to "signed" on that basis: use it to refresh the screen, and rely on the signed envelope.completed webhook received by your server for the real state. This is the classic pitfall of payment integrations, and it repeats identically here.

Developing on your own machine

With a test key, redirectUrl accepts http://localhost (and 127.0.0.1), so you can build the whole flow locally before deploying. A live key rejects it, so that no production envelope ever sends a real signer to a machine that is not theirs.

Show the ceremony inside your page

Frame mode displays the signature inside your own interface, with no window. It asks one more step than window mode: proving that the site displaying it is yours. That is what stops a third party from framing our ceremonies to mislead your customers.

  • • Declare your site under Settings → Embedded signing, then publish the DNS record shown there. An exact address: a different subdomain or port counts as a different site.
  • • For each signature, request a fresh URL from POST /api/v1/envelopes/{id}/embed-url. It carries the display authorization and lasts two hours, which matches click time rather than envelope creation.
  • • Two journeys cannot run inside a frame: qualified signatures, whose identity check refuses to be framed in turn, and payment on signature. In both cases the signer is sent to a full window.
  • • The “Powered by Certyneo” line disappears from the frame on the Business plan and above. Frame mode itself is included from the Standard plan.
JavaScriptjavascript
// Mode cadre : la cérémonie s'affiche DANS votre page.
//
// 1. Déclarez votre site une fois pour toutes dans Réglages → Signature
//    intégrée, et publiez l'enregistrement DNS qu'on vous y donne.
//
// 2. À chaque signature, votre serveur demande une URL fraîche. Elle porte
//    l'autorisation qui permet au navigateur d'afficher la cérémonie chez
//    vous, et vaut deux heures :
//
//      POST /api/v1/envelopes/{id}/embed-url
//      { "origin": "https://portail.exemple.fr" }
//      -> { "signingUrl": "…", "expiresAt": "…" }

Certyneo.sign({
  mode: "iframe",
  el: "#zone-signature",        // sélecteur ou nœud de votre page

  getUrl: () =>
    fetch("/api/devis/42/signature", { method: "POST" })
      .then((r) => r.json())
      .then((d) => d.signingUrl),

  onCompleted: (e) => afficherConfirmation(e.envelopeId),
  onDeclined: () => afficherRefus(),
});

// Le cadre parle à votre page par postMessage — pas besoin de page de retour,
// contrairement au mode fenêtre.

Get a signature without sending any email

A portal that shows the ceremony inside its own pages rarely needs our invitation: it lands next to their own flow and offers an account to a customer who does not want one. Set a recipient's notification channel to NONE and nothing is sent to them — you are the one taking them to their signature.

  • • The setting is per recipient, not per envelope: you can silence the customer you host and keep the email for an outside counter-signer, who has no portal of their own.
  • • It is limited to signers and approvers, the only ones you have a link to hand over. You read that link in the create and send responses, or you ask for a fresh URL at click time from POST /api/v1/envelopes/{id}/embed-url.
  • • Automatic reminders stop on their own for that recipient: we never wrote to them, so we do not start with a reminder. Every other recipient is still reminded as usual.
  • • The signature certificate says so, recipient by recipient. An evidence file that stayed silent about it would suggest this signer received an invitation from us that never existed.
JSONjson
POST /api/v1/envelopes

{
  "subject": "Mandat de gestion",
  "documentIds": ["doc_8f2c1a4b9e7d"],
  "recipients": [
    {
      "email": "client@exemple.fr",
      "name": "Camille Client",
      "role": "SIGNER",
      "notificationChannel": "NONE"
    },
    {
      "email": "notaire@exemple.fr",
      "name": "Maître Notaire",
      "role": "SIGNER"
    }
  ]
}

// La réponse rend le lien de chaque signataire :
//
//   "recipients": [
//     { "email": "client@exemple.fr",  "accessToken": "…",
//       "notificationChannel": "NONE"  },
//     { "email": "notaire@exemple.fr", "accessToken": "…",
//       "notificationChannel": "EMAIL" }
//   ]
//
// Le client ne reçoit rien : vous l'emmenez sur sa signature depuis votre
// portail. Le notaire, lui, reçoit son invitation comme d'habitude.

Show your customer's name as the sender

By default, the signer reads the name of the API key owner. If you have documents signed for several companies with a single key, pass the sender field when creating the envelope: the signing page and the invitation and reminder emails then show the name of the company concerned.

  • • Available on the Business plan and above. On a lower plan the request is refused, rather than letting the signer read a name other than the one you expect.
  • • The name is display only: the certificate and audit trail keep the real sender, and emails still leave from Certyneo.
  • • The address is optional. Without it, the signer sees only the name, never the key owner's address.
JSONjson
POST /api/v1/envelopes

{
  "subject": "Devis 2026-118",
  "documentIds": ["doc_8f2c1a4b9e7d"],
  "recipients": [
    { "email": "client@exemple.fr", "name": "Camille Client", "role": "SIGNER" }
  ],
  "sender": {
    "name": "1.2.3. Panneaux Solaires",
    "email": "devis@123-panneaux-solaires.fr"
  }
}

Authentication

Each call carries an API key in the Authorization header. Keys are generated from Settings → API Keys and are displayed only once.

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_… in production, sk_test_… for sandbox. Header: Authorization: Bearer <clé>.
  • • Scopes: envelopes, documents, webhooks, seals — in read (:read) or write (:write). Writing implies reading; the * scope grants all rights.
  • • Keys sk_test_ create sandbox resources, excluded from quota and billing. No real email is sent to the recipient, unless their address matches the sending account's own email — useful for testing the full flow on yourself.
  • • Errors: 401 invalid key, 403 insufficient scope, 429 rate limit exceeded, 402 monthly quota reached.

Response format

One thing to know before writing your client: collections are wrapped in a data object, while single resources are returned flat. Reading response.data.data on a single resource therefore returns undefined.

Collection — wrappedjson
// GET /api/v1/envelopes
// Collections are WRAPPED in a "data" array.
{
  "data": [
    { "id": "env_abc123", "subject": "Contrat", "status": "SENT" }
  ]
}
Single resource — flatjson
// GET /api/v1/envelopes/{id}
// Single resources are returned FLAT — no "data" envelope.
{
  "id": "env_abc123",
  "subject": "Contrat",
  "status": "COMPLETED",
  "recipients": [ /* … */ ]
}

Rate Limits

Limits ensure stable service quality for all customers. If you need more, contact us.

  • • 100 requests per minute per API key
  • • Burst tolerated up to 200 requests in less than 10 seconds
  • • 429 response with Retry-After header indicating the delay in seconds

Go further