API Reference
Atlas
Signing infrastructure for agents. Base URL: https://atlaswork.ai
Quickstart
Get your API key from the Dashboard. Three paths to a signed document — pick one.
A — AI-generated contract (markdown diff)
Agent sends original + revised text. Signer sees a word-level diff of what changed.
curl -X POST https://atlaswork.ai/api/envelope \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"base_template": "Payment terms: net 30.",
"proposed_markdown": "Payment terms: net 14.",
"signer_email": "alice@example.com",
"webhook_url": "https://your-app.com/webhook"
}'
# { "success": true, "sign_url": "https://atlaswork.ai/sign/...", "envelope_id": "..." }
# Open sign_url → sign → download: GET /api/envelope/ENVELOPE_ID/pdfB — Upload an existing PDF (one-time send)
Any PDF works. Need a test file? Use any contract PDF from your machine.
curl -X POST https://atlaswork.ai/api/envelope/upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@agreement.pdf" \
-F "signer_email=alice@example.com"
# { "success": true, "sign_url": "https://atlaswork.ai/sign/...", "envelope_id": "..." }C — PDF template (set up once, send forever)
AI detects fields automatically. Pass the output directly to templates/upload — pct coords are converted server-side.
# 1. Detect signing fields
curl -X POST https://atlaswork.ai/api/templates/infer-fields \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@nda.pdf"
# → { "fields": [{ "type":"signature","page":1,"x_pct":0.44,"y_pct":0.80,"w_pct":0.28,"h_pct":0.08,"confidence":0.96 }] }
# 2. Save template (paste fields array directly from step 1)
curl -X POST https://atlaswork.ai/api/templates/upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "name=Standard NDA" \
-F "file=@nda.pdf" \
-F 'fields=[{"type":"signature","page":1,"x_pct":0.44,"y_pct":0.80,"w_pct":0.28,"h_pct":0.08}]'
# → { "template": { "id": "tmpl_abc123", ... } }
# 3. Send — agent calls this forever, no re-upload
curl -X POST https://atlaswork.ai/api/envelope \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "template_id": "tmpl_abc123", "signer_email": "client@company.com" }'Authentication
All requests require a Bearer token. Get both keys from the Dashboard.
Authorization: Bearer atlas_live_xxxx # live key — legally binding
Authorization: Bearer test_xxxx # test key — shows TEST banner, not binding
{ "sandbox": true } // anonymous sandbox — no key needed, for prototyping onlyCreate envelope
For AI-generated contracts (markdown diff) or sending from a saved template. For uploading a new PDF, use Upload PDF.
Required (one of)
base_templatestringOriginal contract text. Use with proposed_markdown.
proposed_markdownstringRevised text. Atlas diffs against base_template word-by-word.
template_idstringUUID of a saved PDF or markdown template. No proposed_markdown needed for PDF templates.
Delivery
signer_emailstringSend signing link email automatically.
webhook_urlstringPOST envelope events to this URL. Signed with X-Atlas-Signature.
expires_in_daysnumberSigning link validity. Default: 7.
signersarray[{ email, name?, order? }] — for bulk send (one envelope per signer) or sequential signing (add sequential: true).
sequentialbooleanChain signers in order on one envelope. See Sequential signing.
embedbooleanReturns embed_url for iframe embedding. See Embedded signing.
Optional
agent_identitystringAI agent identifier. HMAC-attested in audit trail.
prefillobjectPre-fill text/date fields on PDF templates by label. See Agent prefill.
variablesobjectSubstituted into {{placeholders}} in markdown templates.
access_codestringSigner must enter this code before signing.
metadataobjectArbitrary JSON attached to the envelope, returned in GET and webhooks.
brand_name / brand_color / brand_logo_urlstringCustomise the signing page header.
email_subject / email_bodystringCustomise the signing email.
curl -X POST https://atlaswork.ai/api/envelope \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: req-123" \
-d '{
"base_template": "Term: {{duration}}.",
"proposed_markdown": "Term: {{new_duration}}.",
"variables": { "duration": "1 year", "new_duration": "2 years" },
"signer_email": "alice@example.com",
"agent_identity": "my-agent/v1",
"webhook_url": "https://your-app.com/webhook",
"metadata": { "deal_id": "d_123" }
}'
# { "success": true, "sign_url": "https://...", "envelope_id": "..." }
# Idempotency-Key prevents duplicates on retry. Replay returns X-Idempotent-Replayed: trueUpload PDF / DOCX
One-time PDF or DOCX upload for signing. Accepts the same optional params as Create envelope (signer_email, webhook_url, agent_identity, access_code, metadata, brand_*, email_*) as form fields, plus fields for field overlays. Max 20 MB.
curl -X POST https://atlaswork.ai/api/envelope/upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@agreement.pdf" \
-F "signer_email=alice@example.com" \
-F "agent_identity=my-agent/v1" \
-F "webhook_url=https://your-app.com/webhook"
# { "success": true, "sign_url": "https://...", "envelope_id": "..." }
# With AI-detected fields (pct coords accepted, converted server-side):
curl -X POST https://atlaswork.ai/api/envelope/upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@nda.pdf" \
-F "signer_email=alice@example.com" \
-F 'fields=[{"type":"signature","page":1,"x_pct":0.44,"y_pct":0.80,"w_pct":0.28,"h_pct":0.08}]'PDF Templates
Save a PDF + field positions once. Agents reference it by template_id forever. See the full 3-step setup in Quickstart → C.
fileFilerequiredPDF or DOCX. Multipart form field. Max 20 MB.
namestringrequiredTemplate name, e.g. “Standard NDA”.
fieldsJSON stringSigning field array. Accepts PDF points or pct coords from infer-fields — converted automatically.
descriptionstringOptional description.
# Save — fields can be pct coords directly from infer-fields
curl -X POST https://atlaswork.ai/api/templates/upload \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "name=Standard NDA" \
-F "file=@nda.pdf" \
-F 'fields=[{"type":"signature","page":1,"x_pct":0.44,"y_pct":0.80,"w_pct":0.28,"h_pct":0.08}]'
# → { "success": true, "template": { "id": "tmpl_abc123", ... } }
# Send — agents call this line forever
curl -X POST https://atlaswork.ai/api/envelope \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "template_id": "tmpl_abc123", "signer_email": "client@company.com", "agent_identity": "my-agent/v1" }'The Dashboard “Send PDF for Signing” flow lets you place fields visually and save as a template — no API call needed for setup.
AI field detection
Upload a PDF and Atlas AI locates every signing field. Returns pct coords passable directly to templates/upload or envelope/upload — no conversion needed. A confidence above 0.85 is safe to use without manual review.
curl -X POST https://atlaswork.ai/api/templates/infer-fields \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@agreement.pdf"
{
"success": true,
"confidence": 0.91, // average across all fields
"fields": [
{ "type": "signature", "page": 1,
"x_pct": 0.52, "y_pct": 0.70, "w_pct": 0.35, "h_pct": 0.04,
"label": "Provider Signature", "confidence": 0.97 },
{ "type": "date", "page": 1,
"x_pct": 0.04, "y_pct": 0.80, "w_pct": 0.18, "h_pct": 0.03,
"label": "Effective Date", "confidence": 0.85 }
]
}Signing fields
Pass a fields array when uploading a PDF to overlay interactive fields on the document. The signer clicks each field to sign, initial, enter text, or confirm date. On submit, Atlas embeds values into the PDF with pdf-lib and computes a SHA-256 hash of the result.
Two coord formats accepted: PDF points (origin: bottom-left, 1pt = 1/72 in) or pct fractions from infer-fields — both work on all upload endpoints.
type"signature" | "initials" | "date" | "text"requiredField type.
pagenumberrequired1-indexed page number.
x / y / width / heightnumberPDF point coords (origin: bottom-left). US Letter = 612 × 792 pts.
x_pct / y_pct / w_pct / h_pctnumberPct coords (0–1 fraction of page). Use output from infer-fields directly.
labelstringDisplay label, e.g. “Authorized Signature”.
requiredbooleanDefaults to true. Set false to make optional.
# Using PDF points
'fields=[{"type":"signature","page":1,"x":320,"y":580,"width":200,"height":60,"label":"Sign here"},
{"type":"date","page":1,"x":320,"y":650,"width":120,"height":24},
{"type":"text","page":1,"x":72,"y":500,"width":200,"height":24,"label":"Print Name"}]'
# Using pct coords (from infer-fields — paste directly)
'fields=[{"type":"signature","page":1,"x_pct":0.52,"y_pct":0.70,"w_pct":0.35,"h_pct":0.08}]'Agent prefill
Pre-populate text and date fields on PDF templates before the signer sees them. Keys match the label of template fields. Signature fields must still be drawn by the signer.
{
"template_id": "tmpl_abc123",
"signer_email": "client@company.com",
"prefill": {
"Client Name": "Acme Corp",
"Deal Amount": "$125,000",
"Effective Date": "March 25, 2026"
}
}Bulk send
Pass a signers array (without sequential: true) to create one independent envelope per signer in a single call. Each gets their own sign URL, email, and audit trail.
{
"template_id": "tmpl_abc123",
"signers": [
{ "email": "alice@company.com" },
{ "email": "bob@company.com" },
{ "email": "carol@company.com" }
]
}
# → { "envelopes": [
# { "envelope_id": "...", "sign_url": "https://...", "signer_email": "alice@company.com" },
# ...
# ] }Sequential multi-party signing
One envelope, multiple signers in order. Add sequential: true — Atlas emails signer 1, then automatically emails each next signer after the previous one completes.
{
"template_id": "tmpl_lease",
"sequential": true,
"signers": [
{ "email": "tenant@example.com", "order": 1 },
{ "email": "landlord@example.com", "order": 2 }
],
"webhook_url": "https://your-app.com/webhook"
}
# → single envelope_id. Tenant gets email now.
# After tenant signs → landlord gets email automatically.
# envelope.signed webhook fires once all parties complete.Opening the link before your turn shows a “Not your turn yet” screen. Each signer gets a unique token in their email link.
Embedded signing
Embed the signing experience in your own UI — no redirect. Pass embed: true to get an embed_url. Render in an <iframe> and listen for postMessage events.
// Create with embed: true
const { embed_url, envelope_id } = await fetch('https://atlaswork.ai/api/envelope', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ template_id: 'tmpl_abc123', signer_email: 'client@co.com', embed: true }),
}).then(r => r.json());
// Render: <iframe src={embed_url} width="100%" height="700" style="border:0" />
// Listen for completion
window.addEventListener('message', (e) => {
if (e.data.type === 'atlas:signed')
// { envelope_id, document_hash, signed_at }
if (e.data.type === 'atlas:declined')
// { envelope_id, reason }
});Sign page
sign_url is a hosted page — share it directly or send via signer_email. No account required for signers. Mobile-optimised. Draws a signature, records time-on-document, produces a SHA-256 hash. Decline shows structured reasons (Terms / Price / Time / Wrong doc / Not authorised) — your webhook receives the exact string.
Automatic reminders — zero config
24h — reminder email sent if not yet signed
72h — final reminder with urgency notice
7 days — envelope auto-voided, envelope.voided fires with reason: "auto_expired_7d"
Stream events (SSE)
SSE stream that polls every 2 seconds and closes on terminal state (signed/declined/voided) or after 10 minutes.
const res = await fetch('https://atlaswork.ai/api/envelope/ENVELOPE_ID/events', {
headers: { Authorization: 'Bearer YOUR_API_KEY' },
});
for await (const line of streamLines(res.body)) {
if (!line.startsWith('data: ')) continue;
const { status, envelope } = JSON.parse(line.slice(6));
if (status === 'signed') { /* envelope.document_hash */ break; }
}Get envelope
Full envelope: status, diff, signature data, document hash, metadata. For PDF envelopes, includes a 1-hour signed pdf_url. Public endpoint — the UUID is the access token.
All envelopes for the authenticated user. Params: ?status= ?signer_email= ?from= ?to= ?limit= ?offset=
Audit log
{
"status": "signed",
"legally_binding": true,
"legal_framework": "ESIGN Act & UETA",
"document_hash": "sha256:abc123...",
"agent_identity": "claude-opus-4-6 / LangChain v0.2",
"agent_identity_attested": true,
"signer_ip": "203.0.113.42",
"time_to_sign_seconds": 154,
"events": [
{ "type": "created", "at": "2026-03-25T10:00:00Z" },
{ "type": "viewed", "at": "2026-03-25T10:05:00Z" },
{ "type": "signed", "at": "2026-03-25T10:07:34Z", "document_hash": "sha256:abc123..." }
]
}Download PDF
Returns the signed document. Markdown diff → generates a 3-page ESIGN certificate on the fly (diff + signature + Certificate of Completion). PDF + fields → the original PDF with all field values embedded in-place by pdf-lib. 302 redirect for PDF envelopes, inline for markdown.
curl -L "https://atlaswork.ai/api/envelope/ENVELOPE_ID/pdf" \ -H "Authorization: Bearer YOUR_API_KEY" \ -o signed.pdf
Void / decline
Sender-side cancellation. Requires Bearer auth + ownership. Fires envelope.voided.
Signer-side decline. The sign page shows structured reasons — your webhook receives the exact string so your agent can act on it.
# Decline reasons the signer picks from:
# "Terms are not acceptable" | "Price / cost is too high"
# "Need more time to review" | "Wrong document or version"
# "Not authorized to sign" | "Other"
# Webhook payload:
{ "event": "envelope.declined", "envelope_id": "...",
"reason": "Price / cost is too high", "declined_at": "2026-03-25T14:22:10Z" }
# Agent response example:
if reason == "Price / cost is too high":
agent.send_counteroffer(envelope_id)Resend signing link
Resend the signing email to the current active signer. Pending envelopes only.
curl -X POST https://atlaswork.ai/api/envelope/ENVELOPE_ID/resend \
-H "Authorization: Bearer YOUR_API_KEY"
# { "success": true, "sent_to": "alice@example.com" }Markdown templates
Save a base_template as a reusable markdown template and send to many signers with template_id + proposed_markdown.
# Save
curl -X POST https://atlaswork.ai/api/templates \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{ "name": "Standard NDA", "base_template": "This NDA is entered into by {{party_a}}..." }'
# Use
{ "template_id": "tmpl_...", "proposed_markdown": "...", "signer_email": "..." }
# Update
curl -X PATCH https://atlaswork.ai/api/templates/TEMPLATE_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{ "name": "Revised NDA v2" }'Webhooks
Atlas POSTs to your webhook_url for these events. Every request is signed with X-Atlas-Signature: sha256=HMAC(payload, api_key).
envelope.createdEnvelope was createdenvelope.viewedSigner opened the signing pageenvelope.signedSigner completed the signatureenvelope.declinedSigner declined — includes structured reasonenvelope.voidedSender cancelled or 7-day auto-void// Verify signature server-side
import { createHmac } from 'crypto';
const expected = 'sha256=' + createHmac('sha256', apiKey).update(rawBody).digest('hex');
const valid = expected === req.headers['x-atlas-signature'];Reliability + testing
Failed deliveries retry with exponential backoff (1m → 5m → 30m → 2h → give up). View all attempts in the Dashboard.
Testing locally? Use webhook.site — get a free public URL in seconds, inspect every delivery in the browser.
Agent identity
Pass agent_identity on envelope creation. Atlas stores HMAC-SHA256(agent_identity, api_key) as agent_identity_sig. The audit log returns agent_identity_attested: true when the sig verifies — legally establishing that a specific AI framework, not a human, initiated the request.
Test mode
Use your test key to create non-binding envelopes. Sign page shows a TEST MODE banner. Webhooks and emails still fire.
Authorization: Bearer test_xxxxxxxxxxxxxxxxxxxx
Framework examples
LangChain (Python)
from langchain.tools import tool
import httpx
@tool
def request_signature(base_template: str, proposed: str, signer_email: str) -> str:
"""Request a human to review and sign a contract change."""
res = httpx.post("https://atlaswork.ai/api/envelope",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"base_template": base_template, "proposed_markdown": proposed,
"signer_email": signer_email, "agent_identity": "my-langchain-agent/v1"})
return res.json()["sign_url"]TypeScript
import { Atlas } from 'atlas-sdk';
const atlas = new Atlas({ apiKey: 'YOUR_API_KEY' });
const { sign_url, envelope_id } = await atlas.envelopes.create({
base_template: 'Original contract...',
proposed_markdown: 'Revised contract...',
signer_email: 'alice@example.com',
});
for await (const event of atlas.envelopes.waitForSignature(envelope_id)) {
if (event.status === 'signed') console.log('Signed!', event.envelope.document_hash);
}Errors
| Status | Meaning |
|---|---|
| 400 | Missing required field or invalid body |
| 401 | Missing or invalid API key |
| 403 | Invalid signer token or incorrect access code |
| 404 | Envelope or template not found |
| 409 | Action not allowed in current status |
| 413 | File too large (max 20 MB) |
| 415 | Unsupported file type (PDF and DOCX only) |
| 429 | Rate limit exceeded — 100 envelopes/hour. Headers: X-RateLimit-Limit / -Remaining / -Reset |
| 500 | Internal server error |
All errors return { "success": false, "error": "..." }.