DoConure

Signing on its own

If the document already exists — you generated it earlier, or you want to review it before anyone is asked to sign — the signature API is separate:

CallDoes
POST /api/v1/sign/filesUpload a PDF you already have and get a fileId. Charges nothing; see below.
GET /api/v1/sign/files, GET /api/v1/sign/files/{fileId}, DELETE /api/v1/sign/files/{fileId}List your PDFs, read one's facts (never its bytes), delete one nobody needs.
POST /api/v1/sign/envelopesDraft an envelope over a document you produced or a PDF you uploaded. Nothing sent, nothing charged — unless you add ?send=true, which drafts and sends in one call (and is then the billable moment).
POST /api/v1/sign/envelopes/{id}/sendIssue the links and email them. This is the billable moment.
GET /api/v1/sign/envelopes/{id}Where it has got to, and what each signer has done.
GET /api/v1/sign/envelopes/{id}/documentThe signed PDF, once complete.
GET /api/v1/sign/envelopes/{id}/evidenceA ZIP with the signed PDF, the source document and the full event trail as JSON — everything needed to defend the signature outside this system. Any closed envelope, not only a completed one.
POST /api/v1/sign/envelopes/{id}/cancelRevoke it (the console button says Revoke; the path stays /cancel). Every link dies, and each signer who had been asked is told it was revoked.
POST /api/v1/sign/envelopes/{id}/recipients/{recipientId}/replacePut somebody else in a signer's place (email, fullName, optional reason; phone when the signer confirms with a code by SMS): the fields and the place in the signing order go to them, the old link dies and its holder is told. On a draft it simply corrects the signer, telling nobody. A signature already given is never undone. Not a charge.
POST /api/v1/sign/envelopes/{id}/recipients/{recipientId}/resendReissue one signer's link — the old one dies immediately. Not a second charge.

Replacing a signer

When the wrong person was named, or somebody is no longer available, replace the signer instead of leaving the request open. Send {"email": "...", "fullName": "...", "reason": "..."} to .../recipients/{recipientId}/replace while the envelope is a draft or open (sent or in_progress). On a draft nothing has been sent and nobody holds a link, so the call simply corrects the signer's name, address (and phone): no message goes out, no callback is sent, and the change is kept as one event on the envelope's trail and one line in the audit log. A signer who confirms with a code by SMS (validationMode: "sms") needs the new person's "phone" as well: the old number is never kept, and without it the request is refused (INVALID_REQUEST_BODY, details.field: "phone"; a number the code cannot reach is SMS_VALIDATION_UNAVAILABLE). A signer who has signed or declined, or whose group was completed by somebody else (skipped), cannot be replaced. The slot is handed over in place, so the signature fields and the signing order stay exactly where they were; the old link stops working at once and its holder receives a message that the request was withdrawn; the new person is invited when it is their turn. The answer's delegatedFrom names who held the slot, with replacedBy set (it is empty when a signer passed their own turn), and the certificate of completion says in words that the sender replaced them; its event trail lists each event under the person who held the slot at the time. Somebody who opens a link that was replaced, withdrawn or that expired before it was used is told which of the three it was, in the envelope's language; the page shows nothing else and the link can do nothing. A signer who passes their own turn on (when you allow it) is held to the same rules: on a signer who confirms with a code by SMS the signing page asks for the new person's mobile number, and the old number is never kept. Use cancel to end the whole request. When a request is cancelled or expires, every signer who had been asked and had not finished is told; somebody who was never invited (a later group of an ordered request) is not. Organisations can also have the mails their signers receive carry their own logo, colours, footer and wording, and have each signer sent the signed document: the first is under Branding (how), the second in Settings, under Notifications.

The subject of the mails

Every mail we send that does not carry your branding has a subject that starts with “DoConure - ”, so a signer recognises it in the inbox: the invitation reads DoConure - followed by your subject. A branded mail keeps your organisation's subject as you wrote it, without our name. In the text of every mail a time is written in your organisation's locale, with its zone. The button sits in the middle of the mail with its address written out underneath, for a mail program that does not show buttons.

The mails your signers receive, in your look

Off until you switch it on (Branding → Brand). You set a header colour, a button colour, a footer colour and a footer text (a contact line, a notice) in Italian and in English, and the logo you already uploaded is shown at the top. Each mail carries the footer text of its envelope's language; when that one is empty, the other. It is never HTML you write: our one template applies those values, and the plain-text part of every mail is the same message with your footer text in it. The logo travels inside the mail, so nothing is fetched when it is opened. The greeting uses the signer's name as you sent it, whole. It covers the invitation, the reminder, the revoked and expired notices and the signed copy.

Two things never change. The verification code mail is always ours: a code is not a place for branding. And a branded mail loses our own “DoConure by Limepeak” line, since the footer is yours, but keeps the automatic-message note and the link for reporting a document the person was not expecting. A test envelope's mail still opens with the red TEST warning.

The evidence bundle is meant to leave this system: hand it to a lawyer, an auditor, or a customer's own archive without giving them access here. A declined or cancelled envelope can produce one too — proving someone refused to sign is still evidence. Signature evidence is kept for 10 years after an envelope closes and the source document for 365 days after it is generated; past either window the corresponding file is silently omitted rather than failing the whole export, and evidence.json's own fields (hashes, timestamps, closedReason) still answer what happened.

Sign a PDF you already have

Signer Fly works without Composer Fly. Upload the PDF once, then name its fileId in any number of envelopes:

POST /api/v1/sign/files
Idempotency-Key: nda-2026-contract-v3

{ "fileName": "nda.pdf", "fileBase64": "JVBERi0xLjQK..." }

201 Created
{ "output": { "sign": { "file": {
    "fileId": "6d1c...", "sha256": "ab12...", "pageCount": 3,
    "sizeBytes": 48211, "created": true } } } }

POST /api/v1/sign/envelopes?send=true
{ "signers": { "A": { "email": "anna@acme.example", "fullName": "Anna Rossi",
                      "fields": [ { "kind": "signature", "page": 3,
                                    "x": 0.1, "y": 0.78 } ] } },
  "settings": { "documents": [ { "fileId": "6d1c..." } ],
                "subject": "NDA" } }
  • One file, many envelopes. The file is referenced, never copied, and it never changes: a new version is a new upload and a new fileId. Each envelope records the file's sha256; add "sha256": "..." beside a fileId to be refused (SOURCE_FILE_CHANGED) if it is not the file you mean.
  • Identical bytes are one file. Uploading a PDF your organisation already holds answers 200 with the existing fileId and created: false. Another organisation's file is never revealed: its id is a 404.
  • One-shot. { "fileBase64": "...", "fileName": "nda.pdf" } in settings.documents is the same upload inline: same validation, same storage, and the file is then on your account like any other. A flow's extra documents take the same three forms; the document a flow composes is still always first.
  • What is refused (FILE_REJECTED, with details.reason): not a PDF, malformed, encrypted, more than 200 pages, over the size limit, or carrying JavaScript, a launch action, an attached file, XFA or rich media — the signer opens the file in their own browser, so nothing that runs is accepted. Nothing is rewritten: the certificate hashes the bytes you sent.
  • Deleting. A file an open envelope still uses cannot be deleted (STORED_FILE_IN_USE). One that only finished envelopes used can; its bytes are kept for their evidence bundle until their retention ends. Unused files expire like any stored file.
  • The certificate says whose it is. A source you uploaded was not produced by this service, so the certificate and the evidence manifest list it as uploaded by the customer, with its hash.
  • The console has the same: Signatures → New signature request uploads or picks a PDF and sends it, with no API key.

Where the signer signs

The signing experience is hosted by DoConure at a secure public URL: the signer follows a link to /sign/… on our domain. That is the whole product today. There is no embedded or iframe signing and no SDK to put the page inside your own site, and it is not promised: it is a possible future evolution that has not been designed, not a feature on a roadmap. (The page refuses to be framed on purpose.)

Test envelopes

settings.isTest: true (needs the isTest token scope) makes a test envelope: it spends the shared daily test allowance and never credit; every mail about it carries [TEST] in the subject and opens with a red warning line (“TEST — this document has no legal value”, in the envelope’s language, italian or english); the signing page, the signed file (a red TEST band and tiled text on every page, signature block and certificate) say TEST and that it has no legal value; and it can never reach a paid third party. Asking for an SMS code, a seal or a timestamp together with isTest is refused with FEATURE_NOT_AVAILABLE_IN_TEST_MODE before anything is created. Read it back as output.sign.isTest.

The request: signers and settings

The body is { signers, settings } — the same shape as Generating a document's { data, settings }. signers is an object keyed by a label you choose ("A", "B", …) — it identifies each signer for you, and carries no ordering by itself:

POST /api/v1/sign/envelopes
Authorization: Bearer lpdc_...

{
  "signers": {
    "A": {
      "email": "anna@acme.example",
      "fullName": "Anna Rossi",
      "validationMode": "sign",
      "fields": [
        { "kind": "signature", "page": 1,
          "x": 0.1, "y": 0.78, "width": 0.32, "height": 0.09 }
      ]
    }
  },
  "settings": {
    "documentId": "9b21...",
    "subject": "Service agreement 2026",
    "hasSigningOrder": false,
    "expirationDate": null,
    "reminderFrequencyHours": 0,
    "cc": [], "ccNotifyOn": "none",
    "bcc": [], "bccNotifyOn": "none",
    "language": "en",
    "callbackUrl": { "onSuccess": null, "onError": null }
  }
}
SettingDefaultMeans
subjectyour organisation's default subject What the request is about, up to 200 characters: the mail's subject, and named again in its text. Required, unless your organisation set a default in Settings.
messageyour organisation's default message, or none Your own words in the request mail, up to 2,000 characters, placed above the signing button. Line breaks and **bold** are understood: Writing the message.
hasSigningOrderfalse false: every signer is one group, notified together. true: each signer's own order (a whole number, required only when this is true) gates them — two signers sharing a number sign as a group, and the next group is told only once this one is entirely done, or once one of them has if the group's groupMode is "OR". There is no separate "sequential" value: ordinary sequential signing is just every signer given a different number. At most 5 groups of at most 4 signers each — full reference and the OR shape: Ask for signatures in a specific order.
expirationDateyour organisation's default (30 days) A specific instant, strictly in the future, at most one year out. Left out, the envelope expires after your organisation's default expiry, 30 days unless you changed it in Settings. The invitation and every reminder say the day, time and time zone the link stops working, in your organisation's locale; the API answers in UTC. There is deliberately no "never" option — past that ceiling the evidence around who held a mailbox has decayed enough that re-sending is the honest move, not extending.
reminderFrequencyHoursyour organisation's default (48) 0 turns reminders off. Above zero, whoever must currently sign is nudged every this many hours from when they were notified. A reminder reissues that signer's link, the same as a resend does, because the plaintext of an old link is never kept anywhere to send again.
cc / bcc[] Plain addresses that are told about the envelope but never asked to sign. Full reference: Cc and bcc on an envelope.
languageyour organisation's language "en" or "it". Selects which language every mail this envelope sends is written in, and which of your two branded footer texts it carries.
callbackUrl.onSuccess / onError / onEventnull Where we POST when the envelope closes — completed to the first, declined/expired/cancelled to the second. Must be https and resolve to a public address. Each request carries X-Signer-Signature: sha256=<hmac> over the raw body, keyed with your tenant's own secret — verify it. The body also carries an event block whose id is stable across retries, so a repeated delivery can be ignored rather than processed twice — see Tracing a call. Left null, nothing is sent and polling works exactly as before. With callbackUrl.env, the two values are paths appended to that callback environment's base URL and the callback carries its authentication (Bearer token, API key or OAuth2); paths without env go to your default environment — callback environments. onEvent is optional and separate: it receives recipient.replaced when a signer is replaced, signed and retried like the other two, which keep saying only how the envelope ended (Being told when a signer is replaced).

Writing the message

The message is plain text, the same in the API, in a flow's signature and in the Default message of Settings (used when a request does not send one). Every mail goes out twice in one: an HTML part, and a plain-text part for the mail programs that show only text. What you write becomes both:

You writeHTML partText part
**clause 4**clause 4, in bold clause 4, without the asterisks
a line break (\n in the JSON)a new linea new line
an empty line (\n\n)a new paragraphan empty line
https://acme.example/terms the address, written out and clickable (a full stop right after it ends the sentence, not the link)the address
<b>, [text](url), *one* shown exactly as typedshown exactly as typed
  • Bold is two asterisks on each side, on one line, at most 60 characters, with no space just inside the marks: **by 30 June** is bold, ** by 30 June ** is printed as typed. It is the only formatting there is: no italics, colours or fonts, and no HTML.
  • A link is shown as its address; there is no way to give it other words. Keep it inside a sentence (The terms are at https://acme.example/terms.): a paragraph that is only an address is drawn as a button, and the mail already has one, the signing button.
  • Do not indent every line of a paragraph: an indented block is shown as a code, in a box.
"message": "Please read **clause 4** before signing.\n\nQuestions? Write to legal@acme.example."

The ** marks belong to mail messages only. In a document, bold comes from the template itself: Writing a template.

Each signer also takes a validationMode: "sign" (default — a mark plus an explicit slide-to-confirm gesture, no code at all), "email" (a one-time code emailed to the signer, the flow this product started with), or "sms" (a one-time code by text message — requires a "phone" field on that signer). An SMS code is a service your organisation buys, each SMS the gateway accepts costs two credits, and where it cannot be delivered the envelope is refused at creation with SMS_VALIDATION_UNAVAILABLE: How do I choose how a signer proves it's them?

Each signer may also declare a signatureMeaning — what their signature means, which 21 CFR Part 11 §11.50(c) asks a signed record to state alongside the name and the time. One of "approved", "reviewed", "authored", "verified" or "responsibility_accepted"; free text is refused, because a meaning nobody can compare against anything is not one. It is per signer, not per envelope: on a real document one person approves and another acknowledges.

Omit it and nothing is printed. That is deliberate rather than a default worth setting: most documents are not signed under a regulated procedure, and stamping “Approved” on one nobody said that about would invent the very fact the rule exists to record. When it is set it appears under the mark, on the certificate of completion, and in the signatureMeaning field of every recipient the API and the evidence bundle return.

Retrying safely

Send an Idempotency-Key on every POST in this API — create, send, resend, replace, cancel. Send is the one that matters most: send is the billable moment, so a retry after a timeout charges you again and emails every signer a second time — the cost lands on your invoice and on your counterparty’s patience at once.

POST /api/v1/sign/envelopes/7a04…/send
Authorization: Bearer lpdc_…
Idempotency-Key: contract-2026-114

The envelope’s own state is a second guard — sending an already-sent envelope is refused — but that refusal is an error, and a caller retrying a request it never got an answer to deserves the answer rather than a complaint. With a key it gets the answer.

Drafting takes one too. Nothing is charged for a draft, and a retried one still leaves a second envelope behind that somebody has to clean up by hand.

So does resending: a resend is not a second signature and costs nothing, but a signer who gets the same link twice thirty seconds apart is being confused for no reason. And so does replacing a signer: a retried replace that was not recognised would be refused, because the slot is no longer the old person's.

And cancelling, which is the case people do not expect. A cancel is harmless twice — and the second answer is a refusal, because the envelope is no longer cancellable. If you never saw the first answer you would read that refusal as the cancel not having happened, which is exactly backwards.

The full rules, the three outcome codes and the 24-hour window: Retrying safely.

The response

Every call in this API — create, send, resend, cancel, read — answers the same envelope, with the envelope itself under output.sign:

{
  "output": {
    "status": "pending",
    "compose": null,
    "sign": {
      "envelopeId": "7a04...",
      "status": "sent",
      "level": "SES",
      "seal": false,
      "timestamp": false,
      "allowDelegation": false,
      "expiresAt": "2027-09-20T20:32:43Z",
      "documentId": "9b21...",
      "sourceSha256": "cdc82a...",
      "signedSha256": null,
      "signedDocumentUrl": null,
      "padesProfile": null,
      "recipients": [
        { "id": "c81e...", "email": "anna@acme.example", "status": "notified",
          "order": 0, "validationMode": "sign", "signedAt": null }
      ]
    },
    "flow": null,
    "credits": { "used": 15, "left": 1232 },
    "warnings": [],
    "error": null
  }
}
KeyMeaning
output.status"pending" while the envelope can still change, "succeeded" once every signer has signed, "failed" if it closed without that — declined, cancelled or expired. Poll on this; output.sign.status is the seven-word vocabulary underneath it.
output.sign.envelopeIdThe id every other call in this API takes. Named to match output.compose.documentId and output.flow.flowId, so that a flow's answer — which carries all three — never leaves you wondering which id is which.
seal, timestamp, allowDelegation, expiresAtYour settings, settled. Each is gated or capped by the organisation's own configuration, so an accepted request does not imply the value you sent. That is why there is no separate settings echo here: this is it.
output.creditsused is what this envelope has settled — the signature, the seal and the timestamp when you send it, then each SMS code as it leaves, so the read that finds it closed carries the definitive figure; items lists it line by line; left is your balance from the ledger now. Creating a draft charges nothing (used: 0). See Credits in every answer.
output.warningsWhere a gate that overruled you says so, with a stable code. One array per call — see Warnings.
recipientsWhat became of each person. You send them as signers, because in your request everybody signs; they come back as recipients, because this product will grow approvers and viewers that do not.

GET /api/v1/sign/envelopes answers { "envelopes": [ … ], "count": n }, where each item is exactly the object above — a collection is a collection of answers, so you parse one shape rather than two. Narrow it with ?status=, ?limit= (up to 200) and ?kind=production or ?kind=test: test and production are never one list unless you ask for all (the default, as for any value it does not know). The same kind works on GET /api/v1/flow.

Breaking change, 2026-09-20. These responses used to be the envelope flat at the root, with id rather than envelopeId and a warnings array inside the envelope. The keys moved; they were not duplicated. The same release gave /api/v1/compose and /api/v1/flow the identical envelope, which is the point: the section is named after the URL you called.

When the person you asked is not the right person

A signer can pass the request on from their own link. Theirs stops working immediately, the new person is sent one, and the certificate of completion names who handed it over — a document signed by somebody the sender never named is precisely the fact a dispute turns on, so it is stated in words rather than left in a table.

It is off unless the organisation turns it on, in Settings. With it on, a request may ask for less:

"settings": { "allowDelegation": false }

The organisation's setting is a ceiling, not a default. If it forbids delegation, asking for it does not fail the call — the envelope is created without it and the answer carries DELEGATION_NOT_ENABLED. Read allowDelegation back from the response rather than assuming what you sent was used.

Once a slot has changed hands, the recipient in the response carries delegatedFrom with the previous holder, when it happened and why. A poller that only reads email would otherwise report the wrong person as the signer.

Refused, each for its own reason: passing it back to the same address; passing it to somebody already signing this document, which would ask them to sign twice and, in an ordered envelope, make them wait for themselves; and more than three hops, because each one kills a link and sends an email.

Where a field's coordinates go: Where the signature goes on the page. What a signer sees, and the evidence trail: What the signer sees. Legal status of what this produces: Signature levels: SES, AES, QES.