eContract Developers

Authentication

API keys, OAuth 2.1 tokens, scopes, the machine role cap and rate limits

Every request to https://api.econtract.online carries one credential in the Authorization header:

Authorization: Bearer <credential>
CredentialLooks likeUse it for
API keycl_live_ + 64 hex charactersYour own backend, scripts, headless agents, CI
OAuth access tokeneco_at_ + 64 hex charactersApps and AI clients that act for other people (Claude, ChatGPT, Cursor, your SaaS). See OAuth 2.1
User session (JWT)eyJ…The eContract web app itself. Not meant for integrations

API keys and OAuth tokens are machine credentials. They work in exactly one workspace, carry scopes, act with a capped role, and can never sign or decline a contract: signing is always done by a person.

API keys

Create a key

  1. Sign in as a workspace owner or admin.
  2. Open Dashboard → API Keys (/dashboard/api-keys) and click Create API Key.
  3. Name the key (for example "Production backend"), tick the scopes it needs and optionally set an expiry date.
  4. Copy the key. It is shown once; eContract stores only its SHA-256 hash.

A workspace can have up to 10 active keys. Keys issued by the production service start with cl_live_. Keys created on a non-production installation start with cl_test_; there is no sandbox mode yet, so a test key behaves exactly like a live key.

Use a key

curl https://api.econtract.online/api/v1/contracts \
  -H "Authorization: Bearer cl_live_your_key_here"
const response = await fetch("https://api.econtract.online/api/v1/contracts", {
  headers: { Authorization: `Bearer ${process.env.ECONTRACT_API_KEY}` },
});
const { data } = await response.json();
import os
import requests

response = requests.get(
    "https://api.econtract.online/api/v1/contracts",
    headers={"Authorization": f"Bearer {os.environ['ECONTRACT_API_KEY']}"},
)
contracts = response.json()["data"]

GET /api/v1/auth/me works with any valid key and is a quick way to check one.

Keep keys on the server. Never put a key in browser or mobile code. Store it in an environment variable or a secret manager.

Who a key acts as

A key acts as the person who created it:

  • Contracts it creates show that person as the creator, and the gateway passes their email to the services.
  • Its role is that person's current workspace role, capped (see Machine role).
  • The key stops working at once when it is revoked or expires, when the workspace is no longer active, or when its creator leaves the workspace.

The gateway caches the result of a key check for up to 30 seconds, so a revoked key can still be accepted for up to 30 seconds.

Manage keys

Key management is done by a signed-in workspace owner or admin (the dashboard does this for you). API keys and OAuth tokens cannot manage keys: they get 403 MACHINE_NOT_ALLOWED.

ActionEndpoint
CreatePOST /api/v1/workspaces/{workspaceId}/api-keys with { name, scopes, expires_at? }
ListGET /api/v1/workspaces/{workspaceId}/api-keys
GetGET /api/v1/api-keys/{id}
Update name, scopes or expiryPATCH /api/v1/api-keys/{id}
RevokePOST /api/v1/api-keys/{id}/revoke
RotatePOST /api/v1/api-keys/{id}/rotate
UsageGET /api/v1/api-keys/{id}/usage

Rotation

POST /api/v1/api-keys/{id}/rotate revokes the old key immediately (there is no grace period) and returns a new key with the same name, scopes and expiry. The new key is created by, and from then on acts as, the admin who rotated it.

To rotate without downtime, create a second key with the same scopes, deploy it, then revoke the old one.

OAuth access tokens

Apps that act for eContract users get tokens through the OAuth 2.1 authorization code flow with PKCE: the user signs in, picks a workspace and approves the scopes. Tokens are bound to that user, workspace and app, last one hour and can be refreshed. Details: OAuth 2.1.

An access token is accepted by the REST API only if it was issued for the API resource https://api.econtract.online. A token issued only for the MCP server (https://api.econtract.online/mcp) is rejected with 401 INVALID_TOKEN. Refresh tokens (eco_rt_…) never work as API credentials.

Scopes

Scopes limit what a machine credential may do. Ask for the smallest set you need.

ScopeAllows
contracts:readView contracts, their status, signed documents and audit trails
contracts:writeCreate, send, update and void contracts, and issue signing links
contracts:deleteDelete contracts
templates:readView contract templates
templates:writeCreate and update contract templates
templates:deleteDelete contract templates
signing:readView signing sessions and signer data
signing:writeManage signing sessions (never signs on your behalf)
workflow:readView workflow status
workflow:writeRun workflow actions
files:readDownload files
files:writeUpload and update files
files:deleteDelete files
webhooks:readView webhooks and their delivery logs
webhooks:writeCreate, update, test and delete webhooks
ai:useUse AI features such as contract analysis
offline_accessOAuth only: get a refresh token
  • Legacy names on older keys keep working: signing:create means signing:write, workflow:execute means workflow:write. Both names are still accepted when you create or edit a key.
  • The legacy wildcard * can no longer be created. Old keys that hold it still pass every scope check above (not offline_access).
  • When an OAuth client asks for no scope it gets contracts:read contracts:write templates:read files:read files:write ai:use.

Which scope a path needs

The gateway checks the scope from the path and method, first match wins. GET/HEAD need :read. DELETE of a resource itself (/contracts/{id}, /contract-templates/{id}, /files/{id}) needs :delete; removing a sub-resource (/contracts/{id}/signers/{signerId}, /contracts/{id}/files/{fileId}) and DELETE on families without a :delete scope need :write. Every other method needs :write.

Path prefixScope family
/api/v1/auth/menone (any valid credential; GET only)
/api/v1/aiai:use for every method
/api/v1/webhookswebhooks:read / webhooks:write
/api/v1/contract-templates/{id}/generatecontracts:write (creates a contract; the service also checks templates:read)
/api/v1/contract-templates/{id}/previewtemplates:read for every method
/api/v1/contract-templates, /api/v1/templatestemplates:*
/api/v1/contracts (incl. /signing-links, /document, /audit-trail)contracts:*
/api/v1/external-signers, /api/v1/signing-tokens, /api/v1/signing, /api/v1/sign, /api/v1/otp, /api/v1/saved-signaturessigning:read / signing:write
/api/v1/workflows, /api/v1/workflowworkflow:read / workflow:write
/api/v1/filesfiles:*
anything elsenot available to machine credentials (403 MACHINE_NOT_ALLOWED)

The service then checks the role permission as well: a machine credential needs both the role permission and the scope. A missing scope answers 403 INSUFFICIENT_SCOPE with a required_scope field; see Errors.

Machine role

A machine credential acts with the workspace role of its user (the key creator, or the person who approved the OAuth app), capped at contract_manager:

User's workspace roleRole of the key / token
owner, admin, manager, contract_managercontract_manager
membermember
anything elseviewer

The role is read again on every check, so demoting or removing the user takes effect for their keys and tokens too (within the 30-second gateway cache).

Webhook management is the one exception to the cap: /api/v1/webhooks needs the webhooks:* scope and a user who is currently an owner or admin of the workspace.

What machines cannot do

API keys and OAuth tokens never act as a person in the moments that need one:

  • Sign or decline a contract (POST /api/v1/contracts/{id}/sign, /decline), manage saved signatures or signing certificates: 403 HUMAN_ACTION_REQUIRED. Hand the signer their link instead (see AI agents).
  • Administer people: users, roles, invitations, notifications, organization settings and audit logs answer 403 SCOPE_REQUIRED or 403 MACHINE_NOT_ALLOWED.
  • Manage credentials and the account: API keys, OAuth consent, connected apps, workspaces and billing are for signed-in users only (403 MACHINE_NOT_ALLOWED).

Rate limits

Machine credentials are limited per principal: per API key, or per OAuth grant (one user + workspace + app, shared by all of its tokens).

WindowLimit
Minute60 requests
Hour1,000 requests
Day10,000 requests

The windows are fixed (they start at the full minute, hour and UTC day). Every response to a machine credential carries:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the window (normally the minute window)
X-RateLimit-RemainingRequests left in that window
X-RateLimit-ResetSeconds until that window ends (not a timestamp)

When a limit is exceeded the gateway answers 429 with Retry-After (seconds) and the headers describe the exceeded window:

{
  "statusCode": 429,
  "error": "Too Many Requests",
  "code": "RATE_LIMITED",
  "message": "Rate limit exceeded",
  "limit": 60,
  "window": 60
}

Rejected requests (for example 403 for a missing scope) count too.

In front of that, the gateway limits every client IP address to 120 requests per minute and 5,000 per hour across all credentials (3,000 per minute for /mcp and /oauth/*, whose users often share the IP of a hosted AI client). That limiter answers 429 with Retry-After and its own RateLimit-* headers.

The MCP server has its own limit of 60 requests per minute per principal and does not count against the REST limits above. The OAuth endpoints, fair-use caps (AI calls, emails) and per-endpoint limits are listed in Limits.

Good practice

  • Use webhooks instead of tight polling loops. If you poll, every 2–5 seconds while a contract is processing and every few minutes while it waits for signers is plenty.
  • On 429 wait for Retry-After, then retry with exponential backoff.
  • Send an Idempotency-Key on retried writes so a retry never creates a second contract.

On this page