API v1.0.0 · https://api.readyink.ai/v1
Get started

Conventions

A few rules apply to the whole API. Knowing them up front saves debugging later.

Requests and responses

  • Base URL: https://api.readyink.ai/v1. HTTPS only.
  • Send JSON bodies with Content-Type: application/json. Upload PDFs with multipart/form-data, or as raw bytes with Content-Type: application/pdf on the upload endpoint.
  • Responses are JSON. Each object has an object field that names its type, such as "document", "recipient", "field", "quote", "event" or "list".
  • Most changes to a document return the whole updated document, so you always have its current state.

IDs

IDs are strings with a prefix that shows what they point to. Treat them as opaque and don't parse them.

Prefix Object
doc_ Document
rcp_ Recipient
fld_ Field

Timestamps

Times are ISO 8601 strings in UTC, like 2026-11-09T15:04:05Z. Fields that haven't happened yet, such as completed_at on a document that's still out, are null.

Pagination

GET /documents returns the newest documents first, 20 at a time by default. Ask for up to 100 with limit. When has_more is true, pass next_cursor as starting_after to get the next page.

curl "https://api.readyink.ai/v1/documents?limit=100&status=completed" \
  -H "Authorization: Bearer $READYINK_API_KEY"

# next page
curl "https://api.readyink.ai/v1/documents?limit=100&status=completed&starting_after=doc_b81kz2" \
  -H "Authorization: Bearer $READYINK_API_KEY" 
async function* allDocuments(params = {}) {
  let cursor;
  do {
    const q = new URLSearchParams({ limit: "100", ...params, ...(cursor && { starting_after: cursor }) });
    const page = await (await fetch(`${BASE}/documents?${q}`, { headers })).json();
    yield* page.data;
    cursor = page.has_more ? page.next_cursor : null;
  } while (cursor);
}

for await (const d of allDocuments({ status: "completed" })) console.log(d.id, d.title);
def all_documents(**params):
    cursor = None
    while True:
        q = {"limit": 100, **params}
        if cursor:
            q["starting_after"] = cursor
        page = requests.get(f"{BASE}/documents", headers=H, params=q).json()
        yield from page["data"]
        if not page["has_more"]:
            break
        cursor = page["next_cursor"]

for d in all_documents(status="completed"):
    print(d["id"], d["title"])

You can also filter by status and by created_after (an ISO 8601 time). An invalid limit returns 422 invalid_limit, and an invalid date returns 422 invalid_date.

Idempotency keys

Sending and cancelling need an Idempotency-Key header, so a network retry never sends or charges twice.

  • Use any unique string up to 200 characters. A UUID works well.
  • Use a new key for each new action, and the same key when you retry that action.
  • A retry with the same key and the same request returns the first response, with the header Idempotent-Replayed: true.
  • The same key with a different request returns 409 idempotency_conflict.
  • Keys are remembered per API key for 24 hours.
  • A missing key returns 400 idempotency_key_required; a key over 200 characters returns 400 idempotency_key_too_long.

All-or-nothing requests

Creating a document with recipients, and adding many fields at once, either fully succeed or change nothing. If one recipient or field is invalid, you get an error and nothing is created, so you can fix the input and try again without cleaning up.

Rate limits

Each key can make 60 requests a minute, and up to 10 of them can be sends. Over the limit, you get 429 rate_limited with a Retry-After header that says how many seconds to wait. Wait the number of seconds in Retry-After before trying again, and add some randomness to retries when you run many workers.

Limits at a glance

Limit Value
PDF size 25 MB
PDF uploads per document 1
Recipients when creating a document 50
Fields per request 200
Documents per page 1–100 (default 20)
Document title 1–200 characters
Requests 60 a minute per key
Sends 10 a minute per key
Idempotency key 200 characters, remembered 24 hours
Download links Valid for 10 minutes
Something unclear or missing? Email info@readyink.ai or use the contact page. A real person will reply.