Core concepts
Signing
Self-sign PDFs, sign for a person you have already identified, send envelopes, and let external signers complete the session without a company account.
Signature levels
Most signing calls accept signature_level: simple, advanced (historical default when omitted), or qualified. The company must have the level open; otherwise the call returns 403. Qualified flows may require TOTP (mfa_code), a second email OTP (second_otp_code), identity enrollment, and a qualified timestamp. Timestamp outage is 503 TIMESTAMP_UNAVAILABLE_RETRY (retryable).
Read the company’s policy on GET /api/v1/companies/{company}/signature-levels and remaining volume on GET /api/v1/companies/{company}/quotas.
Self-sign
POST /api/v4/sign/info returns page count and dimensions so you can place marks. Then POST /api/v4/sign applies the connected user’s certificate (max 100 PDFs, 10 MB each).
Multipart: file or files[] plus a JSON string params. JSON alternative: { "data"|"url", "filename", "params" } or { "documents": [...], "params" }.
{
"signature_level": "advanced",
"pages": [
{
"page": 0,
"signatures": [
{ "x": 120, "y": 200, "width": 200, "height": 70, "type": "signature" }
]
}
],
"otp_code": "123456"
}Mark types: signature, initials, seal, qrcode, date, text, approval. Coordinates are millimetres. When OTP is required, send otp_code inside params after POST /api/v4/sign/otp. Check GET /api/v4/sign/second-factor?level=qualified before a qualified call. A PDF over 10 MB returns 400 PDF_TOO_LARGE.
Success for one document includes document_uuid, signed_pdf_url, and verification_url. A multi-document partial success is 207.
Anchors
Print the anchor as real text in the PDF (for example {{signature}}), then send that exact string as anchor. The API searches the document, centers the signature on every match, and covers the printed text. Optional page limits the search to one page. Optional occurrence (0-based) keeps a single match. width and height still set the box size, in points. Missing text returns 400 ANCHOR_NOT_FOUND.
{
"pages": [
{
"signatures": [
{ "anchor": "{{signature}}", "width": 150, "height": 50, "type": "signature" }
]
}
]
}Attested qualified signature
POST /api/v4/sign/qualified signs one PDF for a person whose identity you have already verified, in two steps. A code is sent first on the channel you choose (email, SMS, or WhatsApp), using the text you provide, and the signature is issued only when that code is sent back. There is no signing link. The level is always qualified: a one-shot certificate is issued in the name you send, the PDF is signed, and the private key is destroyed.
Multipart only: file (the PDF, max 10 MB) and a JSON string payload. signer.phone and signer.reason are required, the same way as email and first_name. signer.id_card.document_type is cni or passport. The response returns only the masked last 4 digits of the ID number. signer.reason is written into the PDF. Place a mark with x and y (millimetres) or with anchor alone. The signature is centered on that text and covers it. width and height set the box size, in points. type is signature (default), initials, or text. For a text mark, text is the wording drawn in the box. signature_image (PNG or JPEG, max 2 MB) is optional: it dresses marks of type signature only. Without an image, the signature is still applied.
curl -X POST https://api.example.com/api/v4/sign/qualified/otp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"channel":"email","email":"awa.kone@example.com","subject":"Votre code","body":"<p>Votre code est {{otp_code}}.</p>"}'
curl -X POST https://api.example.com/api/v4/sign/qualified \
-H "Authorization: Bearer $TOKEN" \
-F "file=@contrat.pdf" \
-F 'payload={
"otp_code": "123456",
"signer": {
"first_name": "Awa",
"last_name": "Koné",
"email": "awa.kone@example.com",
"phone": "+2250700000000",
"reason": "Acceptation du contrat",
"id_card": { "document_type": "cni", "document_number": "CI123456789" }
},
"params": {
"pages": [{
"signatures": [
{ "anchor": "{{signature}}", "width": 200, "height": 70, "type": "signature" },
{ "x": 20, "y": 40, "width": 180, "height": 40, "type": "text", "text": "Lu et approuvé" }
]
}]
}
}'Envelopes
An envelope is a batch of documents plus signers. POST /api/v4/envelopes is multipart: PDF documents[] (max 10 MB each) and a JSON string payload. Attachments use attachments_{idx}[] (max 15 MiB), where idx is the document index.
{
"title": "NDA Q1",
"message": "Please sign",
"signing_order": "ordered",
"signature_level": "advanced",
"signers": [
{
"name": "Dupont",
"first_name": "Jean",
"email": "jean@example.com",
"phone": "+2250700000001",
"priority": 1,
"positions": {
"0": [
{ "page": 0, "x": 120, "y": 200, "width": 200, "height": 70, "type": "signature" }
]
}
}
]
}signing_order is ordered (default) or any. positions keys are document indexes ("0", "1", …). A mark can send anchor with the text already printed in that PDF. Optional password.enabled protects the PDF (user_password min 4 characters).
The 201 response includes batch_id and each signer’s uuid + signing_url (and dev_otp_code in test mode). Then list, cancel, revoke post-completion access, or delete with the authenticated /api/v4/envelopes/... routes. Cancel / delete on a finished envelope returns 409 ENVELOPE_ALREADY_TERMINAL / ENVELOPE_NOT_TERMINAL.
Public signer session
External signers do not log in. They open the link you send them:
GET /api/v4/public/envelopes/{signer_uuid}— before OTP, documents are hidden; title, message, identity, can_sign, and features are still returnedPOST .../otp(no body) thenPOST .../verifywith{ "otp_code": "123456" }- Optional identity steps when identity.required is true
POST .../signorPOST .../refuse
After OTP, documents[] includes uuid, positions, preview_url, and attachments. Preview a page with GET /api/v4/public/documents/{doc_uuid}/preview?signer={signer_uuid}. A qualified internal signer without TOTP may need POST .../second-otp.
Sign body (all fields optional): signature_image_base64, initials_image_base64, seal_image_base64, seal_consent, mfa_code, second_otp_code, extra_positions (keyed by document uuid, max 50 marks). Refuse accepts reason and optional reason_code seal_consent_declined. Applying a seal without consent returns 400 SEAL_CONSENT_REQUIRED.
Identity
When identity.required is true on the public session, enroll after OTP:
POST .../identity/enroll— multipart document, document_type (cni or passport), 3–8 selfie_frames[], truthy consent. Returns 202 pending with a 30-minute SLA.../rescan— new selfie frames after a mismatch.../document-check— after rescan attempts are exhausted.../prepare— resume after human approval (no body)
Typical codes: IDENTITY_LIVENESS_FAILED, IDENTITY_FACE_MISMATCH, IDENTITY_DOCUMENT_UNREADABLE, IDENTITY_CONSENT_REQUIRED, IDENTITY_BLOCKED, IDENTITY_ON_THE_FLY_QUOTA_REACHED.
Company users manage seal, job title, locale, and signature mentions on PATCH /api/v1/profile/{user}/update. External signers manage reusable identities through /api/v1/public/external-identity (email code, then X-Identity-Token).
Downloads and QR
List signed files with GET /api/v4/documents (page, per_page max 100, status, batch_id). GET /api/v4/documents/{uuid}/download is public and uses the signed URL pair (expires, sig). Share a time-limited link with POST /api/v4/documents/{uuid}/share (expires_in 60–604800 seconds). The QR on the PDF opens GET /api/v4/verify/{uuid} (format=json|html, no Bearer).