Developers
API reference
A small REST API over the same domain the application uses. Every data response is JSON (downloads return PDF bytes), every timestamp is ISO 8601 with an explicit offset, and every id is a readable prefixed string.
Authentication
Create a key under Settings → API keys. The full key is shown once, at creation; only a hash is stored, so a lost key must be replaced rather than recovered.
curl https://cursiv.co/api/v1/envelopes \
-H "Authorization: Bearer csv_live_xxxxxxxxxxxxxxxxxxxx"Legacy keys with empty scopes allow only read, prepare and template read. Narrow access with envelopes:read, envelopes:write, or templates:read. Contact writes always require explicit contacts:write and an active permitted workspace member. Document brand writes require explicit document-brands:write and an active owner/admin.
Prepare an unsent draft
Start with GET /api/v1/workspace and GET /api/v1/me/profile. The represented company, current human and API key label are separate. Company legal/trading names, registration, VAT details, address and notice email are recorded claims, not verified identity. A recorded title or workspace role does not verify authority to bind a company.
Use a template, upload a file through /api/v1/envelopes/from-document, or submit finished wording through /api/v1/envelopes/from-contract. The latter is also MCP tool create_envelope_from_contract. Values must already be written into the text. Structured signature blocks map roles to recipients and let the server derive field positions. Both creation routes leave the envelope unsent.
/api/v1/envelopes/from-contract{
"subject": "Consultancy agreement — Example Company",
"externalId": "crm-example-8841-contract-v1",
"documentName": "Consultancy agreement",
"contractMarkdown": "# Consultancy agreement\n\nExample Company appoints Ada Example to provide the services agreed in the attached schedule.\n\n```signature\nConsultant | Ada Example | In personal capacity\n```",
"recipients": [
{
"name": "Ada Example",
"email": "ada@example.test",
"type": "signer"
}
],
"signatories": [
{
"role": "Consultant",
"recipient": 0
}
]
}Select documentBrandId from GET /api/v1/document-brands or MCP list_document_brands. Document printing profiles are separate from email branding: they configure a PNG/JPEG logo, supported body font (Helvetica, Times or Courier), hex colours, page margins, header, footer and printed company details. No arbitrary logo/font URL is fetched. Profiles can be created and updated through the listed routes or MCPcreate_document_brand/update_document_brand. Updates require the numeric profile expectedVersion and do not change existing rendered documents.
Read placement warnings and preview the draft before sending. ReusingexternalId returns the existing envelope; it does not apply corrections. Check the current version before editing. Send is a separate action that delivers invitations and consumes a credit. Signing, consent and ratification remain human actions; no API or MCP tool can perform them for a recipient.
Read /api/v1/envelopes/{id}/readiness for the draft version, problems, warnings, readyToSend and safeToLeaveUnsent. Scheduled drafts are not safe to leave unsent; cancel the schedule first./api/v1/envelopes/{id}/preview returns a watermarked PDF with field overlays and creates no signature or consent. MCP tools areget_envelope_readiness and preview_envelope.
PATCH /api/v1/envelopes/{id}/draft orcorrect_envelope_draft requires the latest stringexpectedVersion. It accepts settings, edits to existing recipients by id, replacement document bytes with anchors, or replacement anchor fields. Replacing a document removes its old fields; supplied anchors replace anchor-derived fields. Reload after 409 version_conflictand reconcile with the open editor. Corrections never send.
Search contacts with GET /api/v1/contacts?q=. Creating and updating contacts requires explicit contacts:write, even for a legacy key with empty scopes. New contacts are private unless shared:true is requested. Updates require expectedVersion and ownership. On duplicate/version conflict, re-read visible contacts and reconcile. StructuredcompanyDetails stores legal/trading identity separately from the legacy company label. Contact usage counters are not business activity reports.
Fetch the running capability guide for the preparation workflow and /api/mcp tool schemas. Audit attribution varies by operation; not every API write is an agent audit event.
All REST routes
/api/v1/agreements/{envelopeId}/reviseVersioned wording, recipient, field and settings correction with comparison
/api/v1/agreements/preparePrepare a checked unsent agreement with required externalId
/api/v1/bootstrapCompany, human, branding, defaults, permissions and credits in one read
/api/v1/capabilitiesPublic Markdown or ?format=json discovery
/api/v1/clauses/{id}/approveOwner/admin approves this exact clause version
/api/v1/clauses/{id}/archiveArchive with expectedVersion
/api/v1/contacts/{contactId}Read a visible contact
/api/v1/contacts/{contactId}Update your contact with expectedVersion; contacts:write required
/api/v1/contactsSearch visible saved contacts with ?q=
/api/v1/contactsCreate a private contact by default; contacts:write required
/api/v1/document-brands/{documentBrandId}Read document printing profile and version
/api/v1/document-brands/{documentBrandId}Update printing profile with expectedVersion
/api/v1/document-brandsList document printing profiles, separate from email brands
/api/v1/document-brandsCreate printing profile; explicit document-brands:write and owner/admin required
/api/v1/document-typesActive workspace filing classifications
/api/v1/documents/convertConvert finished Markdown or supported file bytes to PDF without sending
/api/v1/entities/{id}/archiveArchive with expectedVersion
/api/v1/envelopes/{envelopeId}/documentsMetadata or download=combined, certificate, or document id
/api/v1/envelopes/{envelopeId}/draftCorrect an unsent draft with expectedVersion; never sends
/api/v1/envelopes/{envelopeId}/evidenceAudit, consent, seal and available transparency receipt with salt
/api/v1/envelopes/{envelopeId}/previewRead a draft PDF with field overlays; optional documentId
/api/v1/envelopes/{envelopeId}/readinessRead validation problems, warnings and version without sending
/api/v1/envelopes/{envelopeId}/remindSend reminders subject to rate limits
/api/v1/envelopes/{envelopeId}/reviewFreeze document previews, recipients, proposed invitations and costs
/api/v1/envelopes/{envelopeId}/reviews/{reviewId}/documents/{documentId}GET envelopes within the workspace; see conversational workflow schemas
/api/v1/envelopes/{envelopeId}/revisionsRead saved revision snapshots or compare fromVersion and toVersion
/api/v1/envelopes/{envelopeId}Envelope, recipients, documents and fields
/api/v1/envelopes/{envelopeId}Legacy draft metadata and void; sending requires review and send-reviewed
/api/v1/envelopes/{envelopeId}Delete a draft or closed envelope when permitted
/api/v1/envelopes/{envelopeId}/sendDeprecated: rejects with review_required; use review then send-reviewed
/api/v1/envelopes/{envelopeId}/send-reviewedSend exactly reviewId with idempotencyKey; explicit envelopes:send required
/api/v1/envelopes/{envelopeId}/validateRead draft readiness and costs without changing the agreement
/api/v1/envelopes/{envelopeId}/voidVoid with a reason
/api/v1/envelopes/from-contractRender finished contract text with structured signature blocks; draft only
/api/v1/envelopes/from-documentUpload a document and place fields by anchors; draft only
/api/v1/envelopesSearch and filter envelopes
/api/v1/envelopesCreate from a template; send:false for review
/api/v1/me/profileCurrent human profile and recorded authority claims
/api/v1/operations/{resource}Read reports, filing, webhooks, deliveries, events, workspace, information-requests or export; POST actions requires scoped operationId
/api/v1/operations/{resource}Read reports, filing, webhooks, deliveries, events, workspace, information-requests or export; POST actions requires scoped operationId
/api/v1/signing-arrangements/{id}/archiveArchive with expectedVersion
/api/v1/templates/{id}/archiveArchive with expectedVersion
/api/v1/templates/{id}GET templates within the workspace; see conversational workflow schemas
/api/v1/templates/{id}PATCH templates within the workspace; see conversational workflow schemas
/api/v1/templates/libraryRead versioned managed templates
/api/v1/templatesPOST templates within the workspace; see conversational workflow schemas
/api/v1/templatesTemplates, roles and field keys
/api/v1/workspaceWorkspace, company profile, current user, scopes and credits
Prepare, validate, preview, send
Start with GET /api/v1/bootstrap for separate company and person profiles, document and email brands, defaults, permissions and credits. Use prepare_agreement or POST /api/v1/agreements/prepare with a stable externalId.
Correct wording, recipients, anchors or settings with revise_draft and expectedVersion. Validate without writes, then create a stored review through POST /api/v1/envelopes/{id}/review. Review includes immutable PDF previews, signing order, proposed emails and cost.
Only when instructed, call send_reviewed_agreement or POST /api/v1/envelopes/{id}/send-reviewed with reviewId and idempotencyKey. Explicit envelopes:send is required. Changed content, settings, recipients, email brand or cost invalidates the review. Legacy send routes refuse. Provider acceptance, delivery and signing are separate states.
Templates, clauses, legal entities and signing arrangements have versioned resources. Operations expose missing-information forms, reports, filing, completed export bundles, webhooks and restricted administration. Forms return links without sending email; submitted details remain unverified. Consult the capability guide and MCP tools/list for current input schemas.
Envelopes
/api/v1/envelopesLists envelopes, newest activity first. Query parameters: status (comma separated), q (searches subject and recipients), kind, limit (max 100), offset.
/api/v1/envelopesCreates an envelope from a template. Pass send: false to leave it unsent. Sending requires a stored review and an explicit sending scope; send: true is rejected.
curl -X POST https://cursiv.co/api/v1/envelopes \
-H "Authorization: Bearer $I60_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "env_m4t2p9x8kq3wz1",
"subject": "Master Services Agreement",
"externalId": "crm-deal-8841",
"roles": [
{ "roleName": "Client", "name": "Ada Lovelace", "email": "ada@example.com" },
{ "roleName": "Provider", "name": "Grace Hopper", "email": "grace@acme.test" }
],
"fieldValues": { "contract_value": "24500.00" },
"send": false
}'Idempotency. Supply externalId and a repeated call returns the original envelope with "idempotent": true rather than creating a duplicate. Changed payloads return409 idempotency_conflict; a concurrent request can return409 creation_in_progress.
/api/v1/envelopes/{id}Returns the envelope with its documents, recipients and fields — including each recipient’s status, IP address and timestamps.
/api/v1/envelopes/{id}Metadata edits accept optional expectedVersion; omission retains legacy unversioned behavior. Prefer the required-version/draft route for preparation corrections. Send a draft with { "status": "sent" }, or void an in-flight envelope with { "status": "voided", "voidReason": "..." }. Voiding invalidates every outstanding signing link immediately.
/api/v1/envelopes/{id}Permanently deletes a draft or closed envelope, along with its documents. Completed envelopes cannot be deleted — void first.
Templates
/api/v1/templatesLists templates with their roles and fieldKeys, so an integration can discover the exact role names and data keys to send rather than hard-coding them.
Documents
/api/v1/envelopes/{id}/documentsMetadata includes the SHA-256 of the normalized stored source PDF. Ingestion re-saves PDFs with object streams disabled; original upload bytes need not match. The sealed combined bundle has a separate digest. Add ?download=combined for the flattened signed bundle, ?download=certificate for the Certificate of Completion, or ?download=<documentId> for one source file.
Before completion, combined downloads are in-progress previews and a generated certificate is provisional, not proof of completion. Completed combined downloads use the stored sealed bytes. Imported records have no certificate (409 no_certificate); purged or unavailable files return 410. These downloads accept an API key withenvelopes:read.
Evidence record
/api/v1/envelopes/{id}/evidenceThe machine-readable record of the whole transaction: per-document hashes, every audit event with its position in the hash chain, each signer’s authentication and consent (including the hash of the exact disclosure text they agreed to), and the document seal.
When present, transparency.salt supports independent verification using the sealed bundle and public log proofs. A null transparency receipt does not establish independent verification. The response itself carries recordSha256 so it can be quoted by digest.
Webhooks
Add an endpoint under Settings → Webhooks. Deliveries are queued, not inline, so a slow endpoint never delays a signer. Failures retry with exponential backoff for roughly six attempts.
Events
envelope.sentenvelope.deliveredenvelope.completedenvelope.declinedenvelope.voidedenvelope.expiredrecipient.completedrecipient.declinedrecipient.deliveredEvery request carries x-cursiv-timestamp and x-cursiv-signature. Verify by recomputing an HMAC over `${timestamp}.${rawBody}` and comparing in constant time. Reject anything older than five minutes.
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verify(req, rawBody, secret) {
const timestamp = req.headers['x-cursiv-timestamp']
const signature = req.headers['x-cursiv-signature'] // "sha256=<hex>"
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
const expected =
'sha256=' +
createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signature)
return a.length === b.length && timingSafeEqual(a, b)
}Errors
Errors return a JSON body with a stable code and a message written for a person reading a log.
{
"error": {
"code": "validation_failed",
"message": "The request body did not validate.",
"details": [
{ "path": "roles.0.email", "message": "Invalid email" }
]
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. |
| 401 | missing_credentials | No bearer token was sent. |
| 401 | invalid_credentials | The key is unknown or revoked. |
| 403 | insufficient_scope | The key lacks the required scope. |
| 404 | not_found | No such resource in this workspace. |
| 409 | not_editable | The envelope is past the point of that change. |
| 410 | purged | Documents were removed under the retention policy. |
| 422 | validation_failed | The body did not validate. |
| 422 | invalid_request | The request is not valid for this envelope. |
| 500 | internal_error | Something went wrong on our side. |