Chat API
Contracts
Send a prepared template for signature, follow its progress and download the signed PDF.
Base URLhttps://chat.polder.work
A workspace prepares a template once in Chat: the PDF, the boxes and, optionally, the sender's own signature. After that a lease, a quotation or a set of terms can go out from your own system without anyone opening a screen.
All endpoints on this page:
- need a token with the
contractsscope that is tied to one workspace (see Overview), - act on that workspace only,
- require that the token's member may send contracts in that workspace (otherwise
404).
| Status | Meaning |
|---|---|
401 |
Unknown or revoked token. |
403 |
The token has no contracts scope, or is not tied to a workspace. |
404 |
The workspace has contracts switched off or the member may not send contracts, or the template or contract does not exist, belongs to another workspace, or the member may not see it. |
Contract ids and template ids are ULIDs.
List templates
/api/v1/contract-templatesReturns the workspace's templates ordered by title, at most 100. Use it to find the template_id and to see what a send call must contain.
| Field | Type | Description |
|---|---|---|
id |
string | The template_id to send. |
title |
string | Default title of contracts made from it. |
message |
string or null | Default message in the mail to recipients. |
requiredSigners |
integer or null | How many recipients the send call must carry, exactly. |
partyCount |
integer | Parties on the contract, including the sender when they signed the template. |
preSigned |
boolean | Whether the sender's signature is already on the template. If so, they are not one of the recipients. |
readyToSend |
boolean | false when someone still has to finish preparing the template. Sending it then returns 422. |
pageCount |
integer or null | Pages in the document. |
parties |
array | One entry per recipient, in order. See below. |
createdAt |
string | ISO 8601. |
Each entry in parties has recipient (0 for the first recipient you will pass) and fields: the boxes that can be filled in beforehand, each with id, label, type and required. Boxes that a person draws (signatures) are never listed. parties is empty while requiredSigners is null.
curl https://chat.polder.work/api/v1/contract-templates \
-H "Authorization: Bearer $POLDER_TOKEN"
{
"data": [
{
"id": "01JA1B2C3D4E5F6G7H8J9K0M1N",
"title": "Rental agreement",
"message": null,
"requiredSigners": 1,
"partyCount": 2,
"preSigned": true,
"readyToSend": true,
"pageCount": 3,
"parties": [
{
"recipient": 0,
"fields": [
{ "id": 481, "label": "Name tenant", "type": "text", "required": true }
]
}
],
"createdAt": "2026-10-01T08:00:00+00:00"
}
]
}
The contract object
| Field | Type | Description |
|---|---|---|
id |
string | Contract id. |
title |
string | |
status |
string | draft, sent, completed, cancelled or expired. |
signedCopy |
string | State of the signed PDF: none (not completed), pending (being composed), ready, or failed. |
signers |
array | Per signer: name, email, signedAt, declinedAt, declineReason, openedAt (all timestamps ISO 8601 or null). |
locale |
string | Language the mails about this contract go out in (nl or en). |
expiresAt, completedAt, createdAt |
string or null | ISO 8601. |
Signing links are never included. They go to the recipient by mail and nowhere else.
Send a contract
/api/v1/contractsMakes a contract from a template, writes down who has to sign and mails them. Every signer gets a mail with a link; the signing order is the order of recipients. Use this carefully: it sends real mail to people outside Chat from an address the workspace owns.
The template must be ready (readyToSend). The number of recipients must equal requiredSigners, the addresses must be unique, and a recipient may not have the same address as the sender who signed the template. Nothing is created or sent when validation fails.
Body
| Field | Type | Required | Description |
|---|---|---|---|
template_id |
string (ULID) | yes | From the template list. |
recipients |
array | yes | 1 to 20 recipients, in signing order. |
recipients.*.name |
string | yes | Up to 255 characters. |
recipients.*.email |
string | yes | Valid address, up to 255 characters. |
recipients.*.values |
object | no | Prefill: field id (from the template's parties) to value, for the boxes of this recipient. Unknown ids are rejected, and values are validated by the box's type. |
title |
string | no | Overrides the template title (200 characters). |
message |
string | no | Overrides the template message (2000 characters). |
valid_for_days |
integer | no | 1 to 365. Days until the contract expires, counted from now. |
locale |
string | no | nl or en. Language of all mails about this contract. |
callback_url |
string | no | HTTPS/HTTP URL (up to 2048 characters) to be told about this contract; see below. Addresses pointing to private networks are refused. |
callback_secret |
string | no | 16 to 255 characters. Requires callback_url. |
Prefilled values are drafts: the recipient sees them on the page, may change them, and nothing counts as theirs until they sign.
curl https://chat.polder.work/api/v1/contracts \
-H "Authorization: Bearer $POLDER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"template_id": "01JA1B2C3D4E5F6G7H8J9K0M1N",
"title": "Rental agreement, Main Street 1",
"valid_for_days": 14,
"recipients": [
{ "name": "Sam Jansen", "email": "sam@example.com", "values": { "481": "Sam Jansen" } }
],
"callback_url": "https://example.com/hooks/contracts"
}'
Returns 201 with the contract object, plus callbackSecret next to data when you gave a callback_url without a callback_secret:
{
"data": {
"id": "01JA9X8Y7Z6W5V4U3T2S1R0Q9P",
"title": "Rental agreement, Main Street 1",
"status": "sent",
"signedCopy": "none",
"signers": [
{ "name": "Sam Jansen", "email": "sam@example.com", "signedAt": null, "declinedAt": null, "declineReason": null, "openedAt": null }
],
"locale": "nl",
"expiresAt": "2026-11-11T09:00:00+00:00",
"completedAt": null,
"createdAt": "2026-10-28T09:00:00+00:00"
},
"callbackSecret": "whs_…"
}
| Status | Meaning |
|---|---|
404 |
Template does not exist in this workspace. |
422 |
Validation failed, or the template is not finished (readyToSend is false), or the recipient count is wrong, an address occurs twice, a recipient is the sender, a prefill id is unknown, or callback_url is refused. |
429 |
More than 10 sends a minute for this token. |
Warning This call is not idempotent. Retrying after a timeout can send the contract twice. If you are unsure, list contracts first.
Callbacks
With a callback_url Chat posts JSON to that address when a signer signs, a signer declines, and when the contract is completed. The body holds event (signed, declined or completed), occurredAt, contract (id, title, status, completedAt), signers (name, email, signedAt, declinedAt, declineReason) and documentUrl: a signed link, valid for 7 days, to the finished PDF, or null when there is none yet.
Each delivery carries X-Postduif-Event and X-Postduif-Signature: sha256=<hex>, an HMAC-SHA256 of the raw request body with the callback secret. Verify it before trusting the body. If you did not send a callback_secret, one is generated (whs_…) and returned once, in callbackSecret; it cannot be read again. Your own secret is never echoed back. A delivery that does not get a 2xx answer within 5 seconds is retried, 4 attempts in total over about 6 minutes, and the callback does not follow redirects.
Workspace admins can also subscribe a URL to these events for all contracts in Chat's settings.
List contracts
/api/v1/contractsReturns the contracts of the token's workspace that the member may see, newest first, at most 100 (no paging). Templates are not included. Drafts are left out unless you ask for them with status.
Query
| Parameter | Description |
|---|---|
status |
draft, sent, completed, cancelled or expired. Invalid values return 422. |
curl "https://chat.polder.work/api/v1/contracts?status=completed" \
-H "Authorization: Bearer $POLDER_TOKEN"
Returns { "data": [ …contract objects… ] }.
Get a contract
/api/v1/contracts/{contract}Returns one contract object. A template, a contract of another workspace or one the member may not see all return 404.
curl https://chat.polder.work/api/v1/contracts/01JA9X8Y7Z6W5V4U3T2S1R0Q9P \
-H "Authorization: Bearer $POLDER_TOKEN"
Download the signed document
/api/v1/contracts/{contract}/documentStreams the signed PDF (with signatures and audit page) as an attachment. A completed contract can take a moment to get its PDF: check signedCopy on the contract, which becomes ready.
| Status | Meaning |
|---|---|
200 |
The PDF. |
404 |
No such contract in this workspace, or the member may not download it. |
409 |
The contract exists but has no signed copy (yet): not signed, still being composed, or composing failed. The address is right; check signedCopy. |
curl https://chat.polder.work/api/v1/contracts/01JA9X8Y7Z6W5V4U3T2S1R0Q9P/document \
-H "Authorization: Bearer $POLDER_TOKEN" \
-o signed.pdf