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, and message are always present.
  • message is a single string for application errors, and an array of strings for validation failures (400) — one entry per violated rule.
  • code is a stable, machine-readable identifier and is present only on application errors. Validation 400s and generic authentication/authorization errors have no code.
  • Some framework-emitted errors add an error field (the HTTP error name, e.g. "Bad Request"), and some errors add a structured details object.

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:

CodeStatusMeaning
QUOTA_EXCEEDED402A plan quota (storage, monthly signing volume, PDF size, templates) would be exceeded.
BILLING_ORG_READ_ONLY402The organization is locked to read-only because of an unpaid invoice.
API_DAILY_LIMIT429The organization used up its daily API request quota.
SMS_RETRY_LIMIT429A 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:

ParameterDefaultNotes
page11-based page index.
pageSize20Maximum 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.