Cursiv

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.

POST/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

PATCH/api/v1/agreements/{envelopeId}/revise

Versioned wording, recipient, field and settings correction with comparison

POST/api/v1/agreements/prepare

Prepare a checked unsent agreement with required externalId

GET/api/v1/bootstrap

Company, human, branding, defaults, permissions and credits in one read

GET/api/v1/capabilities

Public Markdown or ?format=json discovery

POST/api/v1/clauses/{id}/approve

Owner/admin approves this exact clause version

POST/api/v1/clauses/{id}/archive

Archive with expectedVersion

GET/api/v1/contacts/{contactId}

Read a visible contact

PATCH/api/v1/contacts/{contactId}

Update your contact with expectedVersion; contacts:write required

GET/api/v1/contacts

Search visible saved contacts with ?q=

POST/api/v1/contacts

Create a private contact by default; contacts:write required

GET/api/v1/document-brands/{documentBrandId}

Read document printing profile and version

PATCH/api/v1/document-brands/{documentBrandId}

Update printing profile with expectedVersion

GET/api/v1/document-brands

List document printing profiles, separate from email brands

POST/api/v1/document-brands

Create printing profile; explicit document-brands:write and owner/admin required

GET/api/v1/document-types

Active workspace filing classifications

POST/api/v1/documents/convert

Convert finished Markdown or supported file bytes to PDF without sending

POST/api/v1/entities/{id}/archive

Archive with expectedVersion

GET/api/v1/envelopes/{envelopeId}/documents

Metadata or download=combined, certificate, or document id

PATCH/api/v1/envelopes/{envelopeId}/draft

Correct an unsent draft with expectedVersion; never sends

GET/api/v1/envelopes/{envelopeId}/evidence

Audit, consent, seal and available transparency receipt with salt

GET/api/v1/envelopes/{envelopeId}/preview

Read a draft PDF with field overlays; optional documentId

GET/api/v1/envelopes/{envelopeId}/readiness

Read validation problems, warnings and version without sending

POST/api/v1/envelopes/{envelopeId}/remind

Send reminders subject to rate limits

POST/api/v1/envelopes/{envelopeId}/review

Freeze document previews, recipients, proposed invitations and costs

GET/api/v1/envelopes/{envelopeId}/reviews/{reviewId}/documents/{documentId}

GET envelopes within the workspace; see conversational workflow schemas

GET/api/v1/envelopes/{envelopeId}/revisions

Read saved revision snapshots or compare fromVersion and toVersion

GET/api/v1/envelopes/{envelopeId}

Envelope, recipients, documents and fields

PATCH/api/v1/envelopes/{envelopeId}

Legacy draft metadata and void; sending requires review and send-reviewed

DELETE/api/v1/envelopes/{envelopeId}

Delete a draft or closed envelope when permitted

POST/api/v1/envelopes/{envelopeId}/send

Deprecated: rejects with review_required; use review then send-reviewed

POST/api/v1/envelopes/{envelopeId}/send-reviewed

Send exactly reviewId with idempotencyKey; explicit envelopes:send required

GET/api/v1/envelopes/{envelopeId}/validate

Read draft readiness and costs without changing the agreement

POST/api/v1/envelopes/{envelopeId}/void

Void with a reason

POST/api/v1/envelopes/from-contract

Render finished contract text with structured signature blocks; draft only

POST/api/v1/envelopes/from-document

Upload a document and place fields by anchors; draft only

GET/api/v1/envelopes

Search and filter envelopes

POST/api/v1/envelopes

Create from a template; send:false for review

GET/api/v1/me/profile

Current human profile and recorded authority claims

GET/api/v1/operations/{resource}

Read reports, filing, webhooks, deliveries, events, workspace, information-requests or export; POST actions requires scoped operationId

POST/api/v1/operations/{resource}

Read reports, filing, webhooks, deliveries, events, workspace, information-requests or export; POST actions requires scoped operationId

POST/api/v1/signing-arrangements/{id}/archive

Archive with expectedVersion

POST/api/v1/templates/{id}/archive

Archive with expectedVersion

GET/api/v1/templates/{id}

GET templates within the workspace; see conversational workflow schemas

PATCH/api/v1/templates/{id}

PATCH templates within the workspace; see conversational workflow schemas

GET/api/v1/templates/library

Read versioned managed templates

POST/api/v1/templates

POST templates within the workspace; see conversational workflow schemas

GET/api/v1/templates

Templates, roles and field keys

GET/api/v1/workspace

Workspace, 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

GET/api/v1/envelopes

Lists envelopes, newest activity first. Query parameters: status (comma separated), q (searches subject and recipients), kind, limit (max 100), offset.

POST/api/v1/envelopes

Creates 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.

GET/api/v1/envelopes/{id}

Returns the envelope with its documents, recipients and fields — including each recipient’s status, IP address and timestamps.

PATCH/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.

DELETE/api/v1/envelopes/{id}

Permanently deletes a draft or closed envelope, along with its documents. Completed envelopes cannot be deleted — void first.

Templates

GET/api/v1/templates

Lists 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

GET/api/v1/envelopes/{id}/documents

Metadata 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

GET/api/v1/envelopes/{id}/evidence

The 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.delivered

Every 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" }
    ]
  }
}
StatusCodeMeaning
400invalid_jsonThe body is not valid JSON.
401missing_credentialsNo bearer token was sent.
401invalid_credentialsThe key is unknown or revoked.
403insufficient_scopeThe key lacks the required scope.
404not_foundNo such resource in this workspace.
409not_editableThe envelope is past the point of that change.
410purgedDocuments were removed under the retention policy.
422validation_failedThe body did not validate.
422invalid_requestThe request is not valid for this envelope.
500internal_errorSomething went wrong on our side.
API reference · Cursiv