Quickstart
This guide sends a one-page PDF to one signer using a free test key. You'll need a readyink.ai account, a PDF and a terminal. Examples are in cURL, Node.js (18 or later) and Python.
1. Create a test key
- Sign in at app.readyink.ai.
- Open Settings › Developers and create a key. Choose Test for now.
- Copy the key. It starts with
ri_test_. Save it as an environment variable so it stays out of your code:
export READYINK_API_KEY="ri_test_…"Check that it works. The response shows your account, "mode": "test" and your token balance.
curl https://api.readyink.ai/v1/account \
-H "Authorization: Bearer $READYINK_API_KEY" const BASE = "https://api.readyink.ai/v1";
const headers = { Authorization: `Bearer ${process.env.READYINK_API_KEY}` };
const account = await (await fetch(`${BASE}/account`, { headers })).json();
console.log(account.mode, account.balance);import os, requests
BASE = "https://api.readyink.ai/v1"
H = {"Authorization": "Bearer " + os.environ["READYINK_API_KEY"]}
account = requests.get(f"{BASE}/account", headers=H).json()
print(account["mode"], account["balance"]){
"object": "account",
"name": "Acme Property Co.",
"plan": "pay_as_you_go",
"mode": "test",
"key_name": "My laptop",
"balance": { "available": 57, "reserved": 0, "monthly": 0 },
"tokens_per_recipient": 1
}2. Create a draft with your PDF
Send the PDF as file and everything else as a JSON string in data. You can add recipients in the same call. This creates a draft and doesn't send anything yet.
curl https://api.readyink.ai/v1/documents \
-H "Authorization: Bearer $READYINK_API_KEY" \
-F "file=@nda.pdf" \
-F 'data={"title":"Mutual NDA","recipients":[{"name":"Ana Diaz","email":"ana@example.com"}]}'import { readFile } from "node:fs/promises";
const form = new FormData();
form.append("file", new Blob([await readFile("nda.pdf")], { type: "application/pdf" }), "nda.pdf");
form.append("data", JSON.stringify({
title: "Mutual NDA",
recipients: [{ name: "Ana Diaz", email: "ana@example.com" }],
}));
const doc = await (await fetch(`${BASE}/documents`, { method: "POST", headers, body: form })).json();
console.log(doc.id, doc.file_status, doc.recipients[0].id);import json
with open("nda.pdf", "rb") as f:
doc = requests.post(
f"{BASE}/documents",
headers=H,
files={"file": ("nda.pdf", f, "application/pdf")},
data={"data": json.dumps({
"title": "Mutual NDA",
"recipients": [{"name": "Ana Diaz", "email": "ana@example.com"}],
})},
).json()
print(doc["id"], doc["file_status"], doc["recipients"][0]["id"]){
"id": "doc_c9x1q8",
"object": "document",
"title": "Mutual NDA",
"status": "draft",
"test_mode": true,
"file_status": "processing",
"recipients": [
{ "id": "rcp_def456", "object": "recipient", "name": "Ana Diaz",
"email": "ana@example.com", "role": "signer", "status": "pending" }
],
"fields": []
}Save the document id and the recipient id. You'll use both in the next steps.
3. Wait for the file to be ready
The PDF takes a few seconds to process. Check the document until file_status is ready. If it's failed, file_error explains why.
curl https://api.readyink.ai/v1/documents/doc_c9x1q8 \
-H "Authorization: Bearer $READYINK_API_KEY" let d = doc;
while (d.file_status === "processing") {
await new Promise((r) => setTimeout(r, 2000));
d = await (await fetch(`${BASE}/documents/${doc.id}`, { headers })).json();
}
if (d.file_status !== "ready") throw new Error(d.file_error);import time
d = doc
while d["file_status"] == "processing":
time.sleep(2)
d = requests.get(f"{BASE}/documents/{doc['id']}", headers=H).json()
if d["file_status"] != "ready":
raise RuntimeError(d["file_error"])4. Place the fields
Positions are fractions of the page from the top-left corner, so x: 0.1 means 10% in from the left edge. This adds a signature and a date near the bottom of page 1.
curl https://api.readyink.ai/v1/documents/doc_c9x1q8/fields \
-H "Authorization: Bearer $READYINK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fields":[
{"type":"signature","recipient_id":"rcp_def456","page":1,"x":0.1,"y":0.8,"w":0.28,"h":0.035},
{"type":"date","recipient_id":"rcp_def456","page":1,"x":0.6,"y":0.8,"w":0.18,"h":0.03}
]}'const rid = doc.recipients[0].id;
await fetch(`${BASE}/documents/${doc.id}/fields`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ fields: [
{ type: "signature", recipient_id: rid, page: 1, x: 0.1, y: 0.8, w: 0.28, h: 0.035 },
{ type: "date", recipient_id: rid, page: 1, x: 0.6, y: 0.8, w: 0.18, h: 0.03 },
] }),
});rid = doc["recipients"][0]["id"]
requests.post(f"{BASE}/documents/{doc['id']}/fields", headers=H, json={"fields": [
{"type": "signature", "recipient_id": rid, "page": 1, "x": 0.1, "y": 0.8, "w": 0.28, "h": 0.035},
{"type": "date", "recipient_id": rid, "page": 1, "x": 0.6, "y": 0.8, "w": 0.18, "h": 0.03},
]})5. Check the cost
The quote tells you how many tokens the send uses, your balance afterwards and anything to fix first. When can_send is true, you're ready.
curl https://api.readyink.ai/v1/documents/doc_c9x1q8/quote \
-H "Authorization: Bearer $READYINK_API_KEY" const quote = await (await fetch(`${BASE}/documents/${doc.id}/quote`, { headers })).json();
if (!quote.can_send) console.log(quote.problems);quote = requests.get(f"{BASE}/documents/{doc['id']}/quote", headers=H).json()
if not quote["can_send"]:
print(quote["problems"]){
"object": "quote",
"document_id": "doc_c9x1q8",
"signers": 1,
"copies": 0,
"tokens": 1,
"balance_available": 57,
"balance_after": 56,
"enough_tokens": true,
"problems": [],
"can_send": true
}6. Send it
Send needs an Idempotency-Key header: any unique string you choose, up to 200 characters. If the request times out, retry with the same key and readyink.ai returns the first result instead of sending twice. Pass quote_tokens so the send is refused if the cost changed since your quote.
curl https://api.readyink.ai/v1/documents/doc_c9x1q8/send \
-H "Authorization: Bearer $READYINK_API_KEY" \
-H "Idempotency-Key: send-doc_c9x1q8-1" \
-H "Content-Type: application/json" \
-d '{"quote_tokens":1}'import { randomUUID } from "node:crypto";
const sent = await (await fetch(`${BASE}/documents/${doc.id}/send`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": randomUUID() },
body: JSON.stringify({ quote_tokens: quote.tokens }),
})).json();
console.log(sent.status); // "out"import uuid
sent = requests.post(
f"{BASE}/documents/{doc['id']}/send",
headers={**H, "Idempotency-Key": str(uuid.uuid4())},
json={"quote_tokens": quote["tokens"]},
).json()
print(sent["status"]) # "out"The status changes to out, and Ana gets an email with a link to sign. With a test key, the email goes to your test inbox and the send costs 0 tokens.
7. Download the signed PDF
Open the test email, sign the document, then check the status. When it's completed, download the signed PDF. The endpoint redirects to a link that works for 10 minutes, so tell your client to follow redirects.
curl https://api.readyink.ai/v1/documents/doc_c9x1q8 -H "Authorization: Bearer $READYINK_API_KEY"
# when "status" is "completed":
curl -fL https://api.readyink.ai/v1/documents/doc_c9x1q8/signed-pdf \
-H "Authorization: Bearer $READYINK_API_KEY" -o signed.pdfimport { writeFile } from "node:fs/promises";
const res = await fetch(`${BASE}/documents/${doc.id}/signed-pdf`, { headers }); // follows the redirect
if (!res.ok) throw new Error((await res.json()).error.message); // e.g. not_completed yet
await writeFile("signed.pdf", Buffer.from(await res.arrayBuffer()));pdf = requests.get(f"{BASE}/documents/{doc['id']}/signed-pdf", headers=H) # follows the redirect
if not pdf.ok:
raise RuntimeError(pdf.json()["error"]["message"]) # e.g. not_completed yet
open("signed.pdf", "wb").write(pdf.content)Next, read how sending and tokens work, then switch to an ri_live_ key. Live sends use real tokens and reach real inboxes.
