---
name: botusign
description: Sign up for BotUsign as an agent from one message ("sign up with BotUsign and send this PDF to Jane"), send agreements for signature, hear when they are viewed, signed, declined, or completed, edit and resend an agreement, send reminders, and download the signed PDF and certificate. Use when asked to sign up with BotUsign and send a PDF for signature, to get a BotUsign account or key, send a contract or client agreement, check signing status, set up notifications so you do not have to poll, fix a typo and send it again, nudge a signer, fetch a signed document, place signature fields without coordinates (tags or anchors), create or archive templates, send one agreement to a list of people, or run an Org account (members, API keys, audit log).
---

# BotUsign

BotUsign is reached over MCP (`POST {APP_URL}/api/mcp`) or REST (`{APP_URL}/api/v1`). Both take the same API key:

```
Authorization: Bearer bsk_live_...
```

Any agent that can make an HTTPS call can use BotUsign: Hermes, a script, Cursor, your own agent. The shortest path needs no connector: it is "One message to your bot" below. With a key you can also use REST from anything, the Claude custom connector (Request headers), ChatGPT desktop and Codex (`config.toml`), and Grok CLI. Setup for each is in https://botusign.com/agents.md under What works today.

The Org plan ($1,200 a month for the whole organization, up to 250 people) includes the API, MCP, webhooks, templates, bulk send, and managing members and keys from the API. Signing links are not returned to API callers; BotUsign emails them to the signer (the one exception is a REST bulk call while email is not connected: see Bulk).

Tool names, inputs, and errors are in https://botusign.com/agents.md. After one person has approved you, you do not need the web app.

## One message to your bot (start here)

The person sends you one message, with a PDF: for example "Sign up with BotUsign (https://botusign.com/llms.txt) and send this PDF to Jane Doe <jane@acme.com> to sign. I sign it too, after her." That is the whole job. It needs no connector, plugin, OAuth screen or directory listing, only the ability to run a command or make an HTTPS POST. Do these five things, in this order, and keep going until you reach step 5 (`$BOTUSIGN` is https://botusign.com):

1. Start the sign-up, with no key: `curl -s -X POST $BOTUSIGN/api/v1/agent/signup -H "Content-Type: application/json" -d '{"agent_name":"<your name>"}'`. Keep `device_code` to yourself.
2. Give the person `verification_url` in your reply and ask them to open it. One link, one visit: they sign in with Google, press Approve, and then enter a card on the Stripe page that follows. Tell them that is all they have to do.
3. Poll `POST $BOTUSIGN/api/v1/agent/signup/poll` with `{"device_code":"…"}` every 3 seconds until `status` is `approved`; that reply carries `api_key` once. Store it. Then call `GET $BOTUSIGN/api/v1/billing` (with `Authorization: Bearer <api_key>`) every few seconds until `plan` is `org`.
4. Create and send, in one call. The person's PDF is `contract.pdf`:

```bash
curl -s -X POST $BOTUSIGN/api/v1/envelopes -H "Authorization: Bearer $BOTUSIGN_KEY" -H "Idempotency-Key: services-agreement-jane" \
  -F file=@contract.pdf -F title="Services Agreement" -F 'to=Jane Doe <jane@acme.com>' -F sender_signs=true
```

5. Tell the person what happens next: Jane gets an email with her own link; when she has signed they get "Your turn to sign: Services Agreement" with their own link; when everyone has signed, both get the finished agreement by email with the signed PDF and the certificate attached. Report the envelope `id` and `envelope.awaiting`.

Variations. `to` takes several people, `Name <email>, Name <email>`, who all sign at the same time; use JSON `recipients` with `order` for a sequence. Leave out `sender_signs=true` when the person only sends; `sender_signs=first` makes them sign before everyone. If the file is reachable by a public HTTPS address, send `-F pdf_url=https://…/contract.pdf` instead of `file`. JSON works too (`pdf_url` or `pdf_base64`, `recipients`, `"sender_signs": true`), and MCP `create_envelope` takes the same arguments. If you already have a key, skip steps 1 to 3.

If you cannot run a command or make an HTTPS POST (many chat apps have no tool for it), you cannot send for the person: say so, and point them to https://botusign.com/app/new.
Limits to plan around: the PDF is up to 10 MB and 100 pages. REST works from anything that can make an HTTP call; a token from the chat-app sign-in works on MCP only.

## Sign up as an agent

If you have no key, get one yourself. A person has to open one link: they sign in with Google, press Approve (step 2), and, if the organization has no plan yet, enter a card on the Stripe page that follows (step 4). You never sign in with Google or handle a card yourself. `$BOTUSIGN` is the base URL (`{APP_URL}`).

1. Start, with no key:

```bash
curl -s $BOTUSIGN/api/v1/agent/signup -H "Content-Type: application/json" \
  -d '{"agent_name":"Contracts agent","org_name":"Acme Co"}'
```

   `org_name` is optional. The reply has `device_code` (secret, keep it), `user_code`, `verification_url`, `interval` (3 seconds) and `expires_in` (900).

2. Give the person `verification_url` and ask them to open it. They sign in with Google, press Approve, and are taken straight to Stripe's page for the card; the page then returns them to a "done" page. If you drive a browser that is already signed in to Google, open the address yourself and press Approve; a person still enters the card. Tell the user the page asks them to choose an organization.

3. Poll until it is not `pending`:

```bash
curl -s $BOTUSIGN/api/v1/agent/signup/poll -H "Content-Type: application/json" \
  -d '{"device_code":"<device_code>"}'
```

   `approved` carries `api_key` once. Store it right away: a later poll says `consumed`. `denied` means stop. `expired` means start again.

4. Wait for the plan. The person normally pays on the page that follows Approve; call `GET /api/v1/billing` until `plan` is `org`. If they closed that page, `POST /api/v1/billing/checkout` with `{"plan":"org"}` returns `checkout_url` to give them: they enter a card once on the Stripe page and the plan usually turns on within seconds. Poll `GET /api/v1/billing` until `plan` is `org` (each poll also asks Stripe directly, so a late confirmation does not hold the plan off). If it is still `none` after two minutes, tell the person and point them to `support@botusign.com`; do not start a second checkout.

5. Start sending (below).

A new organization has no plan until someone pays. Until then only `get_billing`, `create_checkout_session`, `create_portal_session` and `get_usage` work, and every other call returns `403 plan_required` with an `upgrade` field that names the billing endpoints. A person can also create a key under Settings, API and MCP once the organization has a plan.

## Billing

- `get_billing` (`GET /api/v1/billing`) shows the plan, allowance used, and `next_steps`. It works with no plan.
- `create_checkout_session` (`POST /api/v1/billing/checkout`, `{ "plan": "org" }` (Org is the only plan on offer, so you can leave `plan` out)) returns `checkout_url`. Give it to a person; they pay and the plan usually turns on within seconds. If the organization has already paid, it answers `409 already_subscribed` rather than starting a second charge. `503 billing_not_configured` means checkout is not connected yet; say so and offer the web app.
- `create_portal_session` (`POST /api/v1/billing/portal`) returns `portal_url` to update the card, see invoices or cancel (paid plans).

## Send

`create_envelope` with the PDF (`pdf_base64`, or `pdf_url` for a public HTTPS address; on REST a `multipart/form-data` upload is the easiest, see "One message to your bot") and the people. Add `"sender_signs": true` when the person behind the key signs too: they are added after everyone else, you do not need their email, and they are emailed "Your turn to sign" when it is their turn:

```json
{
  "title": "Services Agreement",
  "pdf_base64": "…",
  "recipients": [
    { "name": "Ada Okafor", "email": "ada@example.com", "role": "signer", "order": 1 },
    { "name": "Hannah Lee", "email": "hannah@acme.example", "role": "signer", "order": 2 }
  ],
  "external_id": "enrollment-2291",
  "metadata": { "project": "12" },
  "idempotency_key": "enrollment-2291"
}
```

Omit `fields` and BotUsign places them for you (see "Place fields without coordinates"). Different `order` values sign one after another. Report the envelope `id` and who it is waiting on (`envelope.awaiting`). The signer's email comes in the name of the person who made the key, and their replies go to that person. When everyone has signed, every signer, every cc and the sender get "Completed: <title>" by email with the signed PDF and the certificate attached (and a download link that works for 30 days).

`create_envelope_from_template` fills a template (`list_templates`, then give a person for each role plus the prefill keys). `bulk_send_from_template` sends up to 500 rows. See "Templates" and "Bulk" below.

If you get `503 email_not_configured`, tell the owner that email delivery for botusign.com (Cloudflare Email Sending) is not connected or not verified yet. Do not retry.

## Place fields without coordinates

Never guess x and y. Leave out `fields` and BotUsign places them, using the first that applies: your `fields`, text tags in the PDF, your `placements`, the signature lines the PDF already has, then the last page. The response's `placement` block shows `method` (`explicit`, `tags`, `anchors`, `detected` or `default`), a `summary` count, and every field's position with `source`, `confidence` and a one-line `why`. If it says `default`, there was nothing to go on: tell the person, or fix the fields as below.

- Tags: write `{{sign}}` (signer 1), `{{sign:2}}`, `{{initial:2}}`, `{{date:2}}`, `{{text:1:Start date}}`, `{{check:1}}` in the PDF where the field goes. Signers are counted in `recipients` order, not counting `cc`. Tag text is covered in the copy people sign. Add `|key=project` to a text tag for a prefill key, `|required=false` for optional.
- Anchors, when the PDF has text but no tags: `placements: [{ "recipient_index": 0, "type": "signature", "anchor": "Employee signature" }, { "recipient_index": 0, "type": "date", "anchor": "Employee signature", "position": "right" }]`. Options: `occurrence` (`first`, `last`, a number), `page`, `position` (`right`, `below`, `above`, `on`), `offset_x`, `offset_y`, `width`, `height`. Use `placements` or `fields`, not both.
- Look first with `analyze_document` (`pdf_base64` or `envelope_id`, free, changes nothing): it lists text blocks, tags and signature lines per page and `method_if_sent_now`.
- `422 anchor_not_found` lists `closest_matches`: pick one and retry. `422 invalid_tag` names the tag (a signer number with no matching signer, or a role name outside a template). Nothing was created, so fix and retry.

PDF requirements: a text PDF, not a scan (if you can select text in a viewer, it works); up to 10 MB and 100 pages; no password; rotated pages are skipped. A scan has no words to read, so it only gets the standard position on the last page.

How blanks are recognised: a blank is a drawn line (also one drawn in several pieces, dashes or dots end to end), typed underscores or dots, an empty box, or a label standing alone with room beside it. It needs a short label made of label words: `Signature`, `Sign`, `By:`, `Date`, `Initials`, `Name`, `Title`, with a party (`Employee signature`, `Landlord initials`) or a recipient's name (`Ada Okafor signature`). Sentences that mention signatures, headings like `SIGNATURES`, and `witness` or `notary` lines are skipped. `Name` and `Title` lines get no field of their own. `analyze_document` shows what was recognised (`signature_lines`), what it would place (`placed`, `suggested_fields`, each with a `why`), and what it skipped (`ignored_text`).

Fix a field that landed wrong, on a draft (`create_envelope` with `send: false`, then `send_envelope` when it is right). Positions are fractions of the page from the top left.

- `get_fields` (`GET /api/v1/envelopes/{id}/fields`) with `{ "envelope_id": "…" }` returns `{ envelope_id, status, page_count, editable, how_to_change, recipients: [{ index, id, name, email, role, signs }], fields: [{ id, type, page, x, y, w, h, required, label, key, value, recipient_index, recipient_id, recipient_email, recipient_name }] }`, plus `signers_without_signature` when a signer has none yet (BotUsign adds one at send).
- `add_field` (`POST …/fields`, 201): `{ "recipient_email": "ada@example.com", "type": "date", "page": 1, "x": 0.5, "y": 0.7 }`. Use `recipient_email` or `recipient_id` (a signer, not `cc`). `type` is `signature`, `initials`, `date`, `text` or `checkbox`. `w` and `h` default to the usual size; `required`, `label`, `key`, `value` are optional. Returns `{ field, field_count }`.
- `update_field` (`PATCH …/fields/{field_id}`): send only what changes, for example `{ "x": 0.12, "y": 0.62, "w": 0.35, "recipient_email": "hannah@acme.example" }` to move, widen and reassign. Returns `{ field, changed: ["x","y","w","recipient"], field_count }`.
- `remove_field` (`DELETE …/fields/{field_id}`) returns `{ removed: true, field_id, field_count }`, plus `signers_without_signature` and a `note` if that signer is left with no signature field.
- Errors: `400 invalid_field` (past the page edge, page not in the document, `cc` recipient), `400 nothing_to_update`, `400 limit_reached` (200 fields), `404 not_found` (agreement, field or person not found), `409 invalid_state`.
- After sending, these tools answer `409 invalid_state` and tell you to use `revise_envelope`: pass it the edited `fields` list from `get_fields` (it already has that shape). A revision makes everyone sign again and uses one round (the first 199 are free).

## Templates

You can make your own. `create_template` takes `name`, `pdf_base64`, and `roles` (`[{ "key": "client", "name": "Client", "role": "applicant", "order": 1 }, { "key": "countersigner", "name": "Countersigner", "order": 2 }]`). Fields are placed like above, with role keys in tags: `{{sign:client}}`, `{{date:client}}`, `{{text:client:Project|key=project}}`. A text field with a `key` is a prefill key. The response has the template and its `placement`.

- `update_template` changes name, roles, fields or the PDF; sent envelopes keep their old version. `archive_template` retires one (`list_templates` stops showing it; using it returns `409 template_archived`). `create_template_from_envelope` turns an envelope you prepared into a template, with `roles` mapping each recipient email to a role key.
- Use a template with `create_envelope_from_template`: a person for each role, plus `prefill`.

## Bulk

For a list of people use `bulk_send_from_template` (one template, up to 100 rows, 500 on Org) or `bulk_create_envelopes` (one PDF, `rows` or `recipients_csv`). The CSV needs a header: `name,email` (more people as `name_2,email_2`), optionally `role`, `order`, `external_id`, `meta_<key>`.

- Always send a call `idempotency_key` and give every row an `external_id`. Each row also has its own key (`<call key>:<row number>` unless you set `idempotency_key` on the row), so a crash and a retry with the same key never sends anyone twice. A row whose `external_id` is already on a live envelope comes back `duplicate`.
- Read the result: `{ batch_id, total, sent, failed, duplicate, rows: [{ row, status: sent | draft | failed | duplicate, envelope_id, external_id, error, delivery }] }`. The call returns 200 even when some rows failed. Fix failed rows (`error.code` says why) and send only those again.
- `delivery: "link"` means email was unavailable and no email went out. Over REST the first response carries `signing_links` on those rows (shown once, never stored; pass them on yourself). Over MCP you get no links: connect email, then `remind_batch`.
- `get_batch` (`batch_id`) shows who viewed and signed. `remind_batch` nudges everyone still pending (once an hour per person). Match on `external_id`.
- `limit_reached` (422) means more than 500 rows. `invalid_csv` (400) means the header is missing or lacks `name` or `email`.

## Run the organization

These need an active plan and a key made by an owner or admin (`403 forbidden` for a key made by a member). Tools: `list_members`, `invite_member` (`email` for one person, or `emails` for 1 to 50 addresses; `role` admin or member for all of them; a list answers `{ invited, skipped, results }` with a result for every address, so read `skipped`: `invalid_email`, `already_member`, `already_invited`, `member_limit`; an organization holds up to 250 people and a list never goes over), `set_member_role`, `remove_member` (by `member_id` or `email`), `list_api_keys`, `create_api_key` (`name`), `revoke_api_key`, `get_audit_log` (`envelope_id`, `since`, `limit`, `cursor`; keep going while `next_cursor` is not null). REST: `/api/v1/org/members`, `/api/v1/org/api-keys`, `/api/v1/org/audit`.

- `create_api_key` returns the `secret` once. Store it in a secret store; never paste it into a message or log. A key cannot remove the person it belongs to, and the owner cannot be removed or demoted.
- Invited people join when they sign in to botusign.com with that email; `email_sent: false` means the invitation email did not go out, but they can still join.

## Retry safely

Always send an idempotency key on `create_envelope`, `create_envelope_from_template`, `send_envelope`, `revise_envelope`, `resend_envelope`, `bulk_send_from_template` and `bulk_create_envelopes`: the `idempotency_key` argument, or the `Idempotency-Key` header on REST. Use something tied to your own record (an enrollment id), not a new random value per retry. If a call times out, repeat the exact same request with the same key: you get the first response back (`idempotent_replay: true` over MCP, `Idempotent-Replay: true` on REST) and nothing is sent or counted twice. The same key with a different request is `422 idempotency_key_reuse`. `409 idempotency_in_progress` means the first request is still running: wait a couple of seconds and repeat it. Keys last 48 hours and belong to the organization.

`external_id` (your own id, up to 200 characters) and `metadata` (up to 20 string pairs) go on the envelope and come back on every response and webhook. `list_envelopes` with `external_id` finds the envelope for one of your records.

## Check

`get_envelope` shows each person's status, when they first opened their link (`viewed_at`), who was emailed (provider message ids), and whether the audit chain verifies. `list_envelopes` with `awaiting_signature: true` finds everything still open. It returns up to 100 at a time: while `next_cursor` is not null, send it back as `cursor` to get the next page. Read `available_actions` in the response instead of guessing what is allowed.

## Hear without polling

Register a webhook once and stop polling. `create_webhook` with `{ "url": "https://your-agent.example/botusign" }` returns a `signing_secret` once: store it. Your endpoint then receives a signed POST for `envelope.sent`, `envelope.viewed`, `recipient.viewed` (each person's first open, with `viewedAt`), `recipient.signed` (one per signer, with `signed`, `signers`, and `awaiting`), `envelope.declined`, and `envelope.completed` (with 24 hour links to the signed PDF and the certificate). Every event has `data.envelope.external_id` and `data.envelope.metadata`, so you can match it to your record. Check `BotUsign-Signature` (HMAC-SHA256 of `{t}.{raw body}`) before acting, reply 2xx within 8 seconds, and de-duplicate on the event `id`: failed deliveries retry for about nine hours. `test_webhook` proves the endpoint works, `list_webhooks` shows recent deliveries and errors, and `delete_webhook` removes it. Endpoints must be public HTTPS URLs. If you cannot receive HTTP, `list_envelopes` with `awaiting_signature: true` still works.

## Fix and send again

Do not create a new envelope to fix one. Every envelope gets 199 free revisions or resends after the first send.

- Content changed (typo, new date, new PDF, different person): `revise_envelope` with only what changed (`title`, `message`, `pdf_base64`, `recipients`, `fields`). Everyone signs again and earlier links stop working. It can also replace `external_id` and `metadata` (`null` clears them), but those alone are not a revision.
- Nothing changed, the signer lost the email or the link expired: `resend_envelope`. Signatures already collected stay. Fresh links go to the person whose turn it is.
- Just a nudge: `remind`. No new link, no new round, one per signer per hour. `nothing_to_remind` (409) means nobody is waiting.

`rounds_remaining` starts at 199 after the first send and drops by one on every revise or resend. When it is 0, the next revise or resend returns `new_envelope: true` with a new `envelope.id` and `supersedes` set. Tell the user the old id was replaced. That new envelope counts as a new send.

## Download

Only when `status` is `completed`. `get_documents` returns 15 minute links to `signed_pdf` and `certificate_pdf` plus the original, each with a SHA-256. If your client cannot follow links, fetch the bytes over REST with the same key: `GET /api/v1/envelopes/{id}/documents/signed` and `…/certificate`. Download both and file them together. `get_evidence` returns the full audit log as JSON.

## Cost

BotUsign charges once for the initial send of a contract. Revisions and resends of that same contract are included, up to 199 revisions. A new contract is a new charge.

A new envelope is counted once, at first send. The organization gets 50,000 new agent envelopes each month, then $0.10 each (a company that sends hundreds of packets a month never reaches it). The first 199 revisions or resends of an envelope, reminders, and drafts are free. `get_usage` (or `get_billing`) shows the count; check it before a big batch. `create_envelope` and `send_envelope` return a `billing` block that says what that send cost.

## Rules

- Never guess an email address. Ask.
- Do not void an envelope unless asked. `void_envelope` tells signers the link no longer works.
- Never paste `device_code` or `api_key` into a message to a person or a log. The person only needs `verification_url`.
- BotUsign signatures are ESIGN/UETA-style electronic signatures.
