Developers
API reference
Every endpoint in the FolioSign REST API, generated from the OpenAPI specification — so it always matches the running code. For concepts, auth and error shapes, start with the REST API guide.
Authentication
Send a bearer credential on every request — an org API key (dctr_live_…) for machine-to-machine access, or a session token for a signed-in user.
curl https://foliosign.com/api/v1/documents \
-H "Authorization: Bearer dctr_live_…"Base URL https://foliosign.com · spec version 1.0.0 · download openapi.json
Identity & session
GET/api/v1/meCurrent user, active org, and all memberships▾
Current user, active org, and all memberships
Responses
200The signed-in user401Not authenticated
DELETE/api/v1/meDelete the account (App Store requirement)▾
Delete the account (App Store requirement)
Deletes the user and everything keyed to them (sessions, memberships, device tokens). Refused while the user is the sole owner of a workspace that still has other members — ownership must be transferred first. Workspaces where they were the only member are left orphaned rather than destroyed: signed documents are legal artifacts with an audit chain that other parties rely on.
Responses
200Deleted400Sole owner of a shared workspace401Not authenticated
POST/api/v1/auth/send-verificationResend the email-verification link to the signed-in user▾
Resend the email-verification link to the signed-in user
Responses
200Sent401Not authenticated
GET/api/v1/organizationsList the organizations the user belongs to▾
List the organizations the user belongs to
Responses
200Memberships401Not authenticated
POST/api/v1/organizationsCreate a workspace and set it active (new-user onboarding)▾
Create a workspace and set it active (new-user onboarding)
Takes a JSON request body — see the schema in openapi.json.
Responses
200Created workspace400Invalid name401Not authenticated
POST/api/v1/organizations/{orgId}/switchSet the active organization for this session▾
Set the active organization for this session
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| orgId* | path | string |
Responses
200Switched401Not authenticated403Not a member of that organization
POST/api/v1/auth/sign-outRevoke the current session token▾
Revoke the current session token
Responses
200Signed out401Not authenticated
Devices & push
POST/api/v1/devicesRegister this device's APNs token for push notifications▾
Register this device's APNs token for push notifications
Takes a JSON request body — see the schema in openapi.json.
Responses
200Registered401Not authenticated
DELETE/api/v1/devices/{token}Unregister a device token▾
Unregister a device token
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| token* | path | string |
Responses
200Unregistered401Not authenticated
Dashboard
GET/api/v1/dashboard/pipelineValue-weighted pipeline KPIs for the active org▾
Value-weighted pipeline KPIs for the active org
Responses
200Pipeline analytics401Not authenticated
Documents
GET/api/v1/documentsList documents (cursor-paginated, newest first)▾
List documents (cursor-paginated, newest first)
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| status | query | DocumentStatus | |
| source | query | DocumentSource | |
| q | query | string | Free-text search over document title and recipient name/email |
| cursor | query | string | |
| limit | query | integer | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200A page of documents401Not authenticated
GET/api/v1/documents/{id}Document detail with recipients▾
Document detail with recipients
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200The document401Not authenticated404Not found
GET/api/v1/documents/{id}/contentBuilder blocks + variables (the reduced mobile editor's read)▾
Builder blocks + variables (the reduced mobile editor's read)
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200Blocks400Not a builder document401Not authenticated404Not found
PATCH/api/v1/documents/{id}/contentUpdate a draft's blocks (server re-validates; totals recomputed at render)▾
Update a draft's blocks (server re-validates; totals recomputed at render)
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Takes a JSON request body — see the schema in openapi.json.
Responses
200Updated400Not a draft / invalid blocks401Not authenticated
GET/api/v1/documents/{id}/analyticsPer-recipient timeline and per-page view stats▾
Per-recipient timeline and per-page view stats
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200Analytics401Not authenticated404Not found
PUT/api/v1/documents/{id}/recipientsReplace the recipients on a draft▾
Replace the recipients on a draft
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Takes a JSON request body — see the schema in openapi.json.
Responses
200Recipients400Not a draft401Not authenticated
POST/api/v1/documents/{id}/sendSend a draft for signature▾
Send a draft for signature
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Takes a JSON request body — see the schema in openapi.json.
Responses
200Sent or rendering400Not sendable401Not authenticated
POST/api/v1/documents/{id}/recipients/{rid}/resendRe-mint a recipient's signing link and re-email it▾
Re-mint a recipient's signing link and re-email it
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| rid* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200Fresh link401Not authenticated404Not found
POST/api/v1/documents/{id}/signing-linkMint a fresh signing link for the caller's own recipient row▾
Mint a fresh signing link for the caller's own recipient row
For a sender who is also a signer, so they can sign in-app without the email. Only ever returns the link for the recipient whose email matches the authenticated user; it cannot be used to obtain anyone else's link. Rotates the token, so any link previously emailed to that recipient stops working.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200Fresh link400Not signable by the caller401Not authenticated404Not found
POST/api/v1/documents/{id}/voidVoid a draft or in-flight document▾
Void a draft or in-flight document
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Takes a JSON request body — see the schema in openapi.json.
Responses
200Voided400Not voidable401Not authenticated
POST/api/v1/documents/{id}/remindManually nudge a recipient (or all outstanding signers)▾
Manually nudge a recipient (or all outstanding signers)
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Takes a JSON request body — see the schema in openapi.json.
Responses
200Reminder(s) sent401Not authenticated404Not found
POST/api/v1/documents/from-pdfCreate a document from a PDF, place a signature field, add the signer, and send▾
Create a document from a PDF, place a signature field, add the signer, and send
One call that uploads a PDF, adds a single signer, places their signature field, and (unless `send:false`) sends it — returning the signing link. Ideal for machine integrations that supply a ready-made PDF (e.g. a Letter of Authorization).
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Takes a JSON request body — see the schema in openapi.json.
Responses
200Created (and sent unless send=false)400Invalid PDF or field placement401Not authenticated403Missing scope
POST/api/v1/documents/uploadCreate a draft document from a PDF▾
Create a draft document from a PDF
Uploads a PDF and leaves it as a DRAFT with no recipients or fields. Returns per-page geometry so a client can render the pages and place fields, then call PUT /documents/{id}/fields and POST /documents/{id}/send.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Takes a JSON request body — see the schema in openapi.json.
Responses
200Draft created400Invalid PDF401Not authenticated403Missing scope
PUT/api/v1/documents/{id}/fieldsReplace the fields on a draft▾
Replace the fields on a draft
Coordinates are normalized 0..1 with a top-left origin, matching the signing ceremony's renderer. Drafts only.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Takes a JSON request body — see the schema in openapi.json.
Responses
200Fields replaced400Not a draft, or invalid placement401Not authenticated404No such document
POST/api/v1/documents/{id}/suggest-fieldsSuggest where fields belong on an uploaded PDF▾
Suggest where fields belong on an uploaded PDF
Reads the PDF's text layer and proposes signature/initials/date/text placements for a human to review. Nothing is saved — send what you accept to PUT /documents/{id}/fields. Costs 1 AI credit only when the model runs; the deterministic heuristics are free. A PDF with no text layer (a photo or flat scan) returns no suggestions.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200Suggestions400No uploaded PDF, or out of AI credits401Not authenticated404No such document
GET/api/v1/documents/{id}/sourceDownload the document's current PDF▾
Download the document's current PDF
The rendered PDF for a builder document, or the uploaded file for an upload — what the signer sees, before any signatures are applied. For the executed copy, use `/documents/{id}/sealed`.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200The PDF bytes401Not authenticated403API key is missing the documents:read scope404No such document here, or it has no PDF
GET/api/v1/documents/{id}/sealedDownload the sealed, certified PDF▾
Download the sealed, certified PDF
The executed document: every signature applied, the certificate of completion appended, and the whole file digitally signed. This is the copy worth keeping — under a workspace-per-customer setup it is the only way the integration holds its customers' executed documents, since those workspaces have no sign-in of their own. Available once the document reaches `COMPLETED` and the seal job has run, which is shortly after the last signer finishes. Poll on `409`: it means not yet. A `404` means the document is not in this workspace, so stop. The response carries `X-Sealed-SHA256`, the same digest as `sealedSha256` on `GET /documents/{id}` — hash the bytes you received and compare, and you have proof the archived copy is the one that was signed.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string | |
| X-FolioSign-Org | header | string | Workspace to act on. Required when authenticating with an API key, which has no session and therefore no active workspace; ignored for session tokens, which use `POST /api/v1/organizations/{orgId}/switch` instead. A key may only name workspaces its own billing account parents — anything else is a 404. Omitting it on an API-key request is a 400 rather than a silent default, because a wrong-but-plausible default files one customer's signed documents under another. |
Responses
200The sealed PDF bytes401Not authenticated403API key is missing the documents:read scope404No such document in this workspace409Not sealed yet — still awaiting signatures or the seal job
AI
POST/api/v1/ai/generateGenerate a proposal from an intake and create a draft document▾
Generate a proposal from an intake and create a draft document
Takes a JSON request body — see the schema in openapi.json.
Responses
200Draft created400Out of AI credits or invalid intake401Not authenticated
Billing
GET/api/v1/billing/usagePlan + usage snapshot for the active org▾
Plan + usage snapshot for the active org
Responses
200Usage summary401Not authenticated
GET/api/v1/billing/plansThe plan catalog (display copy + prices)▾
The plan catalog (display copy + prices)
Responses
200Plans401Not authenticated
POST/api/v1/billing/checkoutStart a Stripe Checkout to upgrade; returns a URL to open externally▾
Start a Stripe Checkout to upgrade; returns a URL to open externally
Takes a JSON request body — see the schema in openapi.json.
Responses
200Checkout link400Invalid plan401Not authenticated403Insufficient role
POST/api/v1/billing/apple/transactionApply a verified App Store subscription transaction▾
Apply a verified App Store subscription transaction
Takes the signed transaction (JWS) from StoreKit and verifies Apple's signature server-side before granting anything — the client's own verification result is not trusted. Idempotent: StoreKit re-delivers transactions until they are finished, and renewals arrive unprompted.
Takes a JSON request body — see the schema in openapi.json.
Responses
200The plan now in force400Transaction failed verification401Not authenticated403Insufficient role
POST/api/v1/billing/portalOpen the Stripe customer portal; returns a URL to open externally▾
Open the Stripe customer portal; returns a URL to open externally
Responses
200Portal link400No billing account yet401Not authenticated403Insufficient role
Admin
GET/api/v1/admin/usersList users across all workspaces▾
List users across all workspaces
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| search | query | string | |
| limit | query | integer |
Responses
200Users401Not authenticated403Not a super-admin
POST/api/v1/admin/impersonateImpersonate a user — returns a session token to use as the bearer▾
Impersonate a user — returns a session token to use as the bearer
Takes a JSON request body — see the schema in openapi.json.
Responses
200Impersonation token401Not authenticated403Not a super-admin
POST/api/v1/admin/impersonate/stopStop impersonating — returns the original admin session token▾
Stop impersonating — returns the original admin session token
Responses
200Original token (may be null)401Not authenticated
API keys
GET/api/v1/apikeysList the active org's API keys (secrets never returned)▾
List the active org's API keys (secrets never returned)
Responses
200API keys401Not authenticated403Insufficient role / not available to API keys
POST/api/v1/apikeysCreate an API key; the full secret is returned exactly once▾
Create an API key; the full secret is returned exactly once
Takes a JSON request body — see the schema in openapi.json.
Responses
201Created key (with secret)400Invalid name or scopes401Not authenticated403Insufficient role / not available to API keys
DELETE/api/v1/apikeys/{id}Revoke an API key▾
Revoke an API key
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string |
Responses
200Revoked401Not authenticated403Insufficient role / not available to API keys404Not found
Workspaces
GET/api/v1/workspacesList the caller's workspaces and the plan's workspace allowance▾
List the caller's workspaces and the plan's workspace allowance
For a signed-in user, the workspaces they are a member of. For an API key, every workspace in its billing group — a key has no memberships, and the workspaces it provisions have no members either. Use the returned `id` as `X-FolioSign-Org` on the document endpoints.
Responses
200Workspaces401Not authenticated403API key is missing the workspaces:read scope
POST/api/v1/workspacesCreate a workspace under the same billing account▾
Create a workspace under the same billing account
It bills through the same subscription while documents, templates and members stay separate. Refused when the plan's workspace allowance is already used, which on every plan but Corporate is one. Send `externalId` — your own id for this customer — to make the call idempotent: a repeat returns the workspace the first call created rather than a second one with the same name. There is no delete endpoint, so a duplicate cannot be undone through the API. A signed-in owner or admin becomes the new workspace's owner. An API key needs the `workspaces:write` scope; the workspace it creates has no members, and is reached by passing its `id` as `X-FolioSign-Org`.
Takes a JSON request body — see the schema in openapi.json.
Responses
200Created, or the existing workspace for a repeated externalId400Allowance reached, or an invalid name401Not authenticated403Requires owner or admin, or the workspaces:write scope for an API key
DELETE/api/v1/workspaces/{id}Delete an empty workspace▾
Delete an empty workspace
For undoing a duplicate — a provisioning call that was retried before you started sending `externalId`, say. It is not a way to close an account. Refused when the workspace holds **any** document, even a draft: a sent document's audit trail is evidence, and a cascade would destroy it silently. Delete or void the documents first. Also refused for the account's main workspace, which holds the subscription the others bill through. An API key needs the `workspaces:delete` scope, which is separate from `workspaces:write` and never granted by default.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
| id* | path | string |
Responses
200Deleted400It holds documents, or it is the account's main workspace401Not authenticated403Requires owner or admin, or the workspaces:delete scope for an API key404No such workspace in this billing account
Settings
GET/api/v1/settings/brandingThe workspace's brand colour▾
The workspace's brand colour
Responses
200Branding401Not authenticated
PATCH/api/v1/settings/brandingSet the workspace's brand colour▾
Set the workspace's brand colour
Send null to clear it back to the FolioSign default. Owner or admin only.
Takes a JSON request body — see the schema in openapi.json.
Responses
200Updated400Not a valid hex colour401Not authenticated403Requires owner or admin
oauth
GET/api/v1/oauth/grantHealth of the OAuth grant this token belongs to▾
Health of the OAuth grant this token belongs to
Reports whether the calling token's grant is live, what it was granted, which billing account it acts on, and when each half of the credential expires. Intended to be polled (hourly is plenty) so an integration can surface connection health to its own operators rather than discovering a lapsed grant through documents silently not being sent. Requires an OAuth access token; an API key has no grant and gets a 403. No scope is required — a grant whose scopes have been narrowed must still be able to report that.
Responses
200Grant health401Missing, expired (`token_expired`) or revoked (`grant_revoked`) credential403Authenticated with an API key, which has no grant