For agents
You act for a person. Registering takes one request. You receive your token and its ID immediately; the token does nothing until the person approves on a one-page form they get by email (their name, a permission table, consent, signature). Then it is live with exactly what they approved. The person never has to log in.
One HTTP call, no browser. Send the fields listed below; the response carries your token and token ID. The person approves by email.
POST https://sign.prodevgroup.com/api/v1/agent-access/requests Content-Type: application/json (no Authorization header needed)
Or let a script do the call and save the credentials:
curl -fsSL https://sign.prodevgroup.com/register.sh | bash -s -- \ --email jane@example.com --name "Jane Doe" \ --agent "Jane's assistant" --scopes documents:read,documents:write,documents:send --wait
What to send
| Field | Required | Meaning |
|---|---|---|
| yes | The person you act for. They receive the approval email and own the token. | |
| name | yes | The person's name. They can correct it on the approval page. |
| requestedScopes | yes (except remove_tokens) | Permissions you ask for; see the list below. The person may approve fewer. |
| agentName | no | How you introduce yourself; shown in the email and on the page. Default "<Name>'s AI assistant". |
| purpose | no | One sentence on why. |
| note | no | Your own wording to the person, shown in the email and on the page. |
| validForHours | no | Temporary token: whole hours from 1 to 168 (one week). It stops on its own after that. Use it when you cannot keep a secret safely (chat transcript, no credential store). Ask again any time; after the first signed authorization each later temporary token is a one-tap approval. |
| action | no | new_account (default for an unknown email), new_token (default otherwise), rotate_token, update_permissions, remove_tokens, create_company, join_company, transfer_company. |
| tokenId | for rotate / update | The token ID (never the token itself). |
| companyName, companyDetails | for create_company | Company name and optional address, phone, website. |
| companyId | for join / transfer | Company code such as co_7F3K9Q2M. Also binds a new token to a company the person already belongs to. |
| newOwnerEmail | for transfer_company | An active member who becomes the owner. |
| organizationName | no | Only used when the person has no account yet. |
Permissions
- documents:read: See documents and their status. List documents, read who has signed, download completed files, audit trails and certificates.
- documents:write: Prepare documents. Upload PDFs, add signers and fields, edit drafts, delete drafts.
- documents:send: Send for signature. Send documents to people in your name, send reminders, issue signing links, void documents.
- templates:read: Use templates. See and use your saved templates.
- templates:write: Manage templates. Create and delete templates.
- webhooks:manage: Receive event notifications. Register web addresses that get notified when documents are viewed, signed or completed.
- admin: Full control. Everything above, plus managing members, other people's keys and organization settings.
What comes back
{
"id": "request id",
"action": "new_account",
"status": "PENDING", // then SIGNING, then APPROVED (or DECLINED / EXPIRED after 7 days)
"apiKey": "psk_live_...", // your token, shown once; keep it
"tokenId": "...", // how you refer to this token later (rotate, change, remove)
"tokenStatus": "PENDING", // becomes ACTIVE when the person signs
"company": { "code": "co_...", "status": "PENDING" }, // only for company actions
"pollUrl": "https://sign.prodevgroup.com/api/v1/agent-access/requests/<id>?secret=...",
"expiresAt": "..."
}Until approval, every call with the token answers 403 token_pending_approval. Poll pollUrl every 30 to 60 seconds, or just retry. Then use the token as Authorization: Bearer psk_live_... on https://sign.prodevgroup.com/api/v1 or on the MCP endpoint https://sign.prodevgroup.com/mcp.
The full guide (plain text)
Everything an agent needs, rendered here as server-side HTML so page readers without JavaScript get all of it. The same text is at /llms.txt.
# Pro Sign AI: guide for AI agents
API-first electronic signature service. An AI agent can register for a person in seconds: send the person's name, email and the permissions you need; you get your token and its ID back immediately. The token does nothing until the person approves and signs on a one-page form in their browser (no typing); then it goes live with exactly the approved permissions. Then you send PDFs for legally valid e-signature, get webhooks, and download sealed results.
## Register for a person (takes seconds, no browser needed on your side)
Fastest, from a terminal (prints and saves your token and token ID; add --wait to block until the person approves):
```bash
curl -fsSL https://sign.prodevgroup.com/register.sh | bash -s -- --email jane@example.com --name "Jane Doe" --agent "Jane's assistant" --scopes documents:read,documents:write,documents:send
```
Or the same thing as one HTTP call (any language):
1. **Ask.** `POST https://sign.prodevgroup.com/api/v1/agent-access/requests` (no key needed) with JSON:
```json
{
"email": "jane@example.com",
"name": "Jane Doe",
"organizationName": "Doe Roofing",
"agentName": "Jane's assistant",
"purpose": "Send contracts for signature",
"requestedScopes": [
"documents:read",
"documents:write",
"documents:send",
"webhooks:manage"
],
"note": "Hi Jane, approve this and I will handle your contracts."
}
```
2. The person gets an email with one page: their name (editable), a permission table (Permission | Asked | Approve | Deny) prefilled from your request, the electronic-signing consent and a signature pad. They tap Approve and sign. Nothing is granted until then.
3. Until then every call with your token answers 403 token_pending_approval. Poll pollUrl every 30 to 60 seconds (tokenStatus becomes ACTIVE, status APPROVED), or simply retry; DECLINED or EXPIRED (7 days) means no.
4. Once approved the same token is live with exactly the permissions the person approved (they may approve fewer than you asked). Nothing to pick up.
5. Use the token as "Authorization: Bearer psk_live_..." on https://sign.prodevgroup.com/api/v1 or as the bearer on the MCP endpoint https://sign.prodevgroup.com/mcp. Keep the tokenId: it is how you refer to the token later (never send the token itself).
Need more later? POST https://sign.prodevgroup.com/api/v1/agent-access/changes with your key and the new requestedScopes; the person re-approves and re-signs; your key's permissions change in place.
## Temporary keys (for chats and harnesses that cannot keep a secret)
If your key would end up in a chat transcript or you have nowhere safe to keep it, ask for a temporary one: add "validForHours": 24 (any whole number from 1 to 168) to a new_account or new_token request. The token works for exactly that long after the person approves, then stops on its own (calls answer 401 token_expired). Ask again as often as you like: the first temporary token needs the signed authorization like any other; every later one is a one-tap approval email with no signature, so it costs the person almost nothing. The response and pollUrl carry temporary, validForHours and tokenExpiresAt.
```json
{
"action": "new_token",
"email": "jane@example.com",
"name": "Jane Doe",
"agentName": "Chat helper",
"requestedScopes": [
"documents:read",
"documents:send"
],
"validForHours": 24
}
```
## Actions (field `action`)
- `new_account`: first token for a person who has no account yet; token returned now, live after approve + sign
- `new_token`: another token on an existing account, for a different agent or application; token returned now, live after approve + sign
- `rotate_token`: fresh token with the same permissions returned now; one-tap confirm makes it live and stops the old one; needs tokenId
- `update_permissions`: change what a token may do; needs tokenId and requestedScopes (approve + sign)
- `remove_tokens`: revoke tokens; tokenIds, or omit to let the person pick (one-tap confirm)
- `create_company`: create a company with the person as owner; needs companyName (+ optional companyDetails). You get the company code (co_...) at once; the company and your company-bound token go live when the owner signs
- `join_company`: join an existing company for the person; needs companyId. The person signs for your company-bound token, and a company admin approves the membership with one tap; the token goes live when both are done
- `transfer_company`: the company owner signs over control to another active member; needs companyId and newOwnerEmail
Every action emails the person a one-page form. Default action is new_account for an unknown email, otherwise new_token.
## Place fields without vision (signature spots)
Place fields without vision. GET /documents/{id}/signature-spots scans the PDF for signature, initials, date, printed-name and title lines (labels such as SIGNATURE, SIGN HERE, BY:, DATE, INITIALS, PRINTED NAME, TITLE; drawn lines; runs of underscores) and returns a numbered list, each with a plain-English description such as "Signature line above 'Christina Kendrick / PRINTED NAME' in the block labeled 'Homeowner'", the paired date spot, and a ready box above the line (26pt for a signature, 18pt for a date). Lines that already carry printed text are skipped. Give each recipient fields like [{"spot": 1}, {"spot": 2}] instead of coordinates; the server sizes them. Fastest path: POST /documents with "detectSpots": true (it returns signatureSpots and never sends), choose spots, PATCH /documents/{id} with recipients, then POST /documents/{id}/send. Spots work in templates too ({"spot": n, "roleOrder": r}).
```json
{
"step1": {
"method": "POST",
"url": "/api/v1/documents",
"body": {
"title": "Full and Final Release",
"fileBase64": "<base64>",
"detectSpots": true
}
},
"step1_response_excerpt": {
"id": "doc_...",
"status": "DRAFT",
"signatureSpots": [
{
"spot": 1,
"page": 5,
"kind": "signature",
"description": "Signature line above 'Jason Kendrick / PRINTED NAME' in the block labeled 'Homeowner'",
"pairedDate": 2,
"box": {
"x": 54,
"y": 161,
"width": 320,
"height": 26
}
}
]
},
"step2": {
"method": "PATCH",
"url": "/api/v1/documents/doc_...",
"body": {
"recipients": [
{
"name": "Jason Kendrick",
"email": "jason@example.com",
"order": 1,
"fields": [
{
"spot": 1
},
{
"spot": 2
}
]
}
]
}
},
"step3": {
"method": "POST",
"url": "/api/v1/documents/doc_.../send"
}
}
```
## Options: alignment, date format, time zone
Per field: "align" left | center | right (default center for signatures and initials, left for text; anchors: {{signature:1:right}}). Per document: "dateFormat" (tokens YYYY YY MMMM MMM MM M DD D; default MM/DD/YYYY) and "timeZone" (IANA, default America/Chicago) decide how DATE fields print; the certificate and audit trail stay in UTC. Every recipient object in a response carries its own "fields" list as well as the document-level list.
## Test mode (sign every role yourself first)
"testMode": true on POST /documents (or /packets) makes a safe dry run: every email goes to the API key owner only, with [TEST] in the subject and the real recipient named, you may list yourself for every signer role (the duplicate-signer rule is off), the sealed PDF and the certificate carry a TEST mark on every page, and the document shows testMode: true in lists. Use it to sign each role yourself before sending a real document.
## Trust: trusted timestamps, disclosure versions, signer notes
Every sealed PDF carries an RFC 3161 trusted timestamp from a public authority (DigiCert, then Sectigo, then FreeTSA) over the seal signature, and the certificate names the authority and time of a second timestamp over the stamped document; GET /documents/{id} shows timestamp.status (done, pending, none) and the audit trail has "timestamped" events. If no authority answers, the document is still sealed, "timestamp unavailable" is recorded, and the worker retries. The electronic-signing disclosure is versioned: each signer's consent stores the version, and the exact text is at /legal/esign-disclosure/<version> (listed at /legal/esign-disclosure). While the active version is a draft, non-test sends answer 409 disclosure_draft. Signers can also leave the sender a note from the signing page without signing or declining (audit action note_left, emailed to the owner); a decline carries the signer's reason the same way. A signing link that has been used stays alive as a status page for as long as the record exists (who has signed, who is pending, completion time) with the current or sealed PDF at <link>/pdf and, once completed, the certificate at <link>/certificate; the read-only link for the signer's own AI keeps answering too (/read/<token> gains a Parties section, /read/<token>/certificate, and certificateUrl in /json). Only unused links that expired, and links the sender revoked, stop answering.
## Several documents in one request (packets)
Several documents in one request: POST /packets with {"title","orderLocked","documents":[{"title","fileBase64" or "templateId","recipients":[...],"useAnchors"}, ...],"send":true}. Each document keeps its own PDF, recipients, fields, signing order, seal and certificate. Each signer gets ONE email with ONE link that opens every document they are on; they agree to sign electronically once (it covers all of them), see a progress bar such as "1/3 complete" with numbered tabs, and are taken to the next document automatically after each signature. orderLocked:true means they must sign in the order listed (the page shows a single "Proceed" button); false means they pick from a numbered list in any order. The read-only link for an AI assistant covers all documents of the packet (/read/<token> lists them; /read/<token>/pdf?doc=2 is the second PDF). Signing order is packet-wide: "signingOrder": "SEQUENTIAL" sends the whole packet to the first signer group, and only when they have finished every document of theirs does it go to the next group; groups come from "signers": [{"email","order"}] (same number = at the same time, e.g. A first, then B and C together) or, when omitted, from each signer's lowest recipient order across the documents. PARALLEL (default) opens the packet to everyone at once. A document's own signingOrder still applies inside that document. POST /packets/{id}/signers/{email}/link gives you a packet link and a ready-to-send message; GET /packets/{id} shows per-document and per-signer progress; /remind and /void work for the whole packet. The same signer may be on only some of the documents. Attachments: a document with "attachment": true carries no recipients or fields; everyone on the packet sees it, each signer must open it before the last step (the page records the review with time, IP and device), and it concludes together with the packet. "acknowledge": "sign" or "initial" instead appends an "Acknowledgment of Receipt" sheet with a block per signer, and the attachment becomes a document they sign or initial. Packet Acknowledgment: when on (packet "acknowledgment": true/false, else the workspace rule: always, attachments (default), never via GET/PATCH /settings), every signer gets one more document, kind PACKET_ACKNOWLEDGMENT, created at send time after the last position: it lists every document they are on with page count, part (signed, initialed, provided for review) and SHA-256 of the file as sent, and takes one signature. It is always the last step: the API answers 409 packet_order until the signer has signed or initialed everything else and opened every attachment. Word it yourself by passing an object instead of true: "acknowledgment": {"title": "Close-out Acknowledgment", "intro": "...", "statement": "By signing, {{signer}} confirms receipt of all {{count}} documents of {{packet}} from {{sender}}."} (parts left out keep the service wording; the sheet notes which parts are yours). An attachment's receipt sheet takes "acknowledgeTitle" and "acknowledgeStatement" the same way (placeholders {{title}}, {{pages}}, {{sender}}, {{packet}}). Document kinds appear as "kind" on every document view.
```json
{
"title": "Smith roof replacement",
"orderLocked": false,
"send": true,
"documents": [
{
"title": "Contract",
"fileBase64": "<base64>",
"useAnchors": true,
"recipients": [
{
"name": "Jane Smith",
"email": "jane@example.com"
}
]
},
{
"title": "Assignment of benefits",
"fileBase64": "<base64>",
"useAnchors": true,
"recipients": [
{
"name": "Jane Smith",
"email": "jane@example.com"
}
]
},
{
"title": "Direction to pay",
"templateId": "tpl_...",
"recipients": [
{
"roleOrder": 1,
"name": "Jane Smith",
"email": "jane@example.com"
}
]
}
]
}
```
## Companies (multi-tenant)
A person has a personal workspace and can belong to any number of companies with the same email. Every documents/templates/webhooks call takes companyId (query or body) to run in that company; without it the call is personal. Company templates and documents are visible to company admins; personal ones are not. If the person is not an active member you get 403 not_a_member; an unknown code gives 404 no_company. GET /me lists the workspaces.
## Permissions you can ask for
- `documents:read`: See documents and their status. List documents, read who has signed, download completed files, audit trails and certificates.
- `documents:write`: Prepare documents. Upload PDFs, add signers and fields, edit drafts, delete drafts.
- `documents:send`: Send for signature. Send documents to people in your name, send reminders, issue signing links, void documents.
- `templates:read`: Use templates. See and use your saved templates.
- `templates:write`: Manage templates. Create and delete templates.
- `webhooks:manage`: Receive event notifications. Register web addresses that get notified when documents are viewed, signed or completed.
- `admin`: Full control. Everything above, plus managing members, other people's keys and organization settings.
## After you have a key
- API docs: https://sign.prodevgroup.com/api/v1/docs
- OpenAPI: https://sign.prodevgroup.com/api/v1/openapi.json
- MCP (Streamable HTTP, bearer = your key): https://sign.prodevgroup.com/mcp
- No vision and a PDF with no signature lines (an invoice, a letter)? Add "signaturePage" to POST /documents: you write the title and the statement, the server appends a page with a block per signer (signature line, date line, printed name, optional title) and places the fields itself; the response carries signaturePageText so you can check it. Example: {"enabled":true,"title":"Acknowledgment of Receipt","statement":"By signing below, I acknowledge that I received and reviewed Final Invoice PB-2026-KEN-FINAL dated October 12, 2026.","blocks":[{"recipient":1,"label":"Homeowner","printedName":"Jason Kendrick","fields":["signature","date"],"align":"left"},{"recipient":2,"label":"Homeowner","printedName":"Christina Kendrick"}]}.
- Put anchor tags in your PDF ({{signature:1}}, {{date:1}}, {{name:1}}, {{text:1:Policy number}}, {{checkbox:1}}) and set useAnchors:true; no coordinates needed. Add a last part left, center or right to align inside the box ({{signature:1:right}}); fields by coordinates take "align".
- Several PDFs for the same people? POST /packets with a documents list (each with its own recipients and fields). Every signer gets one link for all of them, consents once, sees "1/3 complete", and moves to the next document automatically. orderLocked:true forces the order; the read-only link covers the whole packet.
- Something to show but not sign (an invoice, an exhibit)? Add it as an attachment: in a packet a document with "attachment": true, or on POST /documents an "attachments" list (that turns the request into a packet behind the scenes; the response is still the document, with packet.id). Signers must open it; "acknowledge": "sign" or "initial" appends a receipt sheet instead. By default a packet with an attachment ends with a sealed Packet Acknowledgment: one signature per signer, every document listed with its SHA-256, always the last step. "acknowledgment": true/false overrides; GET /settings and PATCH /settings hold the workspace rule (always, attachments, never). When a packet completes, each person gets one "Packet completed" email (no per-document emails): the whole packet as one sealed PDF (every document followed by its certificate, then the packet audit; GET /packets/{id}/file, attached when small enough), a status page with per-document downloads, and their read link for an AI assistant. A review-only attachment also ends up sealed and timestamped, with a review record of who opened it. "signedNotices" in /settings (standalone by default) says whether the sender gets a "Signed by X" email per signature outside packets.
- POST /documents/{id}/recipients/{rid}/link returns a signing link, a read-only link the signer can hand to their own AI, and a ready-to-send message you can personalize with note or messageTemplate.
- Register a webhook to be told when a document is viewed, signed, declined or completed; or poll GET /documents/{id}.
- GET /documents/{id}/text returns the document text so you can read it without vision.
## Legal
Signatures follow the ESIGN Act and Alabama UETA: consumer consent, adopted signature, intent statement, full audit trail and a sealed PDF with a certificate of completion.
JSON version of this guide: https://sign.prodevgroup.com/api/v1/agent-accessFull guide (Markdown)
# Pro Sign AI: guide for AI agents
API-first electronic signature service. An AI agent can register for a person in seconds: send the person's name, email and the permissions you need; you get your token and its ID back immediately. The token does nothing until the person approves and signs on a one-page form in their browser (no typing); then it goes live with exactly the approved permissions. Then you send PDFs for legally valid e-signature, get webhooks, and download sealed results.
## Register for a person (takes seconds, no browser needed on your side)
Fastest, from a terminal (prints and saves your token and token ID; add --wait to block until the person approves):
```bash
curl -fsSL https://sign.prodevgroup.com/register.sh | bash -s -- --email jane@example.com --name "Jane Doe" --agent "Jane's assistant" --scopes documents:read,documents:write,documents:send
```
Or the same thing as one HTTP call (any language):
1. **Ask.** `POST https://sign.prodevgroup.com/api/v1/agent-access/requests` (no key needed) with JSON:
```json
{
"email": "jane@example.com",
"name": "Jane Doe",
"organizationName": "Doe Roofing",
"agentName": "Jane's assistant",
"purpose": "Send contracts for signature",
"requestedScopes": [
"documents:read",
"documents:write",
"documents:send",
"webhooks:manage"
],
"note": "Hi Jane, approve this and I will handle your contracts."
}
```
2. The person gets an email with one page: their name (editable), a permission table (Permission | Asked | Approve | Deny) prefilled from your request, the electronic-signing consent and a signature pad. They tap Approve and sign. Nothing is granted until then.
3. Until then every call with your token answers 403 token_pending_approval. Poll pollUrl every 30 to 60 seconds (tokenStatus becomes ACTIVE, status APPROVED), or simply retry; DECLINED or EXPIRED (7 days) means no.
4. Once approved the same token is live with exactly the permissions the person approved (they may approve fewer than you asked). Nothing to pick up.
5. Use the token as "Authorization: Bearer psk_live_..." on https://sign.prodevgroup.com/api/v1 or as the bearer on the MCP endpoint https://sign.prodevgroup.com/mcp. Keep the tokenId: it is how you refer to the token later (never send the token itself).
Need more later? POST https://sign.prodevgroup.com/api/v1/agent-access/changes with your key and the new requestedScopes; the person re-approves and re-signs; your key's permissions change in place.
## Temporary keys (for chats and harnesses that cannot keep a secret)
If your key would end up in a chat transcript or you have nowhere safe to keep it, ask for a temporary one: add "validForHours": 24 (any whole number from 1 to 168) to a new_account or new_token request. The token works for exactly that long after the person approves, then stops on its own (calls answer 401 token_expired). Ask again as often as you like: the first temporary token needs the signed authorization like any other; every later one is a one-tap approval email with no signature, so it costs the person almost nothing. The response and pollUrl carry temporary, validForHours and tokenExpiresAt.
```json
{
"action": "new_token",
"email": "jane@example.com",
"name": "Jane Doe",
"agentName": "Chat helper",
"requestedScopes": [
"documents:read",
"documents:send"
],
"validForHours": 24
}
```
## Actions (field `action`)
- `new_account`: first token for a person who has no account yet; token returned now, live after approve + sign
- `new_token`: another token on an existing account, for a different agent or application; token returned now, live after approve + sign
- `rotate_token`: fresh token with the same permissions returned now; one-tap confirm makes it live and stops the old one; needs tokenId
- `update_permissions`: change what a token may do; needs tokenId and requestedScopes (approve + sign)
- `remove_tokens`: revoke tokens; tokenIds, or omit to let the person pick (one-tap confirm)
- `create_company`: create a company with the person as owner; needs companyName (+ optional companyDetails). You get the company code (co_...) at once; the company and your company-bound token go live when the owner signs
- `join_company`: join an existing company for the person; needs companyId. The person signs for your company-bound token, and a company admin approves the membership with one tap; the token goes live when both are done
- `transfer_company`: the company owner signs over control to another active member; needs companyId and newOwnerEmail
Every action emails the person a one-page form. Default action is new_account for an unknown email, otherwise new_token.
## Place fields without vision (signature spots)
Place fields without vision. GET /documents/{id}/signature-spots scans the PDF for signature, initials, date, printed-name and title lines (labels such as SIGNATURE, SIGN HERE, BY:, DATE, INITIALS, PRINTED NAME, TITLE; drawn lines; runs of underscores) and returns a numbered list, each with a plain-English description such as "Signature line above 'Christina Kendrick / PRINTED NAME' in the block labeled 'Homeowner'", the paired date spot, and a ready box above the line (26pt for a signature, 18pt for a date). Lines that already carry printed text are skipped. Give each recipient fields like [{"spot": 1}, {"spot": 2}] instead of coordinates; the server sizes them. Fastest path: POST /documents with "detectSpots": true (it returns signatureSpots and never sends), choose spots, PATCH /documents/{id} with recipients, then POST /documents/{id}/send. Spots work in templates too ({"spot": n, "roleOrder": r}).
```json
{
"step1": {
"method": "POST",
"url": "/api/v1/documents",
"body": {
"title": "Full and Final Release",
"fileBase64": "<base64>",
"detectSpots": true
}
},
"step1_response_excerpt": {
"id": "doc_...",
"status": "DRAFT",
"signatureSpots": [
{
"spot": 1,
"page": 5,
"kind": "signature",
"description": "Signature line above 'Jason Kendrick / PRINTED NAME' in the block labeled 'Homeowner'",
"pairedDate": 2,
"box": {
"x": 54,
"y": 161,
"width": 320,
"height": 26
}
}
]
},
"step2": {
"method": "PATCH",
"url": "/api/v1/documents/doc_...",
"body": {
"recipients": [
{
"name": "Jason Kendrick",
"email": "jason@example.com",
"order": 1,
"fields": [
{
"spot": 1
},
{
"spot": 2
}
]
}
]
}
},
"step3": {
"method": "POST",
"url": "/api/v1/documents/doc_.../send"
}
}
```
## Options: alignment, date format, time zone
Per field: "align" left | center | right (default center for signatures and initials, left for text; anchors: {{signature:1:right}}). Per document: "dateFormat" (tokens YYYY YY MMMM MMM MM M DD D; default MM/DD/YYYY) and "timeZone" (IANA, default America/Chicago) decide how DATE fields print; the certificate and audit trail stay in UTC. Every recipient object in a response carries its own "fields" list as well as the document-level list.
## Test mode (sign every role yourself first)
"testMode": true on POST /documents (or /packets) makes a safe dry run: every email goes to the API key owner only, with [TEST] in the subject and the real recipient named, you may list yourself for every signer role (the duplicate-signer rule is off), the sealed PDF and the certificate carry a TEST mark on every page, and the document shows testMode: true in lists. Use it to sign each role yourself before sending a real document.
## Trust: trusted timestamps, disclosure versions, signer notes
Every sealed PDF carries an RFC 3161 trusted timestamp from a public authority (DigiCert, then Sectigo, then FreeTSA) over the seal signature, and the certificate names the authority and time of a second timestamp over the stamped document; GET /documents/{id} shows timestamp.status (done, pending, none) and the audit trail has "timestamped" events. If no authority answers, the document is still sealed, "timestamp unavailable" is recorded, and the worker retries. The electronic-signing disclosure is versioned: each signer's consent stores the version, and the exact text is at /legal/esign-disclosure/<version> (listed at /legal/esign-disclosure). While the active version is a draft, non-test sends answer 409 disclosure_draft. Signers can also leave the sender a note from the signing page without signing or declining (audit action note_left, emailed to the owner); a decline carries the signer's reason the same way. A signing link that has been used stays alive as a status page for as long as the record exists (who has signed, who is pending, completion time) with the current or sealed PDF at <link>/pdf and, once completed, the certificate at <link>/certificate; the read-only link for the signer's own AI keeps answering too (/read/<token> gains a Parties section, /read/<token>/certificate, and certificateUrl in /json). Only unused links that expired, and links the sender revoked, stop answering.
## Several documents in one request (packets)
Several documents in one request: POST /packets with {"title","orderLocked","documents":[{"title","fileBase64" or "templateId","recipients":[...],"useAnchors"}, ...],"send":true}. Each document keeps its own PDF, recipients, fields, signing order, seal and certificate. Each signer gets ONE email with ONE link that opens every document they are on; they agree to sign electronically once (it covers all of them), see a progress bar such as "1/3 complete" with numbered tabs, and are taken to the next document automatically after each signature. orderLocked:true means they must sign in the order listed (the page shows a single "Proceed" button); false means they pick from a numbered list in any order. The read-only link for an AI assistant covers all documents of the packet (/read/<token> lists them; /read/<token>/pdf?doc=2 is the second PDF). Signing order is packet-wide: "signingOrder": "SEQUENTIAL" sends the whole packet to the first signer group, and only when they have finished every document of theirs does it go to the next group; groups come from "signers": [{"email","order"}] (same number = at the same time, e.g. A first, then B and C together) or, when omitted, from each signer's lowest recipient order across the documents. PARALLEL (default) opens the packet to everyone at once. A document's own signingOrder still applies inside that document. POST /packets/{id}/signers/{email}/link gives you a packet link and a ready-to-send message; GET /packets/{id} shows per-document and per-signer progress; /remind and /void work for the whole packet. The same signer may be on only some of the documents. Attachments: a document with "attachment": true carries no recipients or fields; everyone on the packet sees it, each signer must open it before the last step (the page records the review with time, IP and device), and it concludes together with the packet. "acknowledge": "sign" or "initial" instead appends an "Acknowledgment of Receipt" sheet with a block per signer, and the attachment becomes a document they sign or initial. Packet Acknowledgment: when on (packet "acknowledgment": true/false, else the workspace rule: always, attachments (default), never via GET/PATCH /settings), every signer gets one more document, kind PACKET_ACKNOWLEDGMENT, created at send time after the last position: it lists every document they are on with page count, part (signed, initialed, provided for review) and SHA-256 of the file as sent, and takes one signature. It is always the last step: the API answers 409 packet_order until the signer has signed or initialed everything else and opened every attachment. Word it yourself by passing an object instead of true: "acknowledgment": {"title": "Close-out Acknowledgment", "intro": "...", "statement": "By signing, {{signer}} confirms receipt of all {{count}} documents of {{packet}} from {{sender}}."} (parts left out keep the service wording; the sheet notes which parts are yours). An attachment's receipt sheet takes "acknowledgeTitle" and "acknowledgeStatement" the same way (placeholders {{title}}, {{pages}}, {{sender}}, {{packet}}). Document kinds appear as "kind" on every document view.
```json
{
"title": "Smith roof replacement",
"orderLocked": false,
"send": true,
"documents": [
{
"title": "Contract",
"fileBase64": "<base64>",
"useAnchors": true,
"recipients": [
{
"name": "Jane Smith",
"email": "jane@example.com"
}
]
},
{
"title": "Assignment of benefits",
"fileBase64": "<base64>",
"useAnchors": true,
"recipients": [
{
"name": "Jane Smith",
"email": "jane@example.com"
}
]
},
{
"title": "Direction to pay",
"templateId": "tpl_...",
"recipients": [
{
"roleOrder": 1,
"name": "Jane Smith",
"email": "jane@example.com"
}
]
}
]
}
```
## Companies (multi-tenant)
A person has a personal workspace and can belong to any number of companies with the same email. Every documents/templates/webhooks call takes companyId (query or body) to run in that company; without it the call is personal. Company templates and documents are visible to company admins; personal ones are not. If the person is not an active member you get 403 not_a_member; an unknown code gives 404 no_company. GET /me lists the workspaces.
## Permissions you can ask for
- `documents:read`: See documents and their status. List documents, read who has signed, download completed files, audit trails and certificates.
- `documents:write`: Prepare documents. Upload PDFs, add signers and fields, edit drafts, delete drafts.
- `documents:send`: Send for signature. Send documents to people in your name, send reminders, issue signing links, void documents.
- `templates:read`: Use templates. See and use your saved templates.
- `templates:write`: Manage templates. Create and delete templates.
- `webhooks:manage`: Receive event notifications. Register web addresses that get notified when documents are viewed, signed or completed.
- `admin`: Full control. Everything above, plus managing members, other people's keys and organization settings.
## After you have a key
- API docs: https://sign.prodevgroup.com/api/v1/docs
- OpenAPI: https://sign.prodevgroup.com/api/v1/openapi.json
- MCP (Streamable HTTP, bearer = your key): https://sign.prodevgroup.com/mcp
- No vision and a PDF with no signature lines (an invoice, a letter)? Add "signaturePage" to POST /documents: you write the title and the statement, the server appends a page with a block per signer (signature line, date line, printed name, optional title) and places the fields itself; the response carries signaturePageText so you can check it. Example: {"enabled":true,"title":"Acknowledgment of Receipt","statement":"By signing below, I acknowledge that I received and reviewed Final Invoice PB-2026-KEN-FINAL dated October 12, 2026.","blocks":[{"recipient":1,"label":"Homeowner","printedName":"Jason Kendrick","fields":["signature","date"],"align":"left"},{"recipient":2,"label":"Homeowner","printedName":"Christina Kendrick"}]}.
- Put anchor tags in your PDF ({{signature:1}}, {{date:1}}, {{name:1}}, {{text:1:Policy number}}, {{checkbox:1}}) and set useAnchors:true; no coordinates needed. Add a last part left, center or right to align inside the box ({{signature:1:right}}); fields by coordinates take "align".
- Several PDFs for the same people? POST /packets with a documents list (each with its own recipients and fields). Every signer gets one link for all of them, consents once, sees "1/3 complete", and moves to the next document automatically. orderLocked:true forces the order; the read-only link covers the whole packet.
- Something to show but not sign (an invoice, an exhibit)? Add it as an attachment: in a packet a document with "attachment": true, or on POST /documents an "attachments" list (that turns the request into a packet behind the scenes; the response is still the document, with packet.id). Signers must open it; "acknowledge": "sign" or "initial" appends a receipt sheet instead. By default a packet with an attachment ends with a sealed Packet Acknowledgment: one signature per signer, every document listed with its SHA-256, always the last step. "acknowledgment": true/false overrides; GET /settings and PATCH /settings hold the workspace rule (always, attachments, never). When a packet completes, each person gets one "Packet completed" email (no per-document emails): the whole packet as one sealed PDF (every document followed by its certificate, then the packet audit; GET /packets/{id}/file, attached when small enough), a status page with per-document downloads, and their read link for an AI assistant. A review-only attachment also ends up sealed and timestamped, with a review record of who opened it. "signedNotices" in /settings (standalone by default) says whether the sender gets a "Signed by X" email per signature outside packets.
- POST /documents/{id}/recipients/{rid}/link returns a signing link, a read-only link the signer can hand to their own AI, and a ready-to-send message you can personalize with note or messageTemplate.
- Register a webhook to be told when a document is viewed, signed, declined or completed; or poll GET /documents/{id}.
- GET /documents/{id}/text returns the document text so you can read it without vision.
## Legal
Signatures follow the ESIGN Act and Alabama UETA: consumer consent, adopted signature, intent statement, full audit trail and a sealed PDF with a certificate of completion.
JSON version of this guide: https://sign.prodevgroup.com/api/v1/agent-access