Docs / Getting started

Authentication and API keys

How Tend authenticates requests, what the tnd_live_ and tnd_dev_ prefixes mean, and how to store, scope and rotate keys safely.

Last updated February 18, 2026

#Bearer authentication

Every request to the Tend API must carry an API key in the Authorization header using the Bearer scheme. There are no query-string keys, no cookies, and no basic auth. This is intentional: URLs end up in logs, proxies and browser history, and headers mostly do not.

Text
Authorization: Bearer tnd_live_4Kp9vXc2RtQe7WnJ

Requests are made over HTTPS to https://api.tendcomputer.com/v2. Plain HTTP connections are refused rather than redirected, so a misconfigured client fails loudly instead of sending a key in the clear.

cURL
curl https://api.tendcomputer.com/v2/schedules?limit=25 \
  -H "Authorization: Bearer $TEND_API_KEY"

#Key prefixes and environments

A key's prefix tells you which environment it belongs to, and the API enforces that boundary. Development keys begin with tnd_dev_. Production keys begin with tnd_live_. The two environments live inside the same project but are otherwise separate worlds: a tnd_dev_ key cannot see, modify or trigger anything created with a tnd_live_ key, and the reverse is also true.

PrefixEnvironmentTypical useCan see the other environment?
tnd_dev_DevelopmentLocal work, staging, CINo
tnd_live_ProductionDeployed servicesNo

Because of this isolation, a job ID from one environment returns 404 resource_not_found in the other. That error is correct behavior and not a bug: the resource genuinely does not exist from that key's point of view. Schedule names are unique per environment, so you can have a nightly-usage-report schedule in both development and production without a schedule_name_taken conflict.

#Scopes and least privilege

A key is created with a set of scopes, and it can only perform operations those scopes allow. Prefer the narrowest scope that works. A service that only enqueues jobs does not need permission to delete schedules, and a dashboard that only displays run history does not need to write anything.

ScopeAllows
jobs:readList and retrieve jobs, runs and attempts
jobs:writeCreate, update and cancel jobs
schedules:readList and retrieve schedules
schedules:writeCreate, update, pause and delete schedules
keys:manageCreate and revoke other keys (dashboard and Scale plan role-based access)

Attempting an operation without the required scope returns 403 insufficient_scope. This is distinct from 401 invalid_api_key, which means the key itself is unknown, revoked, or does not begin with tnd_live_ or tnd_dev_. A missing or malformed Authorization header returns 401 missing_api_key.

JSON
{
  "error": {
    "code": "insufficient_scope",
    "message": "This key does not have the schedules:write scope.",
    "request_id": "req_01J8QK3T9DXV5M2WBA7E4NHCYR"
  }
}

#Storing keys safely

Treat a tnd_live_ key like a database password, because that is effectively what it is: anyone holding it can schedule work that calls your endpoints and consumes your run quota. The practices below are not exotic, but they are the ones that get skipped.

  • Load keys from environment variables or a secrets manager. Never commit them to source control, including private repositories.
  • Use separate keys per service and per environment, so revoking one does not take down everything.
  • Never embed a key in a browser bundle or mobile app. Anything shipped to a client is public. Route through your own backend instead.
  • Keep tnd_live_ keys out of CI logs. Mask them in your CI provider's secret settings.
  • Give CI and staging a tnd_dev_ key. There is rarely a good reason for a staging pipeline to hold production credentials.

Tend participates in secret scanning with major code hosting providers. If a tnd_live_ key appears in a public repository, we are notified, revoke the key automatically, and email the project owner. This is a safety net, not a plan.

#Rotating and revoking keys

Rotate keys on a schedule you can defend, and immediately after any suspected exposure or when someone with access leaves the team. Rotation without downtime is straightforward because a project can hold multiple active keys at once.

  • Create a new key with the same scopes in the dashboard, or with the API using a key that holds keys:manage.
  • Deploy the new key to your services and confirm requests succeed with it.
  • Revoke the old key. Revocation propagates globally within a few seconds.
  • Watch for 401 invalid_api_key responses in your logs, which indicate a service still using the old key.
cURL
# Create a replacement key
curl -X POST https://api.tendcomputer.com/v2/keys \
  -H "Authorization: Bearer $TEND_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "billing-service-2026-03", "environment": "live", "scopes": ["jobs:write", "jobs:read"]}'

# Revoke the old one
curl -X DELETE https://api.tendcomputer.com/v2/keys/key_01J7ZM8C4RQ2XH6VNT0BWAE5DS \
  -H "Authorization: Bearer $TEND_ADMIN_KEY"

Revoking a key does not cancel jobs or schedules that key created. Those belong to the project and continue to run. To stop work, cancel the jobs or pause the schedules explicitly.

#Rate limits and authentication errors

Rate limits apply per project, not per key, and are measured in requests per minute. Exceeding the limit returns 429 rate_limit_exceeded with a Retry-After header giving the number of seconds to wait. Honor it. The SDKs do so automatically.

PlanRequests per minuteConcurrency limit
Hobby605
Pro60050
Scale3,000500
Enterprise10,000 (negotiable)2,000 (dedicated pools available)

Authentication failures are cheap to diagnose if you know what each code means.

HTTPCodeWhat to check
401missing_api_keyThe Authorization header is absent or is not in the form Bearer <key>.
401invalid_api_keyThe key is mistyped, revoked, or does not start with tnd_live_ or tnd_dev_.
403insufficient_scopeThe key is valid but lacks the scope this operation requires.
404resource_not_foundOften a tnd_dev_ key looking for a tnd_live_ resource, or the reverse.
429rate_limit_exceededBack off for Retry-After seconds; consider a higher plan for sustained load.

Every error response includes a request_id. Include it when you contact support and we can find your request in seconds, rather than asking you to describe it in prose.