# API quick start

Create an API key, make your first request, and learn how errors, limits, retries and pagination work in the eSignStack API.

## Create an API key

1. Open **Developers → API keys** and click **Create key**.
2. Enter a **Key name** and, if you want, an **Expiry**.
3. Choose the **Key mode**: **Test** for sandbox data or **Live** for real envelopes.
4. Choose abilities. **Read only**, **Send envelopes** and **Full access** fill in common sets; you can change them one by one.
5. Copy the secret from the dialog. It's shown once and can't be retrieved later.

Workspace owners, admins and developers can manage keys. Developers can only issue keys with the `webhooks:read`, `webhooks:write` and `keys:write` abilities. To revoke a key, open its **⋯** menu and click **Revoke**, or call `DELETE /api/v1/api_keys/{key}`.

Keep secrets on your server. Never put a key in browser code, a mobile app, logs or source control.

### Key types

| Prefix      | Key                     | Scope                                                     |
| ----------- | ----------------------- | --------------------------------------------------------- |
| `rk_live_`  | Workspace key, live     | One workspace. Created under **Developers → API keys**.   |
| `rk_test_`  | Workspace key, test     | The same workspace's sandbox data.                        |
| `sk_live_`  | Platform secret key     | Every workspace the platform owns. Created in the platform console. |
| `sk_test_`  | Platform secret key, test | Sandbox data of the platform's workspaces.              |

A key only sees data in its own mode: a test key can't read live envelopes, and a live key can't read test ones. Test keys create and send watermarked test envelopes, from a file or a template, and nothing counts toward your limits. SMS verification is refused in test mode (`422`), and changing brand kits, folders, filing rules or identity providers needs a live key (`403 live_key_required`). See [Sandbox](/docs/sandbox) for more. A platform key acts inside a workspace when you send `Workspace-Id: ws_…`; see the [platform integration guide](/docs/platform-integration-guide).

### Abilities

Every key lists its abilities explicitly. There are no wildcards.

| Ability                                | Allows                                                         |
| -------------------------------------- | -------------------------------------------------------------- |
| `envelopes:read`                       | Read envelopes, recipients, documents and fields               |
| `envelopes:write`                      | Create and edit drafts, preview, duplicate, signing sessions, archive and restore, place legal holds |
| `envelopes:send`                       | Send, remind, extend and correct envelopes                     |
| `envelopes:void`                       | Void envelopes                                                 |
| `templates:read`, `templates:write`    | Read or change templates                                       |
| `folders:read`, `folders:write`        | Read or change folders and filing rules                        |
| `files:write`                          | Upload files                                                   |
| `artifacts:read`                       | Download completed PDFs, one by one or as a ZIP                |
| `webhooks:read`, `webhooks:write`      | Read events; manage webhook endpoints                          |
| `brand_kits:read`, `brand_kits:write`  | Read or change branding                                        |
| `usage:read`                           | Read usage counters                                            |
| `workspaces:export`                    | Export workspace data                                          |
| `audit:read`                           | Read the workspace activity log (`GET /audit_events`, live keys only). Never in a preset |
| `keys:write`                           | List, create and revoke keys                                   |

Platform keys can also hold `platform:read` (read the platform's settings and members), `workspaces:read`, `workspaces:write`, `login_links:write` and `identity_providers:write`. A key created through the API (`POST /api/v1/api_keys`) has the same mode as the key that created it, so a test key can only issue test keys. It also can't have abilities its creator doesn't have, and can't outlive it.

## Make your first request

The base URL is `https://esignstack.app/api/v1`. Send the key as a bearer token. This request returns the key's own details and needs no ability:

```bash
curl https://esignstack.app/api/v1/api_keys/current \
  -H "Authorization: Bearer $ESIGNSTACK_KEY"
```

Objects use prefixed IDs such as `env_…`, `rcp_…`, `tpl_…` and `file_…`. JSON keys are snake_case. Every object has `object`, `id`, `created` (a UTC timestamp such as `2026-11-02T18:15:00+00:00`) and `livemode`.

## Create an envelope from a template

Get the template's role labels with `GET /api/v1/templates/{template}`, then map each person to a role:

```bash
curl -X POST https://esignstack.app/api/v1/templates/$TEMPLATE_ID/envelopes \
  -H "Authorization: Bearer $ESIGNSTACK_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-456-create" \
  -d '{
    "name": "Acme service agreement",
    "recipients": [
      {"role_label": "Client", "name": "Jane Doe", "email": "jane@example.com"}
    ],
    "external_id": "order-456",
    "send": false
  }'
```

Roles that the template always sends to the same person (`recipient_email` on the role) are filled in for you, so you can leave them out. Include one to send that role to someone else on this envelope.

`send` defaults to `true`. With `send: false` the envelope stays a draft so you can check it, then send it with `POST /api/v1/envelopes/{envelope}/send`. `values` fills template fields by label. To start from your own PDF instead, see the [Envelopes API](/docs/envelopes).

## Idempotency keys

Send an `Idempotency-Key` header (up to 255 characters) on POST requests, and reuse it when you retry the same request after a timeout.

- A repeat of a completed request returns the stored response with `Idempotent-Replayed: true`.
- The same key with a different request returns `409 idempotency_key_reused`.
- A request still running returns `409 idempotency_key_in_progress` with `Retry-After`.
- Requests that fail don't use up the key. Keys are kept for 24 hours.
- The header is ignored on `POST /verify`, `POST /login_identities/{id}/deprovision` and `POST /identity_providers/{idp}/subjects/{sub}/deprovision`.

## Errors

Errors return JSON with an `error` object:

```json
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_failed",
    "message": "The name field is required.",
    "param": "name",
    "doc_url": "https://esignstack.app/docs/api#validation_failed",
    "request_id": "req_01J…"
  }
}
```

| Status | Common codes                                                      |
| ------ | ----------------------------------------------------------------- |
| 400    | `invalid_request`, `workspace_required` (a platform key without `Workspace-Id`), `api_version_unsupported` |
| 401    | `authentication_required`: missing, invalid or expired key        |
| 403    | `permission_denied`: missing ability, suspended workspace; `live_key_required` |
| 404    | `resource_missing`: not found in this workspace and mode          |
| 409    | `resource_conflict`, `correction_requires_void`, idempotency codes |
| 422    | `validation_failed`                                               |
| 429    | `rate_limit_exceeded`                                             |

Every response has a `Request-Id` header. Include it when you contact support.

## API envelope limits

API and MCP envelopes have their own monthly allowance. An envelope counts when it's sent through the API or MCP in live mode, or when the API issues a signing session for an envelope that was sent another way. Test envelopes never count.

| Plan     | API and MCP envelopes                                        |
| -------- | ------------------------------------------------------------ |
| Free     | Part of the plan's 5 envelopes a month (app and API together) |
| Starter  | 50 a month                                                   |
| Pro      | 200 a month                                                  |
| Platform | 500 a month included, then 25¢ each                          |

Yearly plans get 12 months of allowance up front (for example, 600 on Starter). On Platform, envelopes past the allowance are billed as you go. You can set a monthly spending cap on extra envelopes on the **Billing** page.

When a send is refused, the API returns `422` with code `validation_failed`, `param` set to `document`, and a `quota` object that says which limit was hit and when it resets:

```json
"quota": { "code": "api_envelope_limit_reached", "resets_at": "2026-11-01T00:00:00Z" }
```

| `quota.code`                  | Meaning                                                 |
| ----------------------------- | ------------------------------------------------------- |
| `envelope_limit_reached`      | The Free plan's 5 envelopes are used                    |
| `api_envelope_limit_reached`  | The plan's API allowance is used                        |
| `api_spending_cap_reached`    | Platform: the spending cap on extra envelopes is reached |
| `api_overage_unavailable`     | Platform: extras are paused, for example an unpaid invoice |

`GET /api/v1/usage` (ability `usage:read`) returns current usage, including `api_envelopes` with `used`, `limit`, `remaining` and `resets_at`. A platform key without `Workspace-Id` also gets `platform`, the platform's own allowance, extras and spending cap.

## Rate limits

Each key can make 100 requests a second, and each workspace 6,000 requests a minute across all its keys. Responses carry `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy`. Over the limit you get `429` with `Retry-After`; wait that long before retrying.

## Versions

The current version is `2026-10-14`. Each key is pinned to the version current when it was created. Send `Api-Version` to choose a version per request; responses echo the version used. New fields can appear in responses at any time, so ignore fields you don't recognize.

## Pagination

List endpoints return `object: "list"`, `data`, `has_more` and `url`, newest first. Use `limit` (1 to 100, default 20) and pass the last ID you received as `starting_after` to get the next page, or the first ID as `ending_before` to go back. Some endpoints take `expand[]`, for example `expand[]=recipients` on envelopes.

The full list of endpoints, fields and filters is in the [API reference](/docs/api).
