Sending and tokens
Sending is the only step that uses tokens. Get a quote first, send with an idempotency key, and you'll never be charged twice or by surprise.
How tokens work
- 1 token per signer per document, however many fields they fill in.
GET /accountreturns the current rate astokens_per_recipient. - Copy recipients are free. So are reminders, downloads and certificates.
- Test keys send for 0 tokens.
- The API uses the same balance as the app. On the Team plan, monthly tokens are included;
balance.monthlyshows what's left this month. - Tokens are held while a send is in progress;
balance.reservedshows how many.
See pricing for token packs and the Team plan.
Get a quote
GET /documents/{id}/quote shows the cost and anything that would stop the send. It doesn't change anything, so call it as often as you like.
{
"object": "quote",
"document_id": "doc_c9x1q8",
"signers": 2,
"copies": 1,
"tokens": 2,
"balance_available": 57,
"balance_after": 55,
"enough_tokens": true,
"problems": [],
"can_send": true
}When can_send is false, problems lists what to fix in plain sentences you can show a person. For example, a signer might have no signature field yet.
Send
POST /documents/{id}/send needs an Idempotency-Key header. Add quote_tokens with the tokens from your quote: if the cost changed in the meantime, for example because someone added a signer, the send is refused with 409 quote_changed and nothing is charged.
curl https://api.readyink.ai/v1/documents/doc_c9x1q8/send \
-H "Authorization: Bearer $READYINK_API_KEY" \
-H "Idempotency-Key: 6f1c2b8e-1d2a-4f0e-9a51-7f3e2c1d9b10" \
-H "Content-Type: application/json" \
-d '{"quote_tokens": 2}'const res = await fetch(`${BASE}/documents/${docId}/send`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": key },
body: JSON.stringify({ quote_tokens: quote.tokens }),
});
if (res.status === 402) {
const { error } = await res.json();
console.log(`Need ${error.needed}, have ${error.available}. Buy tokens: ${error.checkout_url}`);
}res = requests.post(
f"{BASE}/documents/{doc_id}/send",
headers={**H, "Idempotency-Key": key},
json={"quote_tokens": quote["tokens"]},
)
if res.status_code == 402:
e = res.json()["error"]
print(f"Need {e['needed']}, have {e['available']}. Buy tokens: {e['checkout_url']}")On success, the document comes back with status: "out". Sending is limited to 10 a minute per key.
When a send is refused
| Response | Code | What to do |
|---|---|---|
| 400 | idempotency_key_required |
Add an Idempotency-Key header. |
| 402 | insufficient_tokens |
Not enough tokens. The error includes needed, available and a checkout_url where a person can buy more. |
| 409 | quote_changed |
The cost changed. Get a new quote and send again with the new tokens. |
| 409 | bad_state |
The document isn't a draft, for example it was already sent. |
| 409 | idempotency_conflict |
You reused a key for a different request. Use a new key. |
| 422 | not_ready_to_send |
Something must be fixed first. problems lists what. |
| 429 | rate_limited |
Wait Retry-After seconds. |
Retry safely
If a send times out or the connection drops, you don't know whether it went through. Retry with the same Idempotency-Key. If the first request worked, you get its response again with Idempotent-Replayed: true, and nothing is sent or charged twice. Keys are remembered for 24 hours.
Cancel a document
POST /documents/{id}/cancel stops a document that's out. It also needs an Idempotency-Key. Signing links stop working and pending signers are emailed.
Tokens come back only if no one had opened the document yet; the response shows how many in tokens_returned.
curl -X POST https://api.readyink.ai/v1/documents/doc_c9x1q8/cancel \
-H "Authorization: Bearer $READYINK_API_KEY" \
-H "Idempotency-Key: cancel-doc_c9x1q8" What you can change after sending
| Change | How |
|---|---|
| Reminder frequency | PATCH /documents/{id} with reminder_days |
| A recipient's email | PATCH /documents/{id}/recipients/{rid} with only {"email": "…"} |
| Anything else | Cancel, then create a new document |
