Skip to content
Browse developers

API reference

Every endpoint, generated from the same contract the API serves, so it cannot fall behind.

Last updated

Generated from the CecurSign API contract on : 243 operations in 33 groups, from version 1.0 of the contract. For the machine-readable document, call GET /v1/openapi.json with a key that has read access.

Before you read this

Every path below already carries the /v1 prefix, so a request goes to https://cecursign.io/api plus the path. The full base URL is https://cecursign.io/api/v1.

Most endpoints accept an API key as a bearer token. Some are reachable from a signed-in session in the app, and a few need no credential at all: the signing ceremony and public proposal acceptance are opened by the link the recipient was sent. Each entry states which applies. For how to obtain and send a key, read Authentication and API keys.

X-Acting-User

Accepted on every endpoint, so it is stated once here rather than repeated on every one of the 243 below. On an API-key request, the person at the firm whose authority this call carries: a CecurSign user id, or the email address of a user in the same account. Verified against a live user before the request runs. An unknown or cross-account value is rejected with 400, never silently ignored. When present, the audit trail and the certificate of completion name that person alongside the integration; when absent, the act is recorded against the API key alone. Required on keys created with `requireActingUser`, and required by POST /v1/envelopes, which must record an author.

Groups

Analytics

GET/v1/activityTenant activity feed

Reverse-chronological, cursor-paginated, merged from the real signing audit trail and the proposal event log. Deliberately returns NO total: a count over a cursor-paginated union is either a second expensive query or a lie, and the page this replaces displayed a lie (a count over fifty envelopes, labelled "Total Events"). High-frequency per-field and per-email audit rows are filtered out of the feed but remain in the envelope audit trail.

Opens with: API key

Parameters

  • cursorstringqueryOpaque cursor from the previous page. Encodes the last row timestamp and id, so a feed that gains rows while being read does not skip or repeat.
  • limitnumberquery
  • subjectsigning | proposalsqueryRestrict to one subject. Omitted means both — and for a caller without proposals, both means signing, because the proposal half is never queried for them.

Responses

  • 200ActivityFeedResponseDto
GET/v1/analytics/exportExport analytics data

Exports analytics data as CSV or JSON for the specified period.

Opens with: API key

Parameters

  • startDatestringqueryStart date (ISO 8601). Defaults to 30 days ago.
  • endDatestringqueryEnd date (ISO 8601). Defaults to now.
  • formatcsv | jsonqueryExport format: csv or json. Defaults to csv.

Responses

  • 200Exported analytics data
  • 403Feature not available on current plan
GET/v1/analytics/funnelGet conversion funnel

Returns the conversion funnel: Sent -> Viewed -> Signed -> Completed with drop-off percentages.

Opens with: API key

Parameters

  • startDatestringqueryStart date (ISO 8601). Defaults to 30 days ago.
  • endDatestringqueryEnd date (ISO 8601). Defaults to now.

Responses

  • 200FunnelResponseDtoConversion funnel data
  • 403Feature not available on current plan
GET/v1/analytics/peak-timesGet peak signing times

Returns signing activity grouped by day-of-week and hour-of-day.

Opens with: API key

Parameters

  • startDatestringqueryStart date (ISO 8601). Defaults to 30 days ago.
  • endDatestringqueryEnd date (ISO 8601). Defaults to now.
  • tzOffsetMinutesnumberqueryThe caller's Date#getTimezoneOffset() in minutes (UTC+1 sends -60). Defaults to 0 (UTC).

Responses

  • 200PeakTimesResponseDtoPeak signing times data
  • 403Feature not available on current plan
GET/v1/analytics/summaryGet analytics summary

Returns high-level KPIs including total sent, completion rate, average time-to-sign, and more.

Opens with: API key

Parameters

  • startDatestringqueryStart date (ISO 8601). Defaults to 30 days ago.
  • endDatestringqueryEnd date (ISO 8601). Defaults to now.

Responses

  • 200SummaryResponseDtoAnalytics summary
  • 403Feature not available on current plan
GET/v1/analytics/team-performanceGet team performance metrics

Returns per-user completion rates, volume, and average time-to-sign. Requires an OWNER or ADMIN session, matching GET /v1/team/members which returns the same roster.

Opens with: API key

Parameters

  • startDatestringqueryStart date (ISO 8601). Defaults to 30 days ago.
  • endDatestringqueryEnd date (ISO 8601). Defaults to now.

Responses

  • 200TeamPerformanceResponseDtoTeam performance data
  • 403Insufficient role, or feature not on current plan
GET/v1/analytics/time-to-signGet time-to-sign distribution

Returns time-to-sign distribution in buckets with percentiles (median, average, p90).

Opens with: API key

Parameters

  • startDatestringqueryStart date (ISO 8601). Defaults to 30 days ago.
  • endDatestringqueryEnd date (ISO 8601). Defaults to now.

Responses

  • 200TimeToSignResponseDtoTime-to-sign distribution
  • 403Feature not available on current plan

API Keys

GET/v1/auth/api-keysList all API keys

Requires an OWNER or ADMIN session.

Opens with: API key

Responses

  • 200ApiKeyListResponseDtoList of API keys
  • 401Not authenticated
  • 403An API key (a key cannot manage keys), or a role below ADMIN
POST/v1/auth/api-keysCreate a new API key

Requires an OWNER or ADMIN session. A key is tenant-wide and unrestricted by department, so minting one is an escalation for anyone who is not already company-wide.

Opens with: API key

Request body: CreateApiKeyDto

  • expiresAtstringExpiration date for the API key (optional)
  • namestringRequiredName for the API key (for identification)
  • permissionsREAD | WRITE[]Permissions for the API key
  • requireActingUserbooleanRefuse any write on this key that does not name the person it is acting for, via the `X-Acting-User` header (a CecurSign user id or the email address of a user in this account). Recommended for partner integrations that act on behalf of named staff: without it, a machine-driven send is recorded against the key alone, and the audit record is append-only, so the attribution cannot be added afterwards. Defaults to false, which keeps existing integrations working unchanged.

Responses

  • 201CreatedApiKeyResponseDtoAPI key created successfully
  • 400Invalid input
  • 401Not authenticated
  • 403An API key (a key cannot mint a key), a role below ADMIN, or API access not available on the current plan
GET/v1/auth/api-keys/{id}Get API key details

Requires an OWNER or ADMIN session.

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200ApiKeyResponseDtoAPI key details
  • 401Not authenticated
  • 403An API key (a key cannot manage keys), or a role below ADMIN
  • 404API key not found
DELETE/v1/auth/api-keys/{id}Revoke an API key

Requires an OWNER or ADMIN session. Revoking is a denial of service against every integration the company runs off that key.

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200MessageResponseDtoAPI key revoked
  • 401Not authenticated
  • 403An API key (a key cannot manage keys), or a role below ADMIN
  • 404API key not found

Authentication

POST/v1/auth/forgot-passwordRequest password reset

Opens with: No credential.

Request body: ForgotPasswordDto

  • emailstringRequiredUser email address

Responses

  • 200MessageResponseDtoReset email sent if account exists
  • 429Too many password reset requests
POST/v1/auth/loginLogin with email and password

Opens with: No credential.

Request body: LoginDto

  • emailstringRequiredUser email address
  • passwordstringRequiredUser password

Responses

  • 200LoginResponseDtoLogin successful
  • 401Invalid credentials
  • 429Too many login attempts
POST/v1/auth/logoutLogout current session

Opens with: Signed-in session

Responses

  • 200MessageResponseDtoLogged out successfully
GET/v1/auth/meGet current user session, or why there is none

Opens with: Signed-in session

Responses

  • 200Returns current user, or authenticated:false with a reason
POST/v1/auth/me/resend-verificationResend the signed-in user's email-verification link

Opens with: Signed-in session

Responses

  • 200Accepted
POST/v1/auth/registerSelf-registration (deprecated, sign up at cecur.io)

Opens with: No credential.

Responses

  • 410Self-registration is no longer available
POST/v1/auth/resend-verificationResend email verification

Opens with: No credential.

Request body: ResendVerificationDto

  • emailstringRequiredUser email address

Responses

  • 200MessageResponseDtoVerification email sent if applicable
  • 429Too many verification email requests
POST/v1/auth/reset-passwordReset password with token

Opens with: No credential.

Request body: ResetPasswordDto

  • passwordstringRequiredNew password (min 8 chars, must include uppercase, number, and special character)
  • tokenstringRequiredPassword reset token from email

Responses

  • 200MessageResponseDtoPassword reset successful
  • 400Invalid or expired token
  • 429Too many password reset attempts
POST/v1/auth/verify-emailVerify email with token

Opens with: No credential.

Request body: VerifyEmailDto

  • tokenstringRequiredEmail verification token

Responses

  • 200VerifyEmailResponseDtoEmail verified
  • 400Invalid or expired token

Billing

GET/v1/billing/addonsVolume packs: what this tenant holds, and what their tier can hold

A pack is capacity bought once and spent only after the plan's own monthly allowance runs out, in whatever month that happens. The figures are ADDITIONS to the plan, not replacement caps.

Opens with: API key

Responses

  • 200AddOnsResponseDtoBalance, offers and grants
POST/v1/billing/addons/checkoutStart Stripe Checkout for a booster pack, via Cecur Home

Opens with: API key

Request body: StartAddOnCheckoutDto

  • addOnCodestringRequiredBooster code from CecurSign's catalogue
  • quantitynumberHow many packs. Defaults to 1.

Responses

  • 200AddOnCheckoutSessionResponseDtoCheckout session created; redirect the browser to `url`
  • 403The booster is not sold on this account's plan
  • 503Cecur Home could not be reached
POST/v1/billing/checkoutStart Stripe Checkout for a plan change, via Cecur Home

Opens with: API key

Request body: StartCheckoutDto

  • billingCyclemonthly | annualRequiredBilling cadence
  • confirmedImmediateChargebooleanSet when the customer has been shown the amount quoted by POST /billing/preview and confirmed it.
  • planINDIVIDUAL | PROFESSIONAL | BUSINESS | ENTERPRISERequiredPlan to move to

Responses

  • 200CheckoutSessionResponseDtoCheckout session created; redirect the browser to `url`
  • 503Cecur Home could not be reached
GET/v1/billing/entitlementsWhat this tenant can actually use, and why they might want a different plan

Opens with: API key

Responses

  • 200EntitlementsResponseDtoEntitlements
GET/v1/billing/invoicesList this account's Cecur invoices, via Cecur Home

Opens with: API key

Responses

  • 200BillingInvoicesResponseDto
GET/v1/billing/plansGet available plans with pricing (public)

Opens with: No credential.

Responses

  • 200PlansResponseDtoAvailable plans
POST/v1/billing/previewQuote what a plan change costs today, before making it

Opens with: API key

Request body: PreviewPlanChangeDto

  • billingCyclemonthly | annualRequiredBilling cadence being considered
  • planINDIVIDUAL | PROFESSIONAL | BUSINESS | ENTERPRISERequiredPlan being considered

Responses

  • 200PlanChangePreviewDtoWhat the change would do, and what it would cost including VAT
  • 503Cecur Home could not be reached
GET/v1/billing/subscriptionGet current plan and subscription details

Opens with: API key

Responses

  • 200SubscriptionDetailsDtoSubscription details
GET/v1/billing/usageGet current usage stats vs plan limits

Opens with: API key

Responses

  • 200UsageStatsDtoUsage statistics

Branding (Public)

Bulk Send

GET/v1/bulk-sendList bulk sends

Get all bulk send operations for your account

Opens with: API key

Responses

  • 200BulkSendListResponseDtoList of bulk sends
POST/v1/bulk-sendCreate a bulk send

Create and start a bulk send operation from a template

Opens with: API key

Request body: CreateBulkSendDto

  • csvRoleIdstringTemplate role ID that the uploaded recipients fill (the "variable" signer). Defaults to the first SIGNER role on the template when omitted.
  • namestringRequiredName for this bulk send batch
  • recipientsBulkSendRecipientInput[]RequiredList of recipients (max 500). Each one becomes its own envelope, mapped to the template role identified by csvRoleId.
  • roleAssignmentsBulkSendRoleAssignmentDto[]How the template's OTHER roles are filled on every envelope (fixed recipient or self-sign). Required for any template role that is not csvRoleId.
  • templateIdstringRequiredTemplate ID to use for envelopes

Responses

  • 201BulkSendResponseDtoBulk send created and queued
  • 400Invalid request
  • 404Template not found
GET/v1/bulk-send/{id}Get bulk send details

Get detailed status of a bulk send operation including recipients

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200BulkSendDetailResponseDtoBulk send details
  • 404Bulk send not found
POST/v1/bulk-send/{id}/cancelCancel a bulk send

Cancel a pending or in-progress bulk send operation

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200BulkSendResponseDtoBulk send cancelled
  • 400Cannot cancel
  • 404Bulk send not found

Contact

POST/v1/public/contactSend an enquiry from the public contact form

Emails the enquiry to the address that answers them and stores nothing. Public and unauthenticated: rate limited to 3 per 10 minutes per caller, with a honeypot field and a time-on-form check. Sends no acknowledgement to the enquirer, deliberately, so the endpoint cannot be used to mail an arbitrary address.

Opens with: No credential.

Request body: CreateContactEnquiryDto

  • companystringThe company they are enquiring for
  • elapsedMsnumberClient-measured time on the form, in milliseconds
  • emailstringRequiredWhere a reply should go
  • enquiryTypeGENERAL | SALES | SECURITY | EXISTING_CUSTOMERWhat the enquiry is about. Steers the subject line; grants nothing.
  • messagestringRequiredThe enquiry itself
  • namestringRequiredThe name the enquirer gave
  • websitestringAnti-spam honeypot. Must be empty. A filled value is accepted and discarded.

Responses

  • 202ContactEnquiryResponseDtoAccepted for delivery. Also returned for a submission the anti-abuse checks discarded, so an automated caller learns nothing about which check it failed.
  • 400Validation failure
  • 429Rate limited: try again shortly
  • 503The notification system is not configured or was unreachable. The message was NOT sent and the caller is told so.

Contacts

GET/v1/contactsList contacts

Get a paginated list of contacts with optional filtering

Opens with: API key

Parameters

  • pagenumberquery
  • pageSizenumberquery
  • searchstringquery
  • categorystringquery
  • favoritesOnlybooleanquery
  • includeArchivedbooleanqueryInclude archived contacts (excluded by default)
  • engagementLIVE | ENDING_SOON | LAPSED | WITH_CLIENT | UNATTENDEDqueryNarrow to one derived engagement bucket. Each bucket carries its own ordering — ENDING_SOON soonest-first, LAPSED/UNATTENDED most-recently-lapsed first, WITH_CLIENT longest-held first — because that is the only ordering that makes each one actionable and it is the one key every row in the bucket is guaranteed to have.

Responses

  • 200Returns paginated contacts list
POST/v1/contactsCreate contact

Create a new contact

Opens with: API key

Request body: CreateContactDto

  • addressstringPostal address (resolves the {{clientAddress}} proposal merge token)
  • categoriesstring[]Category tags for the contact
  • companystringCompany name
  • emailstringRequiredContact email address
  • isFavoritebooleanMark as favorite
  • namestringContact name
  • notesstringNotes about the contact
  • phonestringContact phone number

Responses

  • 201Contact created
  • 409Contact with this email already exists
GET/v1/contacts/{id}Get contact details

Get a single contact with detailed stats

Opens with: API key

Parameters

  • idstringpathRequiredContact ID

Responses

  • 200Returns contact details
  • 404Contact not found
PATCH/v1/contacts/{id}Update contact

Update an existing contact

Opens with: API key

Parameters

  • idstringpathRequiredContact ID

Request body: UpdateContactDto

  • addressstringPostal address (resolves the {{clientAddress}} proposal merge token)
  • categoriesstring[]Category tags for the contact
  • companystringCompany name
  • emailstringContact email address
  • isFavoritebooleanMark as favorite
  • namestringContact name
  • notesstringNotes about the contact
  • phonestringContact phone number

Responses

  • 200Contact updated
  • 404Contact not found
  • 409Contact with this email already exists
DELETE/v1/contacts/{id}Delete or archive a contact

Deletes the contact outright when it has no proposal or envelope history. When it does, the contact is ARCHIVED instead — hidden from the default list and restorable — because Proposal.contactId is a required Restrict foreign key. Check `outcome` on the response to tell which happened.

Opens with: API key

Parameters

  • idstringpathRequiredContact ID

Responses

  • 200DeleteContactResponse
  • 404Contact not found
GET/v1/contacts/{id}/documentsGet every document this client has been sent

Derived from the envelopes the contact is a recipient of and the proposals whose engagement letter was rendered for them — there is no manual document-to-client link to go stale. This is where rendered engagement letters live now that they are excluded from the company-wide document library.

Opens with: API key

Parameters

  • idstringpathRequiredContact ID
  • pagenumberquery
  • pageSizenumberquery

Responses

  • 200Returns the client’s documents, newest first
  • 404Contact not found
GET/v1/contacts/{id}/envelopesGet contact envelope history

Get all envelopes associated with a contact

Opens with: API key

Parameters

  • idstringpathRequiredContact ID
  • pagenumberquery
  • pageSizenumberquery

Responses

  • 200Returns envelope history
  • 404Contact not found
POST/v1/contacts/{id}/favoriteToggle favorite

Toggle the favorite status of a contact

Opens with: API key

Parameters

  • idstringpathRequiredContact ID

Responses

  • 200Favorite status toggled
  • 404Contact not found
POST/v1/contacts/{id}/restoreRestore an archived contact

Clears the archived flag, returning the contact to the default list. No-op if it is not archived.

Opens with: API key

Parameters

  • idstringpathRequiredContact ID

Responses

  • 201ContactResponse
  • 404Contact not found
GET/v1/contacts/categoriesGet all categories

Get all unique categories used across contacts

Opens with: API key

Responses

  • 200Returns list of category names
GET/v1/contacts/companiesGet all companies

Get every distinct company name in use, de-duplicated case-insensitively. Backs the company autocomplete on the contact form.

Opens with: API key

Responses

  • 200Returns list of company names
GET/v1/contacts/favoritesGet favorite contacts

Get all contacts marked as favorites

Opens with: API key

Responses

  • 200Returns favorite contacts
POST/v1/contacts/importImport contacts from a parsed CSV

Accepts rows already parsed by the client (same contract shape as bulk-send). Send `dryRun: true` for the preview step — it reports per-row validation and whether each row creates or updates, without writing. Committing upserts by (tenant, email) rather than failing on duplicates.

Opens with: API key

Request body: ImportContactsDto

  • dryRunbooleanWhen true, validate and report what WOULD happen without writing anything. Powers the preview step.
  • rowsImportContactRowDto[]RequiredParsed CSV rows, in file order

Responses

  • 201ImportContactsResponse
GET/v1/contacts/out-of-contractClients out of contract with nothing in flight

Every accepted engagement has ended and no proposal is open. The renewal nudge already exists as an overnight email from the proposal sweeps; this is the same fact, in the app, on the day somebody can act on it. Most recently lapsed first. Archived clients are excluded, because archiving is how a company says the relationship is over.

Opens with: API key

Parameters

  • limitnumberqueryMax rows (default 5, cap 50)

Responses

  • 200OutOfContractResponse
GET/v1/contacts/recentGet recent recipients

Get recently used recipient emails from envelope history

Opens with: API key

Parameters

  • limitnumberqueryMax results (default 10)

Responses

  • 200Returns recent recipients
GET/v1/contacts/statsEngagement counts for the Clients list tile row

Live / Ending soon / Lapsed / With the client, derived from proposals and never stored. Honours search, category, favourites and archived so the tiles caption the same population the list shows; ignores the engagement filter itself.

Opens with: API key

Parameters

  • searchstringquery
  • categorystringquery
  • favoritesOnlybooleanquery
  • includeArchivedbooleanquery

Responses

  • 200ContactStatsResponse

Dashboard

GET/v1/dashboard/layoutGet the current user's Home layout

Returns the widget list in render order. A user who has never customised Home gets a default generated from their tenant's current entitlements, with `customised: false` and nothing written to the database.

Opens with: API key

Responses

  • 200DashboardLayoutResponseDto
PUT/v1/dashboard/layoutReplace the current user's Home layout

Takes the complete widget list in render order and replaces what was stored. Creates the row on first save. Unknown widget ids are rejected by validation; duplicates are dropped rather than rejected, so a client bug cannot cost the user the rest of an arrangement they just built.

Opens with: API key

Request body: UpdateDashboardLayoutDto

  • showQuickStartbooleanWhether the quick start row above the grid is shown. Optional so a client built before the flag existed keeps saving layouts; absent means shown.
  • widgetsHomeWidgetPlacementDto[]RequiredThe complete widget list, IN RENDER ORDER. A full replacement, not a patch: the client holds the whole arrangement while the user drags it around, and a partial update would need an ordering protocol to express "this moved above that". An EMPTY array is legal — a user is allowed to clear their Home, and the picker is how they put it back.

Responses

  • 200DashboardLayoutResponseDto
  • 400Unknown widget id, or too many widgets
DELETE/v1/dashboard/layoutReset the current user's Home layout to the product default

Deletes the saved arrangement and returns the freshly generated default. The user goes back to tracking entitlement changes automatically, exactly as before they customised.

Opens with: API key

Responses

  • 200DashboardLayoutResponseDto
GET/v1/dashboard/summaryHome dashboard summary

Counts and the attention list that Home renders above its recent lists. Deliberately NOT gated on the analyticsAccess plan feature: these are operational counts of the tenant's own work, not reporting, and Home is the first screen every user sees on every plan. The proposals block is omitted entirely — not zero-filled — for callers who cannot use proposals.

Opens with: API key

Parameters

  • tzOffsetMinutesnumberqueryThe caller's Date#getTimezoneOffset() in minutes (UTC+1 sends -60). Used only to decide which local day each timestamp falls in for the weekly chart. Defaults to 0 (UTC).

Responses

  • 200DashboardSummaryResponseDto

Departments

GET/v1/departmentsList the company’s departments

Active only by default. Open to any authenticated user: the web labels with these.

Opens with: API key

Parameters

  • includeInactivestringqueryRequired

Responses

  • 200DepartmentListResponse
POST/v1/departmentsCreate a department

Opens with: API key

Request body: CreateDepartmentDto

  • descriptionstring
  • namestringRequired

Responses

  • 201DepartmentResponse
  • 409A department with that name already exists
PATCH/v1/departments/{id}Rename, re-describe, or retire a department

Opens with: API key

Parameters

  • idstringpathRequired

Request body: UpdateDepartmentDto

  • descriptionstring
  • isActivebooleanSet false to retire a department without deleting its history.
  • namestring

Responses

  • 200DepartmentResponse
  • 400The default department cannot be deactivated
  • 404Department not found
DELETE/v1/departments/{id}Retire a department

Soft — sets isActive=false and keeps membership, so reactivating restores the previous state. A department that has issued work cannot be hard-deleted without deciding what happens to that work, which is not a decision a delete button should take.

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200DepartmentResponse
  • 400The default department cannot be deactivated
  • 404Department not found
GET/v1/departments/assignableDepartments the caller may file new work into

What the create-time picker offers. Narrower than GET /departments, which is every department in the company for labelling: this answers where THIS caller may put a new envelope or proposal, and is resolved by the same function the create endpoints validate against, so the picker cannot offer a choice the API would reject. When singleChoice is true the picker should not be rendered — there is no decision to make.

Opens with: API key

Responses

  • 200AssignableDepartmentsResponse
GET/v1/departments/meThe caller’s own visibility scope and departments

Returns the EFFECTIVE scope, not the stored column: OWNER and ADMIN always read TENANT. An API-key caller has no user and is tenant-wide, so it reports TENANT with no departments.

Opens with: API key

Responses

  • 200MyScopeResponse
GET/v1/departments/rosterEvery user in the company with their scope and departments

Deleted users are excluded; suspended users are not.

Opens with: API key

Responses

  • 200DepartmentRosterResponse
PUT/v1/departments/roster/{userId}Set one user’s visibility scope and department membership

Membership is replaced wholesale by departmentIds, so the stored state always matches what the admin had on screen. DEPARTMENT scope with no departments is rejected — it would produce a user who sees nothing, with no explanation.

Opens with: API key

Parameters

  • userIdstringpathRequiredThe user whose scope is being set

Request body: SetUserScopeDto

  • departmentIdsstring[]Replaces this user’s membership wholesale. Required and non-empty when visibilityScope is DEPARTMENT; ignored otherwise but still applied, so switching a user between DEPARTMENT and TENANT does not lose where they sit.
  • visibilityScopeOWN | DEPARTMENT | TENANTRequiredTENANT = the whole company (today’s behaviour for everyone). DEPARTMENT = only the departments listed below. OWN = only what this user created.

Responses

  • 200DepartmentRosterMemberResponse
  • 400Empty department list, or a department not in this company
  • 404No such user in this tenant

Document Categories

GET/v1/document-categoriesList document categories

The company-wide shelves for the document library. `documentCount` is scoped to what the caller can see and is intended for the management drawer on the Documents screen. Filter chips carry no count.

Opens with: API key

Responses

  • 200DocumentCategoryResponseDto[]
POST/v1/document-categoriesCreate a document category

Opens with: API key

Request body: CreateDocumentCategoryDto

  • colorstringHex colour for the chip. Omit for the neutral tone.
  • namestringRequiredCategory name, unique within the company
  • sortOrdernumberDisplay order, ascending

Responses

  • 201DocumentCategoryResponseDto
  • 409A category with this name already exists
PATCH/v1/document-categories/{id}Rename, recolour or reorder a category

Opens with: API key

Parameters

  • idstringpathRequired

Request body: UpdateDocumentCategoryDto

  • colorstringNew hex colour
  • namestringNew name
  • sortOrdernumberNew display order

Responses

  • 200DocumentCategoryResponseDto
  • 404Category not found
  • 409A category with this name already exists
DELETE/v1/document-categories/{id}Delete a category

Documents in it are NOT deleted — they return to "Uncategorised". No in-use guard, because nothing is lost by deleting a shelf.

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 204Deleted
  • 404Category not found

Document copies

GET/v1/copies/{token}What a copy link points at, and record that it was opened

Returns only what the reader needs in order to decide whether to open the document: the subject, who sent it, the firm, their note and when the link expires. No envelope id, no recipient list and no signer names: the reader was not a party to the document. The FIRST open of a link writes one audit event; later opens move a counter on the row.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredThe token from the emailed link

Responses

  • 200PublicCopyResponseDto
  • 404The link is not valid
  • 410The link was withdrawn, has expired, or the document is no longer available
GET/v1/copies/{token}/documentDownload the signed document behind a copy link

Served through our API and re-verified against the hash recorded for the document, like every other route that hands over a signed PDF. A mismatch is refused rather than served, because nobody can tell from here whether the file or the record is wrong.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredThe token from the emailed link

Responses

  • 200The signed PDF
  • 404The link is not valid
  • 410The link was withdrawn or has expired

Document packs

GET/v1/document-packsList the firm's document packs

A pack is a named set of supporting items a template can carry, so a firm sending its onboarding bundle picks one thing instead of five. Firm-wide, like the templates that point at them.

Opens with: API key

Responses

  • 200DocumentPackListResponseDto
POST/v1/document-packsCreate a document pack

Items follow exactly the same rules as a supporting document: a library document or an https link to the firm's own site, never both. A library document is verified against the caller's tenant and department scope.

Opens with: API key

Request body: CreateDocumentPackDto

  • descriptionstringA note to the colleague choosing between packs. Never shown to a recipient.
  • itemsDocumentPackItemInputDto[]The items, in the order the recipient will read them. May be empty.
  • namestringRequiredWhat the firm calls this pack. Unique within the firm.

Responses

  • 201DocumentPackResponseDto
  • 400An item gave both or neither source, or a non-https link
  • 404One of the documents was not found
  • 409A pack with this name already exists
GET/v1/document-packs/{packId}Read one document pack and its items

Opens with: API key

Parameters

  • packIdstringpathRequiredDocument pack id

Responses

  • 200DocumentPackResponseDto
  • 404Document pack not found
PATCH/v1/document-packs/{packId}Rename a pack, or replace its items

Sending `items` replaces the whole list; omitting it leaves the items alone. Editing a pack never changes an envelope or a proposal already created from it: the items were copied at instantiation, not referenced.

Opens with: API key

Parameters

  • packIdstringpathRequiredDocument pack id

Request body: UpdateDocumentPackDto

  • descriptionstringA note to the colleague choosing between packs
  • itemsDocumentPackItemInputDto[]The complete new item list, in order. Sent, it REPLACES every item on the pack; omitted, the items are left alone. Editing a pack never reaches an envelope or a proposal that has already been created from it: materialisation copies, it does not reference.
  • namestringWhat the firm calls this pack

Responses

  • 200DocumentPackResponseDto
  • 400An item gave both or neither source, or a non-https link
  • 404Document pack, or one of the documents, not found
  • 409A pack with this name already exists
DELETE/v1/document-packs/{packId}Delete a document pack

Templates pointing at it simply stop carrying a pack, and every envelope ever built from one keeps the copy it was given. No document is deleted.

Opens with: API key

Parameters

  • packIdstringpathRequiredDocument pack id

Responses

  • 204Deleted
  • 404Document pack not found
POST/v1/document-packs/{packId}/itemsAdd one item to a document pack

Opens with: API key

Parameters

  • packIdstringpathRequiredDocument pack id

Request body: AddDocumentPackItemDto

  • descriptionstringOne line of context, shown under the label
  • documentIdstringA document from this firm's library. Verified against the caller's tenant AND their department scope before it is accepted. Mutually exclusive with externalUrl.
  • externalUrlstringAn https URL the firm already publishes on its own site. Mutually exclusive with documentId. An external link is never tracked: the request goes to the firm's own server and never reaches us.
  • labelstringRequiredWhat the recipient sees. Not the filename: "Our privacy policy" reads better to a client than "privacy-policy-v4-FINAL.pdf".
  • ordernumberPosition in the list. Omit and it is appended to the end.

Responses

  • 201DocumentPackResponseDto
  • 400Both or neither source given, or a non-https link
  • 404Document pack or document not found
DELETE/v1/document-packs/{packId}/items/{itemId}Remove one item from a document pack

Opens with: API key

Parameters

  • packIdstringpathRequiredDocument pack id
  • itemIdstringpathRequiredPack item id

Responses

  • 200DocumentPackResponseDto
  • 404Document pack or item not found
PUT/v1/document-packs/{packId}/items/orderReorder a pack's items

Takes every item id on the pack, in the order they should appear. A partial list is refused rather than applied.

Opens with: API key

Parameters

  • packIdstringpathRequiredDocument pack id

Request body: ReorderDocumentPackItemsDto

  • idsstring[]RequiredEvery item id on this pack, in the order they should appear. Ids that do not belong here are refused rather than ignored.

Responses

  • 200DocumentPackResponseDto
  • 400The list is incomplete, or names something else
  • 404Document pack not found

Documents

GET/v1/documentsList all documents

Opens with: API key

Parameters

  • pagenumberqueryPage number (1-indexed)
  • pageSizenumberqueryPage size
  • statusPROCESSING | READY | ERROR | DELETEDqueryFilter by status
  • searchstringquerySearch by name
  • sortrecent | newest | oldest | name-asc | name-descqueryResult ordering. Exists because the pickers sort, and they now paginate server-side: sorting a single page in the browser reorders ten rows out of a hundred and calls it sorted. `recent` puts the documents that last went out first (most recent send first), then the never-sent ones newest first. It is what a picker wants at the top: the letter that goes out weekly, not whatever was uploaded last.
  • includeArchivedbooleanqueryInclude archived documents. Off by default — archiving exists to get superseded editions out of the way, so they have to be out of the way unless asked for.
  • categoryIdstringqueryFilter to one category. The literal string `none` returns UNCATEGORISED documents — a real chip in the UI, and not expressible as a UUID.

Responses

  • 200DocumentListResponseDtoList of documents
  • 401Unauthorized
POST/v1/documentsUpload a new document

Opens with: API key

Request body

  • filebinaryRequiredDocument file (PDF, DOCX, PNG, JPEG)
  • namestringCustom document name (optional)

Responses

  • 201UploadResponseDtoDocument uploaded successfully
  • 400Invalid file type or size
  • 401Unauthorized
GET/v1/documents/{id}Get document by ID

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 200DocumentResponseDtoDocument details
  • 404Document not found
PATCH/v1/documents/{id}Rename, archive or restore a document

Archiving hides a document from the library and both pickers. It does NOT detach it from envelopes or templates that already carry it, and a draft that already references it still sends.

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Request body: UpdateDocumentDto

  • archivedbooleanArchive (true) or restore (false). Archiving hides the document from the library and both pickers; it does not detach it from envelopes or templates that already carry it.
  • categoryIdobjectMove to a category. Pass `null` to return it to Uncategorised. Omit to leave unchanged.
  • namestringNew display name

Responses

  • 200DocumentResponseDtoUpdated document
  • 400Empty name
  • 404Document not found
DELETE/v1/documents/{id}Delete a document

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 204Document deleted
  • 403Document is in use
  • 404Document not found
GET/v1/documents/{id}/contentGet document content for viewing (inline)

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 200Document file stream for inline viewing
  • 404Document not found
GET/v1/documents/{id}/detailsGet document with pages and fields

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 200DocumentWithPagesDtoDocument with pages and fields
  • 404Document not found
GET/v1/documents/{id}/downloadDownload document content

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 200Document file stream
  • 404Document not found
GET/v1/documents/{id}/fieldsGet fields for a document scoped to a template or envelope.

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 200FieldResponseDto[]Scoped fields
  • 400Missing or conflicting scope
  • 404Document not found
POST/v1/documents/{id}/fieldsAdd a field to document (scoped to a template or envelope)

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Request body: CreateFieldDto

  • gridGroupIdstringGrid group this field belongs to (client-supplied GridGroup id).
  • groupKeystringGroup key for RADIO fields. All options in the same radio group share this key.
  • heightnumberRequiredHeight (0-1 normalized)
  • labelstringField label
  • linkKeystringLink key for Field Linking. Fields sharing a non-null linkKey propagate values to one another during signing. Distinct from groupKey (RADIO grouping).
  • multilinebooleanFor TEXT fields: allow multiple lines. Signers get a textarea and the completed PDF word-wraps the value instead of shrinking it onto one line.
  • optionLabelstringOption label/value for RADIO fields (the text shown next to the option).
  • pageNumbernumberRequiredPage number (1-indexed)
  • placeholderstringPlaceholder text
  • recipientRolestringRecipient role this field is assigned to
  • requiredbooleanIs field required
  • typeSIGNATURE | INITIALS | DATE_SIGNED | TEXT | CHECKBOX | DROPDOWN | ATTACHMENT | RADIO | DATERequiredField type
  • validationobjectValidation rules (JSON)
  • widthnumberRequiredWidth (0-1 normalized)
  • xnumberRequiredX position (0-1 normalized)
  • ynumberRequiredY position (0-1 normalized)

Responses

  • 201FieldResponseDtoField created
  • 400Invalid field data or missing/conflicting scope
  • 404Document not found
PUT/v1/documents/{id}/fields/{fieldId}Update a field within a template or envelope scope

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID
  • fieldIdstringpathRequiredField ID

Request body: UpdateFieldDto

  • groupKeystringGroup key for RADIO fields
  • heightnumberHeight (0-1 normalized)
  • labelstringField label
  • multilinebooleanFor TEXT fields: allow multiple lines (textarea + word-wrapped PDF output).
  • optionLabelstringOption label for RADIO fields
  • placeholderstringPlaceholder text
  • recipientRolestringRecipient role
  • requiredbooleanIs field required
  • widthnumberWidth (0-1 normalized)
  • xnumberX position (0-1 normalized)
  • ynumberY position (0-1 normalized)

Responses

  • 200FieldResponseDtoField updated
  • 404Document or field not found
DELETE/v1/documents/{id}/fields/{fieldId}Delete a field within a template or envelope scope

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID
  • fieldIdstringpathRequiredField ID

Responses

  • 204Field deleted
  • 404Document or field not found
POST/v1/documents/{id}/fields/bulkAdd multiple fields to a document (scoped to a template or envelope)

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Request body: BulkCreateFieldsDto

  • fieldsCreateFieldDto[]RequiredArray of fields to create

Responses

  • 201FieldResponseDto[]Fields created
  • 400Invalid field data or missing/conflicting scope
  • 404Document not found
POST/v1/documents/{id}/fields/replaceAtomically replace fields for a (document, owner) pair. Editors call this with the full desired field set.

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Request body: ReplaceFieldsDto

  • envelopeIdstringEnvelope that owns these field placements
  • fieldsCreateFieldDto[]RequiredFull set of fields to persist for this (document, owner) pair. Replaces existing.
  • gridGroupsGridGroupDto[]Full set of grid groups to persist for this (document, owner) pair. Replaces existing.
  • templateIdstringTemplate that owns these field placements

Responses

  • 200FieldResponseDto[]Fields after replacement
  • 400Invalid scope or field data
  • 404Document/template/envelope not found
GET/v1/documents/{id}/grid-groupsGet grid groups for a document scoped to a template or envelope.

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 200GridGroupResponseDto[]Scoped grid groups
  • 400Missing or conflicting scope
  • 404Document not found
GET/v1/documents/{id}/pages/{pageNumber}Get page thumbnail URL

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID
  • pageNumbernumberpathRequiredPage number (1-indexed)

Responses

  • 200objectPage thumbnail URL
  • 400Invalid page number
  • 404Document not found
POST/v1/documents/bulk-archiveArchive or restore several documents

The same as `archived` on PATCH /documents/:id, for a set. Archiving hides a document from the library and both pickers and detaches it from nothing. Not all or nothing: each id gets an outcome (ARCHIVED, RESTORED, UNCHANGED or NOT_FOUND) and the rest go through.

Opens with: API key

Request body: BulkArchiveDocumentsDto

  • archivedbooleanRequired`true` archives (hides from the library and both pickers), `false` restores.
  • documentIdsstring[]RequiredDocuments to archive or restore, at most 100.

Responses

  • 200BulkArchiveDocumentsResponseDto
  • 400An invalid list
POST/v1/documents/bulk-categoriseFile several documents under one category

Moves every listed document to the category, or to Uncategorised with `categoryId: null`. All or nothing: if any document is not found or not editable by the caller, nothing moves.

Opens with: API key

Request body: BulkCategoriseDocumentsDto

  • categoryIdstringRequiredThe category to file them under, or `null` for Uncategorised.
  • documentIdsstring[]RequiredDocuments to move, at most 100. All or nothing: one id the caller cannot edit refuses the whole call.

Responses

  • 200BulkCategoriseDocumentsResponseDto
  • 400Category not found, or an invalid list
  • 404One or more documents not found
GET/v1/documents/recentRecently used documents

The documents this caller reached for most recently, newest first — the ranking signal a picker actually wants. READY uploads only; drafts do not count as use.

Opens with: API key

Responses

  • 200DocumentResponseDto[]Recently used documents, most recent first
  • 401Unauthorized

Envelopes

GET/v1/envelopesList envelopes

Opens with: API key

Parameters

  • cursorstringqueryCursor for pagination (use nextCursor from previous response)
  • limitnumberqueryNumber of items to return (max 100)
  • pagenumberqueryDeprecatedPage number (1-indexed) - DEPRECATED: Use cursor pagination instead
  • pageSizenumberqueryDeprecatedPage size - DEPRECATED: Use limit instead
  • statusDRAFT | SENT | VIEWED | PARTIALLY_SIGNED | COMPLETED | DECLINED | VOIDED | EXPIRED[]queryFilter by status. Repeat the parameter for several — `?status=SENT&status=VIEWED`. Matches any of the given statuses.
  • searchstringquerySearch by subject or recipient email
  • folderIdstringqueryFilter by folder ID. Use "null" to get envelopes not in any folder.
  • tagstringqueryFilter by tag. Use "null" for envelopes with no tag.
  • createdFromdate-timequeryOnly envelopes created on or after this instant.
  • createdTodate-timequeryOnly envelopes created on or before this instant.
  • contactEmailstringqueryFilter to envelopes with a recipient at this email address (case-insensitive). This is how a client is matched — recipients carry no contact ID.
  • sourceDIRECT | PROPOSAL_ACCEPTANCE | SELF_SIGNqueryFilter by provenance. `DIRECT` for envelopes composed in the app, `PROPOSAL_ACCEPTANCE` for those spawned by a client accepting a proposal, `SELF_SIGN` for documents the sender signed alone. Omit for `DIRECT` only: `SELF_SIGN` was never sent to anybody, and `PROPOSAL_ACCEPTANCE` belongs to its proposal, so both are excluded unless named.
  • includeAllbooleanqueryInclude every source, hidden ones too. Ignored when `source` is given. Omit for the default, which is `DIRECT` only.
  • mineOnlybooleanqueryOnly envelopes created by the calling user. Narrows the caller's existing scope and never widens it.

Responses

  • 200EnvelopeListResponseDtoReturns list of envelopes
POST/v1/envelopesCreate a new envelope

Opens with: API key

Parameters

  • Idempotency-KeystringheaderUnique key for idempotent requests

Request body: CreateEnvelopeDto

  • departmentIdstringWhich department this envelope belongs to. Omit and it resolves from the creator: their single department, or the company-wide default when they belong to none or several. Must be one the caller may file into (GET /v1/departments/assignable) — anything else is a 400, never a silent fallback.
  • documentIdsstring[]RequiredDocument IDs to include in the envelope
  • expiresAtdate-timeA fixed expiration date. Give at most one of expiresAt, expiryDays and neverExpires; give none for the standard 30 days after sending.
  • expiryDaysnumberExpire this many days after the envelope is SENT, not after it is created.
  • folderIdstringFolder ID to assign the envelope to
  • idempotencyKeystringIdempotency key for this request. Can also be provided via the Idempotency-Key header.
  • messagestringMessage to recipients
  • metadataobjectCustom metadata
  • neverExpiresbooleanThe envelope never expires. It stays open until completed or voided; signing links still lapse after 30 days and a reminder issues a fresh one.
  • recipientsCreateRecipientDto[]RequiredRecipients of the envelope
  • signingOrderPARALLEL | SEQUENTIALSigning order type
  • subjectstringRequiredEnvelope subject (email subject line)
  • templateIdstringTemplate ID if creating from a template

Responses

  • 201EnvelopeDetailResponseDtoEnvelope created successfully
  • 400Invalid input, or an API-key request that does not name the person it is acting for (`ACTING_USER_REQUIRED`) — see the X-Acting-User header
  • 404Document(s) not found
GET/v1/envelopes/{id}Get envelope by ID

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200EnvelopeDetailResponseDtoReturns envelope details
  • 404Envelope not found
PATCH/v1/envelopes/{id}Edit a draft envelope

Change a DRAFT in place: subject, message, signing order, expiry, folder, and the complete set of documents and recipients (each replaces the current set when present; an omitted field is unchanged). A recipient row that names the id it replaces and omits its access code keeps the stored one. Field placements are edited through the document field routes with envelopeId. Only a DRAFT accepts this; a sent envelope is voided or resent, and a proposal acceptance or self-sign envelope is refused.

Opens with: API key

Parameters

  • idstringpathRequired

Request body: UpdateEnvelopeDto

  • documentIdsstring[]The complete, ordered set of document IDs. Replaces the current set.
  • expiresAtobjectA fixed expiration date. Naming any of expiresAt, expiryDays or neverExpires replaces the whole expiry choice; omit all three to leave it unchanged. Null clears.
  • expiryDaysnumberExpire this many days after sending. Null clears.
  • folderIdobjectFolder to file the envelope into. Null removes it from its folder.
  • messagestringMessage to recipients. An empty string clears it.
  • neverExpiresbooleanThe envelope never expires.
  • recipientsUpdateRecipientDto[]The complete set of recipients. Replaces the current set.
  • signingOrderPARALLEL | SEQUENTIALSigning order type
  • subjectstringEnvelope subject (email subject line)

Responses

  • 200EnvelopeDetailResponseDtoDraft updated
  • 400Invalid input, or a document is not ready
  • 404Envelope, document or folder not found
  • 409The envelope is not a draft, or cannot be edited
DELETE/v1/envelopes/{id}Delete a draft envelope

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 204Envelope deleted successfully
  • 404Envelope not found
  • 409Only draft envelopes can be deleted
GET/v1/envelopes/{id}/auditGet envelope audit trail

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Returns chronological audit events with hash chain verification
  • 404Envelope not found
GET/v1/envelopes/{id}/audit/anchorsList and verify timestamp anchors

Every batch this envelope is included in, the providers that timestamped each batch, and a re-verification of the inclusion proof against the root those providers signed. An envelope with no anchors returns anchored:false and verified:true — absence of an anchor is an absent claim, not a failed check, and every envelope completed before 2026-08-06 has none.

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Anchor verification results
  • 404Envelope not found
GET/v1/envelopes/{id}/audit/verifyVerify audit chain integrity

Verifies the hash chain, cross-checks any timestamp anchor against the chain as it stands now, and reconciles the trail against the envelope and recipient statuses recorded outside it. Returns the breakpoint on a hash failure and a typed finding for each contradiction. An unanchored envelope is verified:true with anchored:false — absence of an anchor is an absent claim, not a failed check.

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Chain verification result
  • 404Envelope not found
GET/v1/envelopes/{id}/certificateGet certificate of completion

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Returns PDF certificate
  • 404Envelope not found
GET/v1/envelopes/{id}/copiesEvery copy sent for this envelope

Who it went to, when, whether it has been opened and whether it is still live. This is the half of the feature a PDF on a shared drive can never give you.

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200EnvelopeCopyListResponseDto
  • 404Envelope not found
POST/v1/envelopes/{id}/copiesEmail a copy of a completed document to one address

Mints a scoped, expiring, revocable link and emails it. The document is never attached and the link is never a storage URL: every read is served through our own API, recorded against the link, and refused once the link is withdrawn or the envelope is voided. Session callers only.

Opens with: API key

Parameters

  • idstringpathRequired

Request body: SendEnvelopeCopyDto

  • emailstringRequiredThe one address this copy is for. One per send, deliberately: a send is an act with a named recipient, and a list would make withdrawing it all-or-nothing.
  • notestringAn optional line from the sender, shown in the email as an attributed quote under their name. It is NOT an email body: URLs are stripped and the text is capped before it is stored, because the email already carries exactly one destination and a second one inside a sender's words is indistinguishable from ours to the reader.

Responses

  • 201EnvelopeCopyResponseDto
  • 400Invalid address, or this firm has reached its hourly limit for copies
  • 403An API key cannot send a copy
  • 404Envelope not found
  • 409The envelope is not completed, or its signed document is not ready yet
POST/v1/envelopes/{id}/copies/{copyId}/revokeWithdraw a copy link

The link stops working immediately. Withdrawing one that is already withdrawn is not an error, so pressing the button twice on a stale list does not report a failure over a link that is in exactly the state the caller asked for.

Opens with: API key

Parameters

  • idstringpathRequired
  • copyIdstringpathRequired

Responses

  • 200The link is withdrawn
  • 404Envelope or copy link not found
GET/v1/envelopes/{id}/documents/{documentId}/downloadDownload envelope document content

Opens with: API key

Parameters

  • idstringpathRequired
  • documentIdstringpathRequired

Responses

  • 200Document file stream
  • 404Envelope or document not found
GET/v1/envelopes/{id}/download/signed-mergedDownload all signed documents merged into a single PDF

Returns the envelope-level merged signed PDF (all documents combined).

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Merged signed PDF stream
  • 404Envelope not completed yet
POST/v1/envelopes/{id}/duplicateDuplicate an envelope

Create a copy of an envelope as a new draft with the same documents and recipients

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 201DuplicateEnvelopeResponseDtoEnvelope duplicated successfully
  • 400Invalid input, or an API-key request that does not name the person it is acting for (`ACTING_USER_REQUIRED`) — see the X-Acting-User header
  • 404Envelope not found
GET/v1/envelopes/{id}/evidenceGet evidence package (ZIP)

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Returns ZIP file
  • 404Envelope not found
POST/v1/envelopes/{id}/folderUpdate envelope folder

Move an envelope to a folder or remove from folder

Opens with: API key

Parameters

  • idstringpathRequired

Request body: UpdateEnvelopeFolderDto

  • folderIdobjectRequiredFolder ID to move envelope to (null to remove from folder)

Responses

  • 200Folder updated
  • 404Envelope or folder not found
POST/v1/envelopes/{id}/quick-signQuick Sign: apply your saved signature to all your fields

Auto-applies the current user's saved signature to every signature/initials/date field assigned to them on this envelope. Only allowed when the user is a pending recipient and (for sequential envelopes) is the active signer. Fails if the user has any required TEXT/CHECKBOX/DROPDOWN/RADIO fields that need manual input.

Opens with: API key

Parameters

  • idstringpathRequired

Request body: QuickSignEnvelopeDto

  • consentbooleanRequiredConsent to sign electronically. Must be true.
  • consentVersionstringId of the consent statement the client displayed, e.g. "quick-sign/v1". The wording is resolved server-side from that id and never taken from the request; this only says which of our own published statements was on screen. Omit and the current one is recorded.
  • initialsIdstringSaved initials ID to use (when the recipient has INITIALS fields). Defaults to the user's default saved initials.
  • signatureIdstringSaved signature ID to use. Defaults to the user's default saved signature.

Responses

  • 200QuickSignResponseDtoSigned
  • 400Cannot quick-sign (validation failed)
  • 404Envelope not found or not awaiting you
POST/v1/envelopes/{id}/recipients/{recipientId}/embedGenerate embedded signing URL

Generate a short-lived URL for embedding signing in an iframe. The URL expires in 5 minutes and can only be used once.

Opens with: API key

Parameters

  • idstringpathRequired
  • recipientIdstringpathRequired

Responses

  • 200Returns embeddable signing URL
  • 403Feature not available on current plan
  • 404Envelope or recipient not found
  • 409Recipient already signed or envelope not in signable state
PATCH/v1/envelopes/{id}/recipients/{recipientId}/mark-notifiedMark recipient as manually notified

Mark a LINK_ONLY recipient as having been notified manually (e.g., after copying and sharing the signing link).

Opens with: API key

Parameters

  • idstringpathRequired
  • recipientIdstringpathRequired

Responses

  • 200Recipient marked as notified
  • 404Envelope or recipient not found
POST/v1/envelopes/{id}/remindSend reminder to pending recipients

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Reminders sent
  • 404Envelope not found
  • 409Cannot send reminders for envelope in current state, or the envelope is a self-sign (source SELF_SIGN), whose only signer is its author
POST/v1/envelopes/{id}/resend-with-answersResend a finished envelope pre-filled with previous answers

For a completed or declined envelope, creates and sends a fresh envelope pre-filled with the data signers entered last time (text, dates, checkboxes, radios). Signatures, initials, signing dates and attachments are left blank so signers review and re-sign. The original envelope is preserved as the record.

Opens with: API key

Parameters

  • idstringpathRequired

Request body: ResendWithAnswersDto

  • messagestringOptional note to the signers explaining what to correct. Becomes the message on the resent envelope and is shown to recipients. Falls back to the original message if omitted.

Responses

  • 201ResendWithAnswersResponseDtoEnvelope resent successfully
  • 400Invalid input, or an API-key request that does not name the person it is acting for (`ACTING_USER_REQUIRED`) — see the X-Acting-User header
  • 404Envelope not found
  • 409Envelope is not completed or declined
POST/v1/envelopes/{id}/save-as-templateSave an envelope as a template

Creates a template from this envelope: its documents in order, its recipients as numbered roles (signer_1, viewer_1, ...), its field placements, its signing order and its message. No recipient email or expiry is carried across. Refused for a proposal acceptance envelope.

Opens with: API key

Parameters

  • idstringpathRequired

Request body: CreateTemplateFromEnvelopeDto

  • categorystringTemplate category for grouping
  • descriptionstringTemplate description
  • namestringRequiredTemplate name

Responses

  • 201TemplateDetailResponseDtoTemplate created
  • 400The envelope is a proposal acceptance, has no documents, or a document is gone
  • 404Envelope not found
POST/v1/envelopes/{id}/sendSend envelope to recipients

Opens with: API key

Parameters

  • idstringpathRequired

Request body: SendEnvelopeDto

  • expiresAtdate-timeOverride expiration date

Responses

  • 200SendEnvelopeResponseDtoEnvelope sent successfully
  • 400Invalid envelope state or missing fields
  • 404Envelope not found
  • 409Envelope already sent
PUT/v1/envelopes/{id}/tagSet or clear the tag on an envelope

Files an envelope under a single optional label, at any status. FILING, NOT EVIDENCE: it writes no audit event, never reaches the signed PDF or the certificate, and changing it changes nothing a counterparty relies on. An empty or whitespace-only value clears the tag, as does null. A self-sign envelope may only be tagged by the person who created it, which is the same privacy rule `mineOnly` applies to the list.

Opens with: API key

Parameters

  • idstringpathRequired

Request body: SetEnvelopeTagDto

  • tagobjectRequiredThe tag, or null to remove it. Trimmed; an empty or whitespace-only string is treated as null, so clearing the box in the UI and sending null mean the same thing.

Responses

  • 200EnvelopeResponseDtoTag set
  • 404Envelope not found
POST/v1/envelopes/{id}/voidVoid an envelope

Opens with: API key

Parameters

  • idstringpathRequired

Request body: VoidEnvelopeDto

  • reasonstringRequiredReason for voiding the envelope

Responses

  • 200VoidEnvelopeResponseDtoEnvelope voided successfully
  • 404Envelope not found
  • 409Cannot void envelope in current state
GET/v1/envelopes/awaiting-meList envelopes awaiting the current user's signature

Returns envelopes where the current logged-in user is a pending signer. For sequential envelopes, only the actively-pending wave is returned.

Opens with: API key

Responses

  • 200AwaitingMeListResponseDtoList of envelopes awaiting current user
POST/v1/envelopes/bulk-moveBulk move envelopes to folder

Move multiple envelopes to a folder or remove from folder

Opens with: API key

Request body: BulkMoveEnvelopesDto

  • envelopeIdsstring[]RequiredEnvelope IDs to move
  • folderIdobjectRequiredFolder ID to move envelopes to (null to remove from folder)

Responses

  • 200Envelopes moved
  • 404Folder not found
POST/v1/envelopes/preview-emailRender the covering email for an envelope being composed

Opens with: API key

Request body: EmailPreviewRequestDto

  • expiresAtobjectISO date, or null.
  • messagestringThe covering note, plain text, possibly carrying references.
  • recipientEmailstringRequiredWhich recipient this preview is for. Everyone gets their own links.
  • recipientNamestring
  • subjectstringRequiredThe envelope subject, which becomes the email subject line.
  • supportingEmailPreviewSupportingDto[]

Responses

  • 200EmailPreviewResponseDto
POST/v1/envelopes/self-signCreate a self-sign envelope

Create an envelope where you are the sole signer. Returns a signing URL to sign immediately.

Opens with: API key

Request body: CreateSelfSignEnvelopeDto

  • consentbooleanSet true to sign the document in the same call. Every placement carrying a `value` is filled and the envelope is completed. Consent is required to sign and is recorded in full on the audit trail. Omit to create an unsigned envelope and sign it separately.
  • consentVersionstringId of the consent statement the interface displayed, e.g. "quick-sign/v1". The wording is resolved server-side from this id and never read from the request. Omit and the current statement is recorded.
  • departmentIdstringWhich department this envelope belongs to. Omit and it resolves from the creator: their single department, or the company-wide default when they belong to none or several. Must be one the caller may file into (GET /v1/departments/assignable) — anything else is a 400, never a silent fallback.
  • documentIdsstring[]RequiredDocument IDs to include in the envelope
  • fieldsSelfSignFieldDto[]Field placements for this envelope, in fractional (0-1) page geometry. Fields cannot be added after creation — a self-sign envelope is created already SENT and placements are frozen from that point. Omit to create an envelope with no fields.
  • gridGroupsSelfSignGridGroupDto[]Grid tables referenced by the `gridGroupId` on any grid cell in `fields`.
  • metadataobjectCustom metadata
  • subjectstringRequiredDocument name/subject for the self-signed document

Responses

  • 201SelfSignEnvelopeResponseDtoSelf-sign envelope created. Use signingUrl to sign.
  • 400Invalid input, documents not ready, or an API-key request that does not name the person it is acting for (`ACTING_USER_REQUIRED`) — see the X-Acting-User header
  • 404Document(s) not found
GET/v1/envelopes/statsEnvelope status counts for the list’s tile row

Counts of drafts, envelopes out for signature, completed and expired, plus the overall total — over the SAME filters and the SAME visibility scope as `GET /envelopes`. Takes every list filter except `status`, which it strips, so the tiles describe the filtered set independently of which status is selected and each one remains clickable. The four buckets deliberately do not sum to `total`: DECLINED and VOIDED are counted in `total` and in no bucket. NOTE: nothing in this codebase writes the EXPIRED status — there is no envelope expiry sweep — so `expired` reflects only seeded or out-of-band rows.

Opens with: API key

Parameters

  • searchstringquerySearch by subject or recipient email, identical to the list endpoint’s
  • folderIdstringqueryFilter by folder ID. Use "null" for envelopes not in any folder.
  • contactEmailstringqueryFilter to one client by recipient email, identical to the list endpoint's
  • sourceDIRECT | PROPOSAL_ACCEPTANCE | SELF_SIGNqueryFilter by provenance, identical to the list endpoint's
  • includeAllbooleanqueryInclude every source, hidden ones too. Ignored when `source` is given. Omit for the default, which is `DIRECT` only.

Responses

  • 200EnvelopeStatsResponseDto
  • 400Invalid query params
GET/v1/envelopes/tagsTags this user has used

Every distinct tag on the calling user’s own envelopes, alphabetical. Derived from the rows rather than stored, so there is no vocabulary to administer and nothing to seed. Scoped to the caller rather than the department: the only surface using it is the self-sign list, which is already narrowed to its owner.

Opens with: API key

Responses

  • 200EnvelopeTagsResponseDto

Field Linking

POST/v1/fields/{id}/unlinkClear a field’s linkKey (unlink it from its group)

Opens with: API key

Parameters

  • idstringpathRequiredField ID

Responses

  • 200FieldResponseDto
POST/v1/fields/linkLink fields under a shared linkKey (value propagation)

Opens with: API key

Request body: LinkFieldsDto

  • fieldIdsstring[]RequiredIDs of the fields to link (at least two, same envelope or same template)
  • linkKeystringShared link key. If omitted, a unique key is generated (or an existing key already on one of the fields is reused).

Responses

  • 200LinkFieldsResponseDto
  • 400Fewer than two fields, or mixed scope

Field Suggestions

GET/v1/documents/{id}/suggestionsList PENDING smart field suggestions for a document

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 200FieldSuggestionResponseDto[]
POST/v1/documents/{id}/suggestions/accept-allAccept all PENDING suggestions for a document

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Request body: SuggestionScopeDto

  • envelopeIdstringEnvelope ID (mutually exclusive with templateId)
  • templateIdstringTemplate ID (mutually exclusive with envelopeId)

Responses

  • 200AcceptSuggestionResponseDto
POST/v1/documents/{id}/suggestions/detectRun smart-field detection on demand and (re)write PENDING suggestions

Synchronous re-run of the upload-time detection job. Idempotent: existing PENDING suggestions are cleared and re-created; ACCEPTED/REJECTED rows are preserved. A detector load/parse failure surfaces as a 500 rather than a false empty result.

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Request body: DetectSuggestionsRequestDto

  • forcebooleanReserved for future use; has no effect on current behaviour

Responses

  • 200DetectSuggestionsResponseDto
  • 404Document not found
  • 500Detection failed (could not analyse the document)
POST/v1/documents/{id}/suggestions/reject-allReject all PENDING suggestions for a document

Opens with: API key

Parameters

  • idstringpathRequiredDocument ID

Responses

  • 200SuggestionActionResultDto
POST/v1/suggestions/{id}/acceptAccept a suggestion (promotes to a real Field; link group accepts together)

Opens with: API key

Parameters

  • idstringpathRequiredSuggestion ID

Request body: SuggestionScopeDto

  • envelopeIdstringEnvelope ID (mutually exclusive with templateId)
  • templateIdstringTemplate ID (mutually exclusive with envelopeId)

Responses

  • 200AcceptSuggestionResponseDto
  • 400Already actioned or invalid scope
POST/v1/suggestions/{id}/rejectReject a suggestion

Opens with: API key

Parameters

  • idstringpathRequiredSuggestion ID

Responses

  • 200FieldSuggestionResponseDto

Folders

GET/v1/foldersGet folder tree

Get the complete folder tree with envelope counts

Opens with: API key

Responses

  • 200Returns folder tree
POST/v1/foldersCreate folder

Create a new folder

Opens with: API key

Request body: CreateFolderDto

  • colorstringHex color for folder icon (e.g., #3B82F6)
  • namestringRequiredFolder name
  • parentIdstringParent folder ID for nesting

Responses

  • 201Folder created
  • 404Parent folder not found
  • 409Folder with this name already exists
GET/v1/folders/{id}Get folder

Get a single folder by ID

Opens with: API key

Parameters

  • idstringpathRequiredFolder ID

Responses

  • 200Returns folder
  • 404Folder not found
PATCH/v1/folders/{id}Update folder

Update a folder (name, parent, color)

Opens with: API key

Parameters

  • idstringpathRequiredFolder ID

Request body: UpdateFolderDto

  • colorstringHex color for folder icon
  • namestringFolder name
  • parentIdobjectParent folder ID (null to move to root)

Responses

  • 200Folder updated
  • 404Folder not found
  • 409Folder with this name already exists
DELETE/v1/folders/{id}Delete folder

Delete a folder. Child folders and envelopes are moved to the parent.

Opens with: API key

Parameters

  • idstringpathRequiredFolder ID

Responses

  • 200Folder deleted
  • 404Folder not found
POST/v1/folders/{id}/moveMove folder

Move a folder to a new parent (or root)

Opens with: API key

Parameters

  • idstringpathRequiredFolder ID

Request body: MoveFolderDto

  • newParentIdobjectNew parent folder ID (null to move to root)

Responses

  • 200Folder moved
  • 400Invalid move (circular reference)
  • 404Folder not found
GET/v1/folders/{id}/pathGet folder path

Get the breadcrumb path from root to this folder

Opens with: API key

Parameters

  • idstringpathRequiredFolder ID

Responses

  • 200Returns folder path
  • 404Folder not found
GET/v1/folders/listList all folders

Get all folders as a flat list

Opens with: API key

Responses

  • 200Returns flat folder list

Health

GET/v1/healthHealth check endpoint

Opens with: No credential.

Responses

  • 200Service is healthy

Notifications

GET/v1/notificationsGet notifications

Get all notifications for the current user

Opens with: API key

Parameters

  • limitnumberqueryMaximum number of notifications to return (default: 20)
  • offsetnumberqueryNumber of notifications to skip (default: 0)
  • unreadOnlybooleanqueryOnly return unread notifications (default: false)

Responses

  • 200List of notifications
DELETE/v1/notificationsDelete all notifications

Delete all notifications for the current user

Opens with: API key

Responses

  • 200All notifications deleted
DELETE/v1/notifications/{id}Delete a notification

Delete a specific notification

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 204Notification deleted
POST/v1/notifications/{id}/readMark notification as read

Mark a specific notification as read

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Notification marked as read
  • 404Notification not found
GET/v1/notifications/countGet unread count

Get the number of unread notifications

Opens with: API key

Responses

  • 200Unread notification count
POST/v1/notifications/read-allMark all notifications as read

Mark all notifications for the current user as read

Opens with: API key

Responses

  • 200All notifications marked as read

Onboarding

GET/v1/onboarding/preferencesGet the current user's onboarding tour preferences

Returns whether the user has permanently opted out of automatic walkthroughs. Also included in GET /onboarding/tours; exposed separately for UI that only needs the toggle.

Opens with: API key

Responses

  • 200OnboardingPreferencesResponseDtoTour preferences
  • 401Unauthorized
PUT/v1/onboarding/preferencesTurn automatic onboarding tours off (or back on) for the current user

Sets the account-wide opt-out behind the "Don't show tours again" checkbox. When toursDisabled is true, no tour auto-runs again for this user — including tours whose version is later bumped, which is the point: a version bump must never override an explicit opt-out. Tours stay replayable on demand from the Help menu. Idempotent.

Opens with: API key

Request body: UpdateOnboardingPreferencesDto

  • toursDisabledbooleanRequiredTrue to stop showing walkthroughs automatically, false to re-enable them. Idempotent — re-sending the same value keeps the original opt-out timestamp.

Responses

  • 200OnboardingPreferencesResponseDtoUpdated tour preferences
  • 401Unauthorized
GET/v1/onboarding/setupFirst-run setup checklist for the current account

Every item is computed from live data on each request rather than stored, so it cannot disagree with the account. `required` marks the items that BLOCK something (company identity, and the per-user proposals grant on an entitled plan); the rest are encouragement. Proposals items appear only for a tenant whose plan includes the module.

Opens with: API key

Responses

  • 200SetupChecklistResponseDtoChecklist
  • 401Unauthorized
GET/v1/onboarding/toursList onboarding tour state for the current user

Returns one row per tour the user has completed or dismissed, plus the account-wide tour preferences. The web client compares completedVersion against its TOUR_VERSIONS constants to decide whether to re-trigger a tour — unless preferences.toursDisabled is true, which suppresses all automatic tours regardless of version.

Opens with: API key

Responses

  • 200TourStateListResponseDtoTour state list
  • 401Unauthorized
DELETE/v1/onboarding/tours/{key}Reset an onboarding tour for the current user

Deletes the user's state for this tour key, causing it to re-trigger on next visit. Used by the "Restart tour" action.

Opens with: API key

Parameters

  • keystringpathRequiredTour key

Responses

  • 204Tour state cleared
  • 401Unauthorized
POST/v1/onboarding/tours/{key}/completeMark an onboarding tour as completed

Records that the user has finished the tour at the given version. Upserts on (userId, tourKey).

Opens with: API key

Parameters

  • keystringpathRequiredTour key (e.g. "sidebar", "dashboard")

Request body: CompleteTourDto

  • versionnumberRequiredThe version of the tour the user just completed. Used to detect when a tour has been updated and should re-trigger.

Responses

  • 200TourStateResponseDtoTour state
  • 401Unauthorized
POST/v1/onboarding/tours/{key}/dismissMark an onboarding tour as dismissed

Records that the user dismissed/skipped the tour at the given version. A skip earns a one-version grace in the web client (see TOUR_SKIP_GRACE in apps/web/lib/tours) — the next bump does not re-ask, the one after that does.

Opens with: API key

Parameters

  • keystringpathRequiredTour key

Request body: DismissTourDto

  • versionnumberRequiredThe version of the tour the user dismissed. Recorded so the same version does not re-trigger.

Responses

  • 200TourStateResponseDtoTour state
  • 401Unauthorized

Recipients

GET/v1/recipientsList aggregated recipients

Get a list of unique recipients aggregated from all envelopes. Useful for autocomplete and recipient management.

Opens with: API key

Parameters

  • pagenumberquery
  • pageSizenumberquery
  • searchstringquery
  • roleSIGNER | VIEWER | CCquery

Responses

  • 200Returns list of aggregated recipients
GET/v1/recipients/{email}Get recipient envelopes

Get all envelopes associated with a specific recipient email.

Opens with: API key

Parameters

  • emailstringpathRequired
  • pagestringqueryRequired
  • pageSizestringqueryRequired

Responses

  • 200Returns recipient details with envelope history
GET/v1/recipients/suggestionsGet recipient suggestions

Get autocomplete suggestions for recipient email/name. Requires at least 2 characters.

Opens with: API key

Parameters

  • qstringqueryRequiredSearch query (min 2 chars)
  • limitnumberqueryMax results (default 10)

Responses

  • 200Returns list of recipient suggestions

Signatures

GET/v1/signaturesList saved signatures

Get all saved signatures for the current user.

Opens with: API key

Responses

  • 200SavedSignatureListResponseDtoList of saved signatures
  • 401Unauthorized
POST/v1/signaturesCreate a saved signature

Save a new signature to the user's signature library for reuse.

Opens with: API key

Request body: CreateSavedSignatureDto

  • imageDatastringRequiredSignature image data (base64 for DRAWN/UPLOADED, text for TYPED)
  • isDefaultbooleanSet as default signature
  • isInitialsbooleanIs this an initials signature
  • namestringRequiredInternal label for this saved signature (shown in pickers)
  • signerEmailstringOptional delivery email. When this signature is applied for a self-sign role, the completion notification + signed PDF are routed to this address instead of the logged-in account email.
  • signerNamestringOptional legal name to attribute to this signature on audit certificates and recipient rows when used for self-sign. Useful when an admin holds signatures for multiple staff (CEO, secretary, etc.). Falls back to the logged-in user's account name when omitted.
  • typeDRAWN | TYPED | UPLOADEDRequiredSignature type

Responses

  • 201SavedSignatureResponseDtoSignature saved successfully
  • 400Invalid data or limit reached
  • 401Unauthorized
GET/v1/signatures/{id}Get a saved signature

Get a specific saved signature by ID.

Opens with: API key

Parameters

  • idstringpathRequiredSignature ID

Responses

  • 200SavedSignatureResponseDtoSaved signature
  • 401Unauthorized
  • 404Signature not found
PUT/v1/signatures/{id}Update a saved signature

Update name or default status of a saved signature.

Opens with: API key

Parameters

  • idstringpathRequiredSignature ID

Request body: UpdateSavedSignatureDto

  • imageDatastringReplace the signature image (base64 data URI for DRAWN/UPLOADED, or generated image for TYPED). Optional — omit to leave the current image untouched while updating other fields.
  • isDefaultbooleanSet as default signature
  • namestringInternal label for this saved signature
  • signerEmailstringDelivery email for self-sign use. Pass empty string to clear; omit to leave unchanged.
  • signerNamestringLegal name to attribute to this signature on audit certificates / recipient rows when used for self-sign. Pass an empty string to clear.
  • typeDRAWN | TYPED | UPLOADEDSignature type: only required when replacing imageData

Responses

  • 200SavedSignatureResponseDtoSignature updated
  • 401Unauthorized
  • 404Signature not found
DELETE/v1/signatures/{id}Delete a saved signature

Remove a saved signature from the library.

Opens with: API key

Parameters

  • idstringpathRequiredSignature ID

Responses

  • 204Signature deleted
  • 401Unauthorized
  • 404Signature not found
POST/v1/signatures/{id}/set-defaultSet as default signature

Set this signature as the default for its type (signature or initials).

Opens with: API key

Parameters

  • idstringpathRequiredSignature ID

Responses

  • 200SavedSignatureResponseDtoSignature set as default
  • 401Unauthorized
  • 404Signature not found
GET/v1/signatures/defaultGet default signature

Get the user's default signature.

Opens with: API key

Responses

  • 200SavedSignatureResponseDtoDefault signature or null if none set
  • 401Unauthorized
GET/v1/signatures/default/initialsGet default initials

Get the user's default initials signature.

Opens with: API key

Responses

  • 200SavedSignatureResponseDtoDefault initials or null if none set
  • 401Unauthorized

Signing

GET/v1/signing/{token}Get signing session data

Public endpoint accessed via signing link. Returns all data needed to render the signing UI.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token (from email link)

Responses

  • 200SigningSessionResponseDtoReturns signing session with documents and fields
  • 404Session not found
  • 409Session expired, revoked, or already completed
  • 429Too many requests
POST/v1/signing/{token}/authenticateAuthenticate signer

For SMS or access code authentication methods. Submit the code received.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token

Request body: AuthenticateDto

  • codestringRequiredAuthentication code (SMS or access code)

Responses

  • 200AuthenticateResponseDtoAuthentication result
  • 404Session not found
  • 409Locked out due to too many failed attempts
  • 429Too many authentication attempts
POST/v1/signing/{token}/completeComplete signing session

Finalize signing after all required fields are completed. Requires consent acknowledgment.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token

Request body: CompleteSigningDto

  • consentbooleanRequiredConsent acknowledgment - signer agrees to sign electronically
  • consentVersionstringId of the consent statement the page displayed, e.g. "signing-ceremony/v1", echoed back from the session response. The wording itself is resolved server-side from that id and never taken from the request. Omit and the current statement is recorded.
  • notestringOptional note from signer

Responses

  • 200CompleteSigningResponseDtoSigning completed successfully
  • 400Not all required fields completed or missing consent
  • 404Session not found
  • 409Session already completed
  • 429Too many requests
GET/v1/signing/{token}/completed-documentDownload or view completed signed document

Returns the fully signed document after all signers have completed. Requires email verification. Defaults to a file download; pass disposition=inline to render it in the browser instead.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token
  • emailstringqueryRequired
  • dispositionstringqueryRequired

Responses

  • 200Signed document file stream
  • 400Email parameter required
  • 403Email does not match recipient
  • 404Session not found
  • 409Envelope not yet completed
GET/v1/signing/{token}/completion-statusGet completion status

Check if the envelope is fully completed and get download URL for the signed document.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token

Responses

  • 200CompletionStatusResponseDtoReturns completion status with optional download URL
  • 404Session not found
  • 429Too many requests
POST/v1/signing/{token}/declineDecline to sign

Decline signing the envelope. This will cancel the entire envelope for all recipients.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token

Request body: DeclineSigningDto

  • reasonstringRequiredReason for declining

Responses

  • 200DeclineSigningResponseDtoSigning declined
  • 404Session not found
  • 409Session already completed or declined
  • 429Too many requests
GET/v1/signing/{token}/documents/{documentId}/contentGet document content for signing

Streams the document content through the API. This proxies DocVault to avoid exposing presigned URLs to the client.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token
  • documentIdstringpathRequiredDocument ID

Responses

  • 200Document file stream
  • 404Session or document not found
  • 409Authentication required or session expired
POST/v1/signing/{token}/fields/{fieldId}Complete a field

Submit the value for a field (signature, text, checkbox, etc.)

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token
  • fieldIdstringpathRequiredField ID to complete

Request body: CompleteFieldDto

  • checkedbooleanCheckbox state (for checkbox fields)
  • selectionstringDropdown selection (for dropdown fields)
  • signatureCompleteSignatureFieldDtoSignature data (for signature/initials fields)
  • textstringText value (for text fields)
  • valueTypeTEXT | SIGNATURE | INITIALS | DATE | CHECKBOX | DROPDOWNRequiredField value type

Responses

  • 200CompleteFieldResponseDtoField completed successfully
  • 400Invalid field value — wrong payload shape for the field type, or the value breaches the author's validation rules (returned on the session field as `validation`)
  • 404Session or field not found
  • 409Field already completed
  • 429Too many requests
DELETE/v1/signing/{token}/fields/{fieldId}Clear a field value

Remove a previously entered value so the signer can re-enter or leave it blank. Only allowed before the signer has finished signing.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token
  • fieldIdstringpathRequiredField ID to clear

Responses

  • 200ClearFieldResponseDtoField value cleared successfully
  • 404Session or field not found
  • 409Session already completed
  • 429Too many requests
POST/v1/signing/{token}/resend-sms-codeResend SMS verification code

Resend the SMS verification code. Rate limited to prevent abuse.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token

Responses

  • 200SendSmsCodeResponseDtoSMS code resent (or rate limited)
  • 400Session does not use SMS authentication or no phone number
  • 404Session not found
  • 409Session expired or locked out
  • 429Too many SMS requests
POST/v1/signing/{token}/send-sms-codeSend SMS verification code

Send a new SMS verification code to the recipient's phone number. For SMS authentication only.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token

Responses

  • 200SendSmsCodeResponseDtoSMS code sent (or rate limited)
  • 400Session does not use SMS authentication or no phone number
  • 404Session not found
  • 409Session expired or locked out
  • 429Too many SMS requests
GET/v1/signing/{token}/supporting/{supportingId}/contentRead a supporting document sent with this envelope

Material sent alongside the envelope to be READ, never signed. Streamed through the API, so no DocVault URL ever reaches the client. Defaults to rendering in the browser; pass disposition=attachment to download, which is recorded as a download rather than an open. Every read writes an activity row, and the first read by this recipient also writes a hash-chained audit event carrying the sha256 of the bytes served. A request whose User-Agent identifies a mail scanner is recorded as automated and never reaches the chain. A recipient who has already signed, and a sequential signer whose turn has not come, may both read; a voided, expired or declined envelope refuses.

Opens with: No credential.

Parameters

  • tokenstringpathRequiredSigning session token
  • supportingIdstringpathRequiredSupporting document id
  • dispositionstringqueryRequired

Responses

  • 200Supporting document file stream
  • 400The item is an external link, not a document we hold
  • 404Session or supporting document not found
  • 409Authentication required, or the envelope is closed
  • 429Too many requests

Support

GET/v1/support/configWhether support is available on this environment

Returns false when HOME_GATEWAY_URL is unset (standalone dev). The web app hides the Support nav item and stops polling the unread count when false, so users are never shown a form that cannot deliver.

Opens with: API key

Responses

  • 200SupportConfigResponseDto
GET/v1/support/ticketsList the current user's support tickets

Scoped to this user’s own tickets for CecurSign only. Pass ticketNumber to resolve a /support?ticket=TKT-000042 deep link in a single call.

Opens with: API key

Parameters

  • statusopen | in_progress | awaiting_response | resolved | closedquery
  • ticketNumberstringqueryExact ticket reference, used to resolve /support?ticket=TKT-000042 deep links
  • pagenumberquery
  • limitnumberquery

Responses

  • 200SupportTicketListResponseDto
POST/v1/support/ticketsRaise a support ticket

Creates a ticket on Home’s support desk. The submitter and tenant come from the session; the browser supplies only subject, description, category, priority and page context.

Opens with: API key

Request body: CreateSupportTicketDto

  • categoryquestion | bug | feature | billing | other
  • contextSupportContextDto
  • descriptionstringRequiredWhat the user needs help with
  • formInstanceIduuidClient-generated UUID, stable for one form submission (including retries). Omit and the server generates one, which de-duplicates nothing.
  • prioritylow | medium | high | urgent
  • subjectstringRequiredTicket subject

Responses

  • 201CreateSupportTicketResponseDto
  • 400Validation failure
  • 403Support is not enabled for this account
  • 429Rate limited: retry shortly
  • 503Support is not configured or Home is unreachable
GET/v1/support/tickets/{id}Get a support ticket and its thread

Staff internal notes are stripped by Home — this only ever returns what the user is allowed to see. Does not clear the unread flag; call the read endpoint for that.

Opens with: API key

Parameters

  • idstringpathRequiredHome ticket id (UUID)

Responses

  • 200SupportTicketThreadResponseDto
  • 404No such ticket for this app and tenant
POST/v1/support/tickets/{id}/commentsReply to a support ticket

Replying reopens an awaiting-response or resolved ticket. Closed tickets reject replies with 409.

Opens with: API key

Parameters

  • idstringpathRequiredHome ticket id (UUID)

Request body: ReplyToSupportTicketDto

  • contentstringRequiredReply body

Responses

  • 201SupportReplyResponseDto
  • 409Ticket is closed: raise a new one
POST/v1/support/tickets/{id}/readClear the unread flag on a ticket

Called when the user opens the thread view. Reads are not implicit — fetching a thread never mutates state.

Opens with: API key

Parameters

  • idstringpathRequiredHome ticket id (UUID)

Responses

  • 200SupportSuccessResponseDto
GET/v1/support/tickets/statsTicket counts by status for the current user

Feeds the KPI tiles above /support. Takes no status filter on purpose — the tiles narrow the set these counts describe, so counting a filtered set would zero every tile but the pressed one.

Opens with: API key

Responses

  • 200SupportTicketStatsResponseDto
GET/v1/support/tickets/unread-countUnread reply count for the Support nav badge

Polled by the web app roughly every 60 seconds, only while the tab is visible. Cached briefly per user so multiple open tabs collapse onto one upstream call.

Opens with: API key

Responses

  • 200SupportUnreadCountResponseDto

Supporting documents

GET/v1/envelopes/{envelopeId}/supportingList the supporting documents on an envelope

Material sent alongside the envelope for the recipient to read, never to sign. Nothing here is merged into the signed PDF, named on the certificate or hashed into the audit chain.

Opens with: API key

Parameters

  • envelopeIdstringpathRequiredEnvelope id

Responses

  • 200SupportingDocumentListResponseDto
  • 404Envelope not found
POST/v1/envelopes/{envelopeId}/supportingAttach a supporting document to a draft envelope

Either a document from the firm's library or an https link to the firm's own site, never both. A library document is verified against the caller's tenant and department scope. An external link is never tracked, because the request goes to the firm's own server and never reaches us.

Opens with: API key

Parameters

  • envelopeIdstringpathRequiredEnvelope id

Request body: AttachSupportingDocumentDto

  • descriptionstringOne line of context, shown under the label
  • documentIdstringA document from this firm's library. Verified against the caller's tenant AND their department scope before it is accepted. Mutually exclusive with externalUrl.
  • externalUrlstringAn https URL the firm already publishes on its own site. Mutually exclusive with documentId. An external link is NEVER tracked: the request goes to the firm's own server and never reaches us, so nothing about it appears in the evidence route.
  • labelstringRequiredWhat the recipient sees. Not the filename: "Our privacy policy" reads better to a client than "privacy-policy-v4-FINAL.pdf".
  • ordernumberPosition in the list. Omit and it is appended to the end.

Responses

  • 201SupportingDocumentResponseDto
  • 400Both or neither source given, or a non-https link
  • 404Envelope or document not found
  • 409The envelope is no longer a draft
DELETE/v1/envelopes/{envelopeId}/supporting/{supportingId}Detach a supporting document from a draft envelope

Opens with: API key

Parameters

  • envelopeIdstringpathRequiredEnvelope id
  • supportingIdstringpathRequiredSupporting document id

Responses

  • 204Detached
  • 404Envelope or supporting document not found
  • 409The envelope is no longer a draft
GET/v1/envelopes/{envelopeId}/supporting/evidenceWho read which supporting document, and how often

Per document, per recipient. `firstOpenedAt` is the read that produced the chained audit event and the one that reaches the certificate; the counts are the lightweight activity log. Reads attributed to a mail scanner are reported separately and are never part of the counts. External links report nothing and never will.

Opens with: API key

Parameters

  • envelopeIdstringpathRequiredEnvelope id

Responses

  • 200SupportingDocumentEvidenceResponseDto
  • 404Envelope not found
PUT/v1/envelopes/{envelopeId}/supporting/orderReorder an envelope's supporting documents

Takes every id on the envelope, in the order they should appear. A partial list is refused rather than applied.

Opens with: API key

Parameters

  • envelopeIdstringpathRequiredEnvelope id

Request body: ReorderSupportingDocumentsDto

  • idsstring[]RequiredEvery supporting document id on this envelope or proposal, in the order they should appear. Ids that do not belong here are refused rather than ignored.

Responses

  • 200SupportingDocumentListResponseDto
  • 400The list is incomplete, or names something else
  • 404Envelope not found
  • 409The envelope is no longer a draft

Team

GET/v1/team/invitationsList pending invitations

Opens with: API key

Responses

  • 200InvitationListResponseDto
POST/v1/team/invitationsInvite a new team member

Opens with: API key

Request body: CreateInvitationDto

  • departmentIdsstring[]Departments to join on acceptance. Validated against the tenant now AND again at acceptance — a department can be retired in between.
  • emailstringRequired
  • firstNamestring
  • grantProposalsbooleanGrant proposals access on acceptance. Omit to write no grant row at all, which is not the same as false: false is an explicit, audited denial.
  • lastNamestring
  • roleOWNER | ADMIN | MEMBER | VIEWERRequired
  • visibilityScopeOWN | DEPARTMENT | TENANTWhat they will be able to see, applied when they accept. Omit to leave the default (TENANT, the whole company). DEPARTMENT requires at least one departmentId.

Responses

  • 201InvitationResponseDto
DELETE/v1/team/invitations/{id}Revoke an invitation

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200
POST/v1/team/invitations/{id}/resendResend an invitation

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200InvitationDto
POST/v1/team/invitations/acceptAccept an invitation and create account

Opens with: No credential.

Request body: AcceptInvitationDto

  • firstNamestringRequired
  • lastNamestringRequired
  • passwordstringDeprecated. Passwords are set through Auth0, not here. Validated if supplied, ignored by the web client. Min 8 chars with uppercase, lowercase, number and special character.
  • tokenstringRequired

Responses

  • 201
GET/v1/team/invitations/accept/{token}Get invitation details by token

Opens with: No credential.

Parameters

  • tokenstringpathRequired

Responses

  • 200
  • 404Invitation not found, already used, or expired
GET/v1/team/membersList team members

Requires an OWNER or ADMIN session.

Opens with: API key

Responses

  • 200TeamMemberListResponseDto
  • 401Not authenticated, or insufficient role
DELETE/v1/team/members/{id}Remove team member

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200
PUT/v1/team/members/{id}/roleUpdate team member role

Opens with: API key

Parameters

  • idstringpathRequired

Request body: UpdateMemberRoleDto

  • roleOWNER | ADMIN | MEMBER | VIEWERRequired

Responses

  • 200TeamMemberDto

Templates

GET/v1/templatesList templates

Opens with: API key

Parameters

  • pagenumberqueryPage number (1-indexed)
  • pageSizenumberqueryPage size
  • categorystringqueryFilter by category
  • isActivebooleanqueryFilter by active status
  • searchstringquerySearch by name or description

Responses

  • 200TemplateListResponseDtoReturns list of templates
POST/v1/templatesCreate a new template

Opens with: API key

Request body: CreateTemplateDto

  • categorystringCategory for organizing templates
  • defaultExpirynumberDefault expiry in days. Omit it to use the standard 30 days applied at send.
  • defaultMessagestringDefault message pre-populated into the envelope "message" field when this template is used
  • descriptionstringTemplate description
  • documentIdsstring[]RequiredDocument IDs to bundle into this template, in signing order
  • documentPackIdobjectA document pack to send alongside every envelope made from this template. Its items are COPIED onto the envelope at creation, so editing the pack afterwards never changes anything already sent. Null clears it.
  • isActivebooleanWhether template is active
  • namestringRequiredTemplate name
  • neverExpiresbooleanEnvelopes made from this template never expire. Give this or defaultExpiry, not both; setting either replaces the other.
  • recipientRolesTemplateRecipientRoleDto[]RequiredRecipient roles for this template
  • signingOrderPARALLEL | SEQUENTIALSigning order for envelopes created from this template. SEQUENTIAL = roles sign in the order listed (one at a time); PARALLEL = all roles can sign at any time.

Responses

  • 201TemplateDetailResponseDtoTemplate created successfully
  • 400Invalid input
  • 404Document not found
GET/v1/templates/{id}Get template by ID

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200TemplateDetailResponseDtoReturns template details
  • 404Template not found
PUT/v1/templates/{id}Update a template

Opens with: API key

Parameters

  • idstringpathRequired

Request body: UpdateTemplateDto

  • categorystringCategory for organizing templates
  • defaultExpirynumberDefault expiry in days. Null clears it (sending then applies the standard 30 days); omit to leave it unchanged.
  • defaultMessagestringDefault message pre-populated into the envelope "message" field when this template is used
  • descriptionstringTemplate description
  • documentIdsstring[]Document IDs (replaces all existing template documents when provided)
  • documentPackIdobjectA document pack to send alongside every envelope made from this template. Its items are COPIED onto the envelope at creation, so editing the pack afterwards never changes anything already sent. Null clears it.
  • isActivebooleanWhether template is active
  • namestringTemplate name
  • neverExpiresbooleanEnvelopes made from this template never expire. Give this or defaultExpiry, not both; setting either replaces the other.
  • recipientRolesTemplateRecipientRoleDto[]Recipient roles for this template
  • signingOrderPARALLEL | SEQUENTIALSigning order for envelopes created from this template

Responses

  • 200TemplateDetailResponseDtoTemplate updated successfully
  • 400Invalid input
  • 404Template not found
DELETE/v1/templates/{id}Delete a template

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 204Template deleted successfully
  • 404Template not found
POST/v1/templates/{id}/duplicateDuplicate a template

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 201TemplateDetailResponseDtoTemplate duplicated successfully
  • 404Template not found
POST/v1/templates/{id}/envelopesCreate an envelope from a template

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 201Envelope created successfully
  • 400Invalid input or missing recipient mappings
  • 404Template not found or inactive
GET/v1/templates/categoriesGet list of template categories

Opens with: API key

Responses

  • 200string[]Returns list of category names
GET/v1/templates/most-usedTemplates ranked by how often they have been sent

Backs Home's Templates panel. Active, non-deleted templates only, most-used first, ties broken by newest. `total` counts every active template so a capped list can say what it left out. The template LIST is firm-wide (a shared library); the usage counts that rank it are department-scoped, so "most used" means most used as far as this caller can see.

Opens with: API key

Parameters

  • limitstringqueryRequired

Responses

  • 200MostUsedTemplatesResponseDtoTemplates in descending use
GET/v1/templates/role-namesRole names this company has used on its templates

Backs the suggestions on the template wizard's "Role name" box. Every role name across the tenant's templates (deleted templates excluded), de-duplicated case-insensitively, most-used first with ties alphabetical, each in its most common spelling. Suggestions only: a role name remains free text.

Opens with: API key

Responses

  • 200TemplateRoleNamesResponseDtoRole names in descending use

Tenants

GET/v1/tenants/brandingGet current branding settings

Opens with: Signed-in session or API key

Responses

  • 200BrandingResponseDtoReturns branding settings
  • 401Not authenticated
  • 404Tenant not found
PUT/v1/tenants/brandingUpdate branding settings (primary colour, company name)

Requires an OWNER or ADMIN session.

Opens with: Signed-in session or API key

Request body: UpdateBrandingDto

  • companyNamestringCompany name displayed in signing pages
  • primaryColorstringPrimary brand colour in hex format (#RRGGBB)

Responses

  • 200BrandingResponseDtoBranding updated
  • 400Invalid input
  • 401Not authenticated
  • 403Plan does not include custom branding
  • 404Tenant not found
GET/v1/tenants/currentGet current tenant

Opens with: Signed-in session or API key

Responses

  • 200TenantResponseDtoReturns current tenant
  • 401Not authenticated
  • 404Tenant not found
PATCH/v1/tenants/currentUpdate the current tenant

Requires an OWNER or ADMIN session. Not available to API keys, because RolesGuard needs a person and these fields are the company's public identity. Partial update of `name`, `primaryColor` and `senderDisplayName`. The `settings` JSONB column is NOT writable through this route and a request carrying it is rejected with 400: it holds plan entitlements, quotas and suspension state, which Cecur Home owns and writes through the provisioning API.

Opens with: Signed-in session or API key

Request body: UpdateTenantDto

  • namestringTenant/Organization name
  • primaryColorstringPrimary brand color (hex format)
  • senderDisplayNameobjectDefault "from" name used on outbound envelope emails. Set to your company name to send under the organisation rather than the individual team member. Pass an empty string to clear.

Responses

  • 200TenantResponseDtoTenant updated
  • 400Invalid input, or a non-writable property was sent
  • 401Not authenticated, or insufficient role
  • 403Sent `primaryColor` on a plan without custom branding
  • 404Tenant not found
GET/v1/tenants/current/company-profileGet the tenant company profile

Returns the company identity used by proposals and engagement letters: the source of the {{companyName}}, {{companyAddress}}, {{companyEmail}} and {{companyVatNumber}} merge tokens, plus the per-tenant default currency, VAT rate and proposal grace window. `isCompleteForProposals` is true only when firmName, companyAddress and companyEmail are all present and non-blank.

Opens with: Signed-in session or API key

Responses

  • 200CompanyProfileResponseDtoReturns the company profile
  • 401Not authenticated
  • 404Tenant not found
PATCH/v1/tenants/current/company-profileUpdate the tenant company profile

Requires an OWNER or ADMIN session. Not available to API keys, because these fields are the company's legal identity on a binding engagement letter. Partial update. Omitted keys are left unchanged; an explicit null (or a blank string) clears a nullable field. `firmName` is not editable here - it mirrors Tenant.name, which is set via PATCH /v1/tenants/current or PUT /v1/tenants/branding. Region-derived defaults: when `region` is set to GB or IE AND the tenant has no default currency / VAT rate yet AND the request does not supply them, they are seeded (GB -> GBP / 20.00, IE -> EUR / 23.00). A value the tenant already chose is never overwritten, a value supplied in the same request always wins, and a region outside the map seeds nothing.

Opens with: Signed-in session or API key

Request body: UpdateCompanyProfileDto

  • companyAddressobjectRegistered company address, source of the {{companyAddress}} merge token. Send null (or an empty string) to clear it; omit the key to leave it unchanged.
  • companyEmailobjectPublic company email address, source of the {{companyEmail}} merge token. Send null (or an empty string) to clear it; omit the key to leave it unchanged.
  • companyRegistrationNumberobjectCompanies registration number (CRO / Companies House). Send null (or an empty string) to clear it; omit the key to leave it unchanged.
  • defaultCurrencyobjectISO 4217 currency code used as the default for new proposals. Send null to clear it; omit the key to leave it unchanged.
  • defaultVatRatestringDefault VAT rate (percent) for new proposals, 0-100 with at most 2 decimal places. Accepts a numeric string ("23.00", preferred) or a number. Send null to clear it; omit the key to leave it unchanged.
  • financialYearStartDayobjectThe day of that month the financial year starts (1-31). Stored separately because a UK sole trader's year starts 6 April, not 1 April — month alone would mis-window a large part of the UK market by five days.
  • financialYearStartMonthobjectThe month this company's financial year starts (1-12). Null clears it, which HIDES the "This financial year" analytics preset rather than falling back to January, because a confidently-labelled year that is not the company's own year is worse than no preset.
  • proposalAutoRemindersEnabledbooleanWhether the nightly sweep chases clients who have not answered a proposal (day 3, 7 and 14 after sending, plus a final nudge two days before it expires). Defaults to true. Not nullable - omit the key to leave it unchanged. Turning it off does NOT disable the manual Remind button, which is an explicit act by someone looking at the proposal.
  • proposalGraceWindowDaysnumberDefault grace window in days for abandoned proposal signing ceremonies. Not nullable - omit the key to leave it unchanged.
  • regionobjectISO 3166-1 alpha-2 country code for the company. Drives currency/date formatting (en-GB / en-IE) and can seed the currency and VAT defaults - see the PATCH endpoint description. Send null to clear it; omit the key to leave it unchanged.
  • vatNumberobjectVAT registration number, source of the {{companyVatNumber}} merge token. Send null (or an empty string) to clear it; omit the key to leave it unchanged.

Responses

  • 200CompanyProfileResponseDtoCompany profile updated
  • 400Invalid input
  • 401Not authenticated, or insufficient role
  • 404Tenant not found
GET/v1/tenants/current/statsGet current tenant statistics

Opens with: Signed-in session or API key

Responses

  • 200TenantStatsDtoReturns tenant statistics
  • 401Not authenticated

Users

GET/v1/users/meGet current user profile

Opens with: Signed-in session or API key

Responses

  • 200UserProfileResponseDtoReturns current user
  • 401Not authenticated
PATCH/v1/users/meUpdate current user profile

Opens with: Signed-in session or API key

Request body: UpdateProfileDto

  • avatarUrlstringAvatar URL
  • firstNamestringFirst name
  • lastNamestringLast name
  • phonestringPhone number
  • senderDisplayNameOverrideobjectPer-user override for the "from" name on outbound envelope emails. When set, takes precedence over the tenant's senderDisplayName. Pass an empty string to clear and revert to the tenant default.

Responses

  • 200UserProfileResponseDtoProfile updated
  • 400Invalid input
  • 401Not authenticated
PATCH/v1/users/me/passwordChange password

Opens with: Signed-in session or API key

Request body: ChangePasswordDto

  • currentPasswordstringRequiredCurrent password
  • newPasswordstringRequiredNew password (min 8 chars, must include uppercase, number, and special character)

Responses

  • 200MessageResponseDtoPassword changed
  • 400Invalid input or password requirements not met
  • 401Not authenticated or current password incorrect

Verification

GET/v1/verification/{envelopeId}Verify an envelope

Public endpoint to verify the authenticity of a signed envelope

Opens with: No credential.

Parameters

  • envelopeIduuidpathRequiredThe envelope ID to verify

Responses

  • 200objectVerification result
  • 404Envelope not found

Version

GET/versionWhat code is running here

Identity, not health. Requires the X-Cecur-Version-Key header. Answers 503 if the key is not configured on this host, and 401 — never 404 — if the presented key is wrong or absent, so that "misconfigured" stays distinguishable from "never implemented".

Opens with: No credential.

Parameters

  • x-cecur-version-keystringheaderRequiredShared secret from CECUR_VERSION_KEY. Compared in constant time.

Responses

  • 200VersionResponseDto
  • 401The version key was missing or wrong.
  • 503CECUR_VERSION_KEY is not configured on this host.

Webhooks

GET/v1/webhooksList webhooks

Get all webhook endpoints for your account

Opens with: API key

Responses

  • 200WebhookListResponseDtoList of webhooks
POST/v1/webhooksCreate a webhook

Register a new webhook endpoint to receive event notifications

Opens with: API key

Request body: CreateWebhookDto

  • eventsENVELOPE_SENT | ENVELOPE_VIEWED | ENVELOPE_COMPLETED | ENVELOPE_DECLINED | ENVELOPE_VOIDED | ENVELOPE_EXPIRED | RECIPIENT_COMPLETED | PROPOSAL_SENT | PROPOSAL_VIEWED | PROPOSAL_ACCEPTED | PROPOSAL_DECLINED | PROPOSAL_EXPIRED | PROPOSAL_TERM_ENDING | BOOKING_CREATED | BOOKING_CANCELLED | BOOKING_RESCHEDULED[]RequiredArray of event types to subscribe to
  • urlstringRequiredThe https URL to receive webhook events. Must not resolve to a private, loopback or link-local address.

Responses

  • 201WebhookResponseDtoWebhook created successfully
  • 400Invalid request or duplicate URL
GET/v1/webhooks/{id}Get a webhook

Get details of a specific webhook endpoint

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200WebhookResponseDtoWebhook details
  • 404Webhook not found
PUT/v1/webhooks/{id}Update a webhook

Update webhook URL, events, or active status

Opens with: API key

Parameters

  • idstringpathRequired

Request body: UpdateWebhookDto

  • eventsENVELOPE_SENT | ENVELOPE_VIEWED | ENVELOPE_COMPLETED | ENVELOPE_DECLINED | ENVELOPE_VOIDED | ENVELOPE_EXPIRED | RECIPIENT_COMPLETED | PROPOSAL_SENT | PROPOSAL_VIEWED | PROPOSAL_ACCEPTED | PROPOSAL_DECLINED | PROPOSAL_EXPIRED | PROPOSAL_TERM_ENDING | BOOKING_CREATED | BOOKING_CANCELLED | BOOKING_RESCHEDULED[]Array of event types to subscribe to
  • isActivebooleanWhether the webhook is active
  • urlstringThe https URL to receive webhook events. Must not resolve to a private, loopback or link-local address.

Responses

  • 200WebhookResponseDtoWebhook updated
  • 404Webhook not found
DELETE/v1/webhooks/{id}Delete a webhook

Remove a webhook endpoint

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200Webhook deleted
  • 404Webhook not found
GET/v1/webhooks/{id}/deliveriesGet webhook deliveries

View delivery history for a webhook

Opens with: API key

Parameters

  • idstringpathRequired
  • limitnumberqueryMaximum number of deliveries to return (default: 50)

Responses

  • 200WebhookDeliveryListResponseDtoList of deliveries
  • 404Webhook not found
POST/v1/webhooks/{id}/deliveries/{deliveryId}/retryRetry a failed delivery

Send a failed or dead letter webhook delivery again. If the delivery is waiting between automatic attempts, its next attempt is brought forward to run now (broughtForward: true) on the same schedule; otherwise it is re-queued with up to three attempts.

Opens with: API key

Parameters

  • idstringpathRequired
  • deliveryIdstringpathRequired

Responses

  • 200Delivery re-queued, or its next automatic attempt brought forward
  • 400Cannot retry this delivery
  • 404Webhook or delivery not found
  • 409An attempt of this delivery is running or about to run; wait for it to finish
POST/v1/webhooks/{id}/regenerate-secretRegenerate webhook secret

Generate a new signing secret for webhook verification

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200WebhookResponseDtoSecret regenerated
  • 404Webhook not found
POST/v1/webhooks/{id}/testTest a webhook

Send a test event to verify webhook is working

Opens with: API key

Parameters

  • idstringpathRequired

Responses

  • 200TestWebhookResponseDtoTest result
  • 404Webhook not found
GET/v1/webhooks/dead-letterGet dead letter deliveries

View all permanently failed webhook deliveries across all webhooks

Opens with: API key

Parameters

  • limitnumberqueryMaximum number of deliveries to return (default: 50)

Responses

  • 200WebhookDeliveryListResponseDtoList of dead letter deliveries