Integra la firma elettronica nel tuo stack
Invia buste, traccia le firme, ricevi webhook. API REST semplice, OpenAPI 3.0, esempi curl/Node/Python — tutto per collegare Certyneo al tuo HRIS, CRM o software gestionale in poche ore.
Avvio rapido
Tre passaggi: crea una chiave API dalle impostazioni, codifica il tuo PDF in base64, invia. La risposta contiene l'`signUrl` che puoi condividere direttamente con il destinatario.
# 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"// 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);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"])Prova l'API dai tuoi strumenti
La raccolta Postman e la scheda RapidAPI sono generate dalla specifica OpenAPI che documenta anche questa pagina. I tre rimangono quindi allineati all'API reale, endpoint per endpoint, piuttosto che divergere al primo aggiunta.
Raccolta Postman
Le 25 richieste organizzate per dominio — buste, documenti, modelli, sigilli, webhook — con un esempio di corpo e risposta per ciascuna. Incollate la vostra chiave nella variabile apiKey della raccolta, quindi avviate GET /health: non richiede alcuna autenticazione e conferma che la vostra configurazione è corretta prima della prima chiamata autenticata.
Buste
Creazione, invio, tracciamento dello stato, annullamento. Una busta può contenere più documenti e più firmatari (parallelo o sequenziale).
Webhook
Tutti gli eventi di busta e destinatario (`envelope.sent`, `recipient.signed`, `envelope.completed`…) consegnati all'URL di vostra scelta — elenco completo su /developers/webhooks. HMAC SHA-256 su ogni payload per verificare l'origine.
Autenticazione semplice
Bearer token. Una chiave per ambiente (test / prod). Revocabile istantaneamente. Limite 100 req/min/chiave, burst di 200, 429 pulito con header Retry-After.
Endpoint disponibili
L'insieme delle rotte pubbliche: account, documenti, buste, modelli, webhook, sigilli elettronici, chiavi API e fatturazione. Tutti accettano un Bearer token e restituiscono JSON.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/account/me | Identity of the authenticated caller (id, email, plan) — scope-less credential probe |
| POST | /api/v1/documents | Upload a PDF (multipart) — returns document id |
| GET | /api/v1/documents | List documents |
| GET | /api/v1/documents/{id} | Fetch document metadata |
| DELETE | /api/v1/documents/{id} | Delete document |
| GET | /api/v1/envelopes | List envelopes (filter with ?status= and ?limit=) |
| POST | /api/v1/envelopes | Create 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}/send | Dispatch DRAFT — sends invitations |
| GET | /api/v1/envelopes/{id}/audit-trail | Download eIDAS audit-trail PDF |
| 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/verify | Public: verify an audit entry + proof against its anchored Merkle root (no auth, reveals nothing) |
| GET | /api/v1/envelopes/{id}/signed-document | Download signed PDF (once COMPLETED) |
| GET | /api/v1/envelopes/bulk | List your bulk-send jobs |
| POST | /api/v1/envelopes/bulk | Bulk 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/templates | List reusable envelope templates |
| POST | /api/v1/templates | Create a template (documents, roles, positioned fields) |
| POST | /api/v1/sepa-mandates | Generate a SEPA mandate PDF and its DRAFT envelope, fields already placed |
| POST | /api/v1/payroll-adapters/normalize | Normalise a payroll CSV (Silae, Sage Paie, PayFit, Lucca) into the bulk-send shape |
| POST | /api/v1/ag-copropriete | Create a condominium general-meeting envelope (resolutions + ownership shares) |
| POST | /api/v1/ag-copropriete/{envelopeId}/votes | Record a co-owner's votes on the meeting resolutions |
| POST | /api/v1/ag-copropriete/{envelopeId}/tally | Tally 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/webhooks | List webhooks |
| POST | /api/v1/webhooks | Register 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/seals | Apply a qualified electronic seal to a document |
| GET | /api/v1/seals/{id} | Fetch seal status |
| GET | /api/v1/seals/{id}/certificate | Download the seal certificate |
| GET | /api/v1/keys | List API keys |
| POST | /api/v1/keys | Create 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/usage | Current period usage and projected cost |
| GET | /api/v1/status | Service status |
| GET | /api/v1/openapi | Machine-readable OpenAPI specification |
Modelli di busta
Un modello registra una volta per tutte il PDF, i ruoli e la posizione dei campi di firma, poi si riutilizza a ogni invio: passate il suo identificativo in templateId al posto di documentIds, e i campi posizionati vengono copiati sulla busta creata.
- • I modelli si creano dal dashboard (Modelli → Nuovo modello), dove depositate il documento e posizionate i campi con il mouse — è il percorso più semplice. L'API permette anche di crearli con POST /api/v1/templates, fornendo i documenti, i ruoli e i campi; attenzione, i campi vi sono posizionati in coordinate assolute (pagina, x, y, larghezza, altezza), quindi bisogna conoscere il layout del PDF.
- • Su un account che non ha ancora registrato alcun modello, GET /api/v1/templates restituisce un elenco vuoto. È il comportamento normale, non un difetto di autenticazione.
- • L'elenco contiene solo i modelli appartenenti all'utente proprietario della chiave API. Un modello creato da un collega non vi figura, anche all'interno di uno spazio di lavoro condiviso: generate la chiave dall'account che possiede il modello.
- • templateId e documentIds si escludono a vicenda: inviate l'uno o l'altro, mai entrambi né nessuno dei due.
- • Fornite almeno tanti destinatari SIGNER quanti ruoli firmatari il modello conta, altrimenti la creazione viene rifiutata con 400. Il campo signerCount restituito dall'elenco indica il numero previsto.
- • Il livello di firma del modello viene ereditato dalla busta, a meno che la richiesta non passi esplicitamente signatureLevel.
# 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.Da Power Automate o Zapier
L'azione « Crea una busta » dei nostri connettori no-code copre solo il percorso per modello: il campo Modello vi è obbligatorio. Per un documento ad hoc che cambia a ogni esecuzione, utilizzate l'azione « Carica un documento » poi un'azione HTTP grezza verso POST /api/v1/envelopes passando il documentIds restituito.
Invio del documento: due forme accettate
POST /api/v1/documents accetta il file in due modi, a scelta. Gli stessi controlli si applicano in entrambi i casi: tipi autorizzati, limite di 50 Mo, verifica della firma binaria e analisi antivirus.
- • In multipart/form-data, con una parte denominata file. È la forma classica, quella di curl -F e della maggior parte delle librerie.
- • In corpo grezzo: gli ottetti del file costituiscono il corpo della richiesta, e l'intestazione Content-Type ne fornisce il tipo (application/pdf per esempio). Utile da uno strumento che trasmette il contenuto così com'è, senza incapsulare la richiesta — è quello che fa il connettore Power Automate.
- • In corpo grezzo, il nome del file non ha spazio nel corpo: indicatelo tramite l'intestazione X-File-Name o il parametro ?fileName=. Senza di esso, il documento viene denominato in base al suo tipo.
- • Un tipo non supportato risponde 415 nominando le due forme accettate, e un corpo annunciato come multipart ma illeggibile risponde 400. Nessuno dei due è un errore del server.
# 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.Posizionamento dei campi di firma
Senza modello, una busta creata a partire da documentIds non ha alcun campo pre-posizionato: il firmatario riceve il documento senza luogo dove firmare. L'array fields, trasmesso nella stessa chiamata di creazione, posiziona ogni campo al punto preciso — è l'equivalente, lato API, di ciò che un modello registra una volta per tutte.
- • Le coordinate sono in punti PDF, origine in alto a sinistra della pagina e asse Y verso il basso (una pagina A4 misura 595 × 842 punti). x e y designano l'angolo superiore sinistro del campo, width e height la sua dimensione.
- • pageNumber inizia da 1, documentIndex inizia da 0. Un numero di pagina al di là del documento non viene rifiutato alla creazione: il campo viene ignorato al momento della firma e non appare da nessuna parte — è la prima cosa da verificare quando un campo manca alla chiamata.
- • recipientEmail deve corrispondere a uno dei destinatari della stessa chiamata, senza distinzione di maiuscole/minuscole. Altrimenti la creazione fallisce elencando tutte le righe difettose, il che evita di correggerle una per una.
- • fields e templateId si escludono a vicenda: un modello porta già il suo proprio layout. L'array fields viene quindi utilizzato solo con documentIds.
- • Tipi accettati: SIGNATURE, INITIALS, DATE_SIGNED, TEXT, CHECKBOX e RADIO_GROUP. required vale true per impostazione predefinita; placeholder e dateFormat sono facoltativi, e options viene considerato solo per RADIO_GROUP.
- • Una busta accetta al massimo 100 campi, 20 documenti e 50 destinatari — i limiti del vostro piano potendo essere inferiori.
# 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.Ancore testuali: posizionare un campo senza conoscere le coordinate
Piuttosto che coordinate, un campo può citare un testo stampato nel documento: anchorText lo individua nel PDF e il server calcola la posizione alla creazione. È la modalità da privilegiare quando il documento viene rigenerato ad ogni invio — stampa unione, generatore di contratti — perché il layout si muove mentre la menzione "Firma del cliente" rimane. anchorPlacement indica da quale lato del testo si posiziona il campo (right per impostazione predefinita, altrimenti below, above o left) e anchorIndex sceglie l'occorrenza quando il testo appare più volte.
x e y rimangono obbligatori anche con un'ancora: servono come ripiego. Un'ancora non trovata non fa fallire la creazione — il campo mantiene la posizione letterale che avete fornito, senza errore né avviso nella risposta. Indicate quindi un ripiego plausibile piuttosto che 0,0 e verificate il rendering su un primo invio.
Autenticazione
Ogni chiamata porta una chiave API nell'intestazione Authorization. Le chiavi si generano da Impostazioni → Chiavi API e vengono visualizzate una sola volta.
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" } }- • Formato: sk_live_… in produzione, sk_test_… per la sandbox. Intestazione: Authorization: Bearer <clé>.
- • Ambiti: envelopes, documents, webhooks, seals — in lettura (:read) o scrittura (:write). La scrittura implica la lettura; l'ambito * concede tutti i diritti.
- • Le chiavi sk_test_ creano risorse in sandbox, escluse dalla quota e dalla fatturazione. Nessuna email reale viene inviata al destinatario, a meno che il suo indirizzo non corrisponda all'email dell'account che invia — utile per testare l'intero flusso su te stesso.
- • Errori: 401 chiave non valida, 403 ambito insufficiente, 429 limite di velocità superato, 402 quota mensile raggiunta.
Forma delle risposte
Un punto da sapere prima di scrivere il tuo client: le collezioni sono incapsulate in un oggetto data, mentre le risorse unitarie vengono restituite in forma piatta. Leggere response.data.data su una risorsa unitaria restituisce quindi undefined.
// GET /api/v1/envelopes
// Collections are WRAPPED in a "data" array.
{
"data": [
{ "id": "env_abc123", "subject": "Contrat", "status": "SENT" }
]
}// GET /api/v1/envelopes/{id}
// Single resources are returned FLAT — no "data" envelope.
{
"id": "env_abc123",
"subject": "Contrat",
"status": "COMPLETED",
"recipients": [ /* … */ ]
}Limiti di velocità
I limiti garantiscono una qualità del servizio stabile per tutti i clienti. Se hai bisogno di più, contattaci.
- • 100 richieste per minuto per chiave API
- • Burst tollerato fino a 200 richieste in meno di 10s
- • Risposta 429 con header Retry-After che indica il ritardo in secondi