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/pdf

B — 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 only

Create envelope

POST/api/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_templatestring

Original contract text. Use with proposed_markdown.

proposed_markdownstring

Revised text. Atlas diffs against base_template word-by-word.

template_idstring

UUID of a saved PDF or markdown template. No proposed_markdown needed for PDF templates.

Delivery

signer_emailstring

Send signing link email automatically.

webhook_urlstring

POST envelope events to this URL. Signed with X-Atlas-Signature.

expires_in_daysnumber

Signing link validity. Default: 7.

signersarray

[{ email, name?, order? }] — for bulk send (one envelope per signer) or sequential signing (add sequential: true).

sequentialboolean

Chain signers in order on one envelope. See Sequential signing.

embedboolean

Returns embed_url for iframe embedding. See Embedded signing.

Optional

agent_identitystring

AI agent identifier. HMAC-attested in audit trail.

prefillobject

Pre-fill text/date fields on PDF templates by label. See Agent prefill.

variablesobject

Substituted into {{placeholders}} in markdown templates.

access_codestring

Signer must enter this code before signing.

metadataobject

Arbitrary JSON attached to the envelope, returned in GET and webhooks.

brand_name / brand_color / brand_logo_urlstring

Customise the signing page header.

email_subject / email_bodystring

Customise 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: true

Upload PDF / DOCX

POST/api/envelope/upload

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

POST/api/templates/upload

Save a PDF + field positions once. Agents reference it by template_id forever. See the full 3-step setup in Quickstart → C.

fileFilerequired

PDF or DOCX. Multipart form field. Max 20 MB.

namestringrequired

Template name, e.g. “Standard NDA”.

fieldsJSON string

Signing field array. Accepts PDF points or pct coords from infer-fields — converted automatically.

descriptionstring

Optional 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

POST/api/templates/infer-fields

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"required

Field type.

pagenumberrequired

1-indexed page number.

x / y / width / heightnumber

PDF point coords (origin: bottom-left). US Letter = 612 × 792 pts.

x_pct / y_pct / w_pct / h_pctnumber

Pct coords (0–1 fraction of page). Use output from infer-fields directly.

labelstring

Display label, e.g. “Authorized Signature”.

requiredboolean

Defaults 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)

GET/api/envelope/{id}/events

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

GET/api/envelope/{id}

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.

GET/api/envelopes

All envelopes for the authenticated user. Params: ?status= ?signer_email= ?from= ?to= ?limit= ?offset=

Audit log

GET/api/envelope/{id}/audit
{
  "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

GET/api/envelope/{id}/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

POST/api/envelope/{id}/void

Sender-side cancellation. Requires Bearer auth + ownership. Fires envelope.voided.

POST/api/envelope/{id}/decline

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

POST/api/envelope/{id}/resend

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

POST/api/templates
GET/api/templates
GET/api/templates/{id}
PATCH/api/templates/{id}

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 created
envelope.viewedSigner opened the signing page
envelope.signedSigner completed the signature
envelope.declinedSigner declined — includes structured reason
envelope.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

StatusMeaning
400Missing required field or invalid body
401Missing or invalid API key
403Invalid signer token or incorrect access code
404Envelope or template not found
409Action not allowed in current status
413File too large (max 20 MB)
415Unsupported file type (PDF and DOCX only)
429Rate limit exceeded — 100 envelopes/hour. Headers: X-RateLimit-Limit / -Remaining / -Reset
500Internal server error

All errors return { "success": false, "error": "..." }.