Conventions
Behaviors that hold across the entire API: the response envelope, error shape, rate limits, and pagination. Everything here applies to every endpoint in the reference.
Success envelope
Every 2xx response wraps its payload in the same three-field envelope. The interesting part is
always data:
{
"success": true,
"data": {
"id": "9b2f8c34-5e1a-4f0b-8c5d-2a7e9d1c6b43",
"originalFileName": "consulting-agreement.pdf",
"pageCount": 4
},
"timestamp": "2026-06-11T09:31:02.143Z"
}
success is always true on the success path, and timestamp is the server time of the
response. Endpoints that return nothing (e.g. DELETE) answer 204 No Content with an empty
body.
Errors
Errors are not wrapped in the envelope — they are flat objects:
{
"statusCode": 402,
"timestamp": "2026-06-11T09:32:10.512Z",
"path": "/api/v1/signing-processes",
"method": "POST",
"message": "Plan limit reached for signingProcessesPerMonth (limit 5, current 5). Upgrade or add a seat to continue.",
"code": "QUOTA_EXCEEDED"
}
statusCode,timestamp,path,method, andmessageare always present.messageis a single string for application errors, and an array of strings for validation failures (400) — one entry per violated rule.codeis a stable, machine-readable identifier and is present only on application errors. Validation400s and generic authentication/authorization errors have nocode.- Some framework-emitted errors add an
errorfield (the HTTP error name, e.g."Bad Request"), and some errors add a structureddetailsobject.
Match on code, not on message. Messages are human-readable and may be reworded at any
time; codes are contractual. The codes you are most likely to encounter:
| Code | Status | Meaning |
|---|---|---|
QUOTA_EXCEEDED | 402 | A plan quota (storage, monthly signing volume, PDF size, templates) would be exceeded. |
BILLING_ORG_READ_ONLY | 402 | The organization is locked to read-only because of an unpaid invoice. |
API_DAILY_LIMIT | 429 | The organization used up its daily API request quota. |
SMS_RETRY_LIMIT | 429 | A signer requested too many SMS one-time codes for one signing. |
Rate limits and quotas
Two independent mechanisms can answer 429. Distinguish them by the presence of code:
1. Request throttle (no code). A burst limiter of 100 requests per 60 seconds per
client IP. Throttled responses carry the standard headers — Retry-After plus the
X-RateLimit-* family — so back off for the indicated duration and retry. PDF upload endpoints
(initiate and commit) additionally enforce 30 requests per 60 seconds per organization.
2. Daily API quota (code: "API_DAILY_LIMIT"). API-key traffic is metered per organization
per UTC day: the plan's per-seat allowance times purchased seats — FREE 100/day/seat, PRO
2,000/day/seat, ENTERPRISE unlimited. Dashboard (user session) traffic is not metered. The
counter resets at midnight UTC; these responses carry no rate-limit headers, so don't busy-retry —
resume after the UTC day rolls over or upgrade the plan.
{
"statusCode": 429,
"timestamp": "2026-06-11T17:40:00.000Z",
"path": "/api/v1/templates",
"method": "GET",
"message": "Daily API request limit reached for your plan.",
"code": "API_DAILY_LIMIT"
}
Pagination
Paginated collections — GET /templates and GET /signing-processes — use page-number
pagination via query parameters:
| Parameter | Default | Notes |
|---|---|---|
page | 1 | 1-based page index. |
pageSize | 20 | Maximum 100; larger values are rejected as a validation error. |
The paginated payload lives inside the usual data envelope:
{
"success": true,
"data": {
"items": [
{
"id": "5b8e2f01-9c4d-4a6e-b7f3-1d2c8a9e0f45",
"name": "Consulting agreement",
"status": "PUBLISHED"
}
],
"total": 41,
"page": 1,
"pageSize": 20
},
"timestamp": "2026-06-11T09:30:12.481Z"
}
total is the number of matching items across all pages, so the last page is
ceil(total / pageSize).
Filters combine with AND — e.g. GET /templates?status=PUBLISHED&categoryId=… returns
templates that match both. Array-valued parameters such as tagIds accept both forms
interchangeably:
# repeated key
curl "https://api.smartdocs.de/api/v1/templates?tagIds=<uuid-1>&tagIds=<uuid-2>" \
-H "X-API-Key: $SMARTDOCS_API_KEY"
# comma-separated
curl "https://api.smartdocs.de/api/v1/templates?tagIds=<uuid-1>,<uuid-2>" \
-H "X-API-Key: $SMARTDOCS_API_KEY"
Within tagIds itself a template matches if it carries at least one of the given tags; that
tag match then ANDs with the other filters.