Skip to content

GuidesGet started

Authentication & scopes

Every request is made with an API key. The key decides whether you’re in live or test mode, and it can only do what its scopes allow.

API keys

Create keys in Settings › Developers. The full key is shown once, when you create it — put it straight into your secret manager. After that, the key list shows only its prefix and id, like sa_live_7Gk2Pq9X….

KeyStarts withWhat it does
Livesa_live_Real envelopes. Emails and texts go out, signatures are legally binding, and envelopes count toward your plan.
Testsa_test_Test envelopes. No emails or texts, never billed or counted. See Test mode.

There’s one host for both modes: the key’s prefix decides which mode a call runs in. An id from the other mode returns 404 not_found.

When you create a key you can also limit it to an IP allowlist (calls from anywhere else return 403 ip_not_allowed) and give it an expiry date. A revoked key stops working within 30 seconds. Creating and revoking keys are recorded in the account’s activity log.

Keep keys on your server

Never put a key in a browser, a mobile app or a public repository. Anyone who has it can act as your account within its scopes. If a key leaks, revoke it in Settings › Developers.

Authenticating requests

Send the key as a bearer token in the Authorization header of every request, over HTTPS. The base URL is https://api.skanything.io/v1.

curl https://api.skanything.io/v1/account \
  -H "Authorization: Bearer $SA_API_KEY"
const res = await fetch("https://api.skanything.io/v1/account", {
  headers: { Authorization: `Bearer ${process.env.SA_API_KEY}` },
});
const account = await res.json();
if (!res.ok) throw new Error(`${account.code}: ${account.detail}`);
console.log(account.plan, account.billing_status);
import os
import requests

res = requests.get(
    "https://api.skanything.io/v1/account",
    headers={"Authorization": f"Bearer {os.environ['SA_API_KEY']}"},
)
res.raise_for_status()
account = res.json()
print(account["plan"], account["billing_status"])
using System.Net.Http.Headers;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SA_API_KEY"));

var account = await http.GetStringAsync("https://api.skanything.io/v1/account");
Console.WriteLine(account);

Never put the key in the URL. A key in the query string is refused with 400 api_key_in_query — and you should revoke it, because URLs end up in logs.

A call without a usable key gets 401: missing_api_key (no Bearer header), invalid_api_key (mistyped, cut short or unknown), api_key_revoked, api_key_expired or account_suspended.

Scopes

When you create a key, pick Full access or choose its scopes. A scope lets the key do one kind of thing, like reading envelopes or voiding them. Scopes can’t be changed later: to change what a key can do, create a new key and revoke the old one.

Scope reference

ScopeLets the keyNotes
Envelopes
envelopes:readList and see envelopes, their recipients, fields and documents, and the audit trail
envelopes:writeCreate and change drafts, add documents, recipients and fields, send, remind, make signing URLs, delete draftsDoesn’t include envelopes:read
envelopes:voidVoid envelopes
documents:downloadDownload documents, signed PDFs and certificates
Templates
templates:readList and see templates and their data schema
templates:writePreview a template as a PDFTemplates are created and edited in the app, not the API
Files · KeepAnything
files:readList and download files and folders, versions, the change feed and search; see grants, links and file requestsLive keys only
files:writeUpload, rename, move, copy, trash and restore files and foldersLive keys only
sharing:writeShare, make links and file requests, change and remove themLive keys only
Account
webhooks:manageSee, add, change and remove webhook endpoints, rotate their secrets, send tests, see and redeliver deliveries
Full access
*Every scope, including ones we add laterKeys made before scopes existed have Full access

How scopes combine

  • A write scope doesn’t include its read scope. A key that sends envelopes and checks on them needs both envelopes:write and envelopes:read.
  • envelopes:read also covers the audit trail.
  • Any key can call GET /v1/account. No scope needed.
  • More scopes arrive as the API grows. Full access picks them up automatically; scoped keys don’t.
  • Every endpoint in the API reference shows the scope it needs.

When a scope is missing

The call fails with 403 and the code insufficient_scope; required_scope names the scope the key needs. Checks run in this order, so you always get the first problem: key → IP allowlist → route → test mode → scope → rate limit. All errors use RFC 9457 problem details — see Errors.

POST /v1/envelopes/env_4f9c2d7e1b8a4c6f9e2d3b7a1c5e8f02/void

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
SignAnything-Request-Id: req_7c1e5a9d3f2b4e8a6c0d9f1b

{
  "type": "https://api.skanything.io/v1/docs/errors#insufficient_scope",
  "title": "Forbidden",
  "status": 403,
  "code": "insufficient_scope",
  "detail": "This API key needs the envelopes:void scope.",
  "request_id": "req_7c1e5a9d3f2b4e8a6c0d9f1b",
  "errors": null,
  "required_scope": "envelopes:void"
}

Test keys

Test keys take the envelope, template and webhook scopes and see the same templates and account as live keys. They create test envelopes, which are signed and sealed exactly like live ones — but no emails or texts go out (sign through a signing URL), and nothing is billed or counted. Test mode allows 30 requests a minute.

Files, folders and sharing need a live key

KeepAnything has no test mode, so a test key can’t be given files:read, files:write or sharing:write. Calls to files, uploads, folders, the change feed, search, grants, links and file requests with a test key return 403 test_mode_not_supported, even with Full access.

GET /v1/folders/company/children

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
SignAnything-Request-Id: req_2b8d4f0a6c1e4a9b7d3f5c0e

{
  "type": "https://api.skanything.io/v1/docs/errors#test_mode_not_supported",
  "title": "Forbidden",
  "status": 403,
  "code": "test_mode_not_supported",
  "detail": "Test keys can't read or change files: KeepAnything has no test mode. Use a live key for KeepAnything.",
  "request_id": "req_2b8d4f0a6c1e4a9b7d3f5c0e",
  "errors": null
}

Checking a key

GET /v1/account works with any key and returns the account’s plan, billing status, seats and this period’s envelope usage. It’s a good start-up check for your integration: it proves the key works before you send anything. On plans with unlimited envelopes, included_envelopes and included_remaining_this_period are null.

{
  "object": "account",
  "plan": "business",
  "billing_status": "active",
  "seats": 12,
  "current_period_start": "2026-11-01T00:00:00Z",
  "current_period_end": "2026-12-01T00:00:00Z",
  "cancel_at_period_end": false,
  "included_envelopes": 500,
  "included_used_this_period": 214,
  "included_remaining_this_period": 286,
  "pack_envelopes_remaining": 0
}

billing_status is one of none, trialing, active, past_due, suspended or canceled; plan is none without a subscription.