API requests: pagination and idempotency
The rules every SignWith API endpoint shares: where requests go, what they look like, how to page through lists, and how to retry without doing anything twice.
Base URL and format
All requests go to https://app.signwith.co/api/v1. They accept and return JSON (Content-Type: application/json) in UTF-8, except two uploads that also accept multipart/form-data: creating a template and verifying a document.
- Timestamps are ISO 8601 strings, such as
2026-09-27T09:00:01Z. - IDs are integers, except template field IDs, which are UUID strings, and checkout IDs, which are UUIDs.
- Where an endpoint takes
PATCH,PUTis accepted as an alias. - A path that doesn't exist returns
404 not_foundwith the method and path in the message.
Every request needs an API key.
Pagination
List endpoints return the newest items first, a page at a time, in this envelope:
{ "object": "list", "data": [ ... ], "has_more": true, "next_cursor": 4812 }
limitsets the page size, from 1 to 100 (default 20). Values below 1 fall back to 20, and values above 100 to 100.cursorgets the next page: pass thenext_cursorfrom the page before.- Keep going while
has_moreistrue. On the last pagenext_cursorisnull.
This fetches every signature request, 100 at a time:
const items = []
let cursor = null
do {
const url = new URL('https://app.signwith.co/api/v1/signature_requests')
url.searchParams.set('limit', '100')
if (cursor) url.searchParams.set('cursor', String(cursor))
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.SIGNWITH_API_KEY}` },
})
const page = await response.json()
items.push(...page.data)
cursor = page.has_more ? page.next_cursor : null
} while (cursor)
import os
import requests
items, cursor = [], None
while True:
params = {"limit": 100}
if cursor:
params["cursor"] = cursor
page = requests.get(
"https://app.signwith.co/api/v1/signature_requests",
headers={"Authorization": f"Bearer {os.environ['SIGNWITH_API_KEY']}"},
params=params,
).json()
items.extend(page["data"])
if not page["has_more"]:
break
cursor = page["next_cursor"]
The list endpoints are GET /templates, GET /signature_requests and GET /credit_packs, which always returns every pack on one page.
Idempotency
Networks fail. To retry a POST without doing it twice, for example sending the same document to the same people again, send a unique Idempotency-Key header. A UUID v4 works well:
Idempotency-Key: 5f1c6c1e-0b1a-4d38-9d0e-8a7a1f2f8d11
- The first successful (2xx) response is stored for 24 hours per API key and path. A retry with the same key gets that stored response back, with the header
Idempotent-Replayed: true. - Failed responses aren't stored, so you can retry a failed request with the same key.
- If the first request is still running, a retry returns
409 idempotency_key_in_use. Wait a moment and try again. - Reusing a key with a different request body returns
422 idempotency_key_reused. Use a new key for each different request.
Every POST endpoint accepts the header; the API reference marks them. The code samples in these docs generate a new key for each run.
Retrying safely
| Response | Retry? |
|---|---|
429 rate_limited | Yes, after the Retry-After seconds when the header is present. See rate limits. |
409 idempotency_key_in_use | Yes, with the same key, after a short wait. |
500 internal_error | Yes, with the same Idempotency-Key for a POST. Contact support if it keeps happening. |
502 payment_provider_error | Later. Nothing was charged. |
Other 4xx | No. Fix the request first; the error.code says what's wrong. |
Start building
The API and MCP server come with every account. 3 free documents a month, then pay per document.