PolderAPI

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 contracts scope 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

GET/api/v1/contract-templatesscope contracts

Returns 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

POST/api/v1/contractsscope contractsthrottle 10 per minute

Makes 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

GET/api/v1/contractsscope contracts

Returns 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

GET/api/v1/contracts/{contract}scope contracts

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

GET/api/v1/contracts/{contract}/documentscope contracts

Streams 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