Authentication

  • Base URL https://terrads.com/api/v1
  • Updated November 1, 2026

Every request to the API is authenticated with an API key sent as a bearer token. There are no sessions, cookies or sign-in flows on the API.

curl -s "$BASE/brands" -H "Authorization: Bearer $TOKEN"

Keys start with trds_live_. Anything else in the header — a missing header, a different prefix, a revoked key — is a 401 with invalid_api_key. The API never says which of those it was.

Creating a key

Keys are created in Settings → API keys in the app and are shown exactly once, at creation; we store only a hash, so a lost key must be replaced. A key carries a name you choose and a prefix (the first few characters) so you can tell keys apart later. Keys act on behalf of your whole team: a run created with a key is visible to every member in the app, and a key can read every run the team owns.

Creating keys requires an active paid plan; every paid plan includes API access. Without one, the settings page links to the plans.

Revoking a key

Revoke from the same settings page. Revocation is immediate: the next request with that key is a 401. A key also stops working, immediately, when the person who created it leaves the team or their account is suspended or deleted — even if the key itself was never revoked. Treat keys as belonging to the person who created them, and rotate when people leave.

Plan checks happen on every request

Holding a key is not enough on its own. On every request the API checks that your team still has API access and is in good standing:

SituationResponse
The team has no active subscription403 no_subscription
The plan does not include API access403 upgrade_required
The subscription's last payment failed and is being retried402 past_due

Creating a run adds one more check, at that endpoint only: a run that would cost more credits than the team holds is a 402 insufficient_credits, and nothing is charged.

These are the same rules the app applies: a team that downgrades or falls behind on payment sees the same refusals in the app and on the API. Existing keys keep working the moment the plan is restored — there is nothing to re-create.

Scopes

Every key created today carries every scope, so you never need to request one. Endpoints are still grouped by the scope they will require once scoped keys exist, and the endpoint reference lists it per route:

ScopeCovers
runs:readReading runs, their events and their results
runs:writeCreating runs, answering questions, approving copy, cancelling
catalog:readBrands, products, angles, audiences, models

A request that lands on an endpoint outside its key's scopes would be a 403 with insufficient_scope. With today's keys you will not see it.

What a key can do if it leaks

This is the part that costs money. A key is a team-wide credential, so whoever holds it can:

  • Read every run the team owns, including the copy and the finished creative.
  • Create runs, which spend the team's credits. maxCredits caps a single run, but the caller chooses that value — it protects you from a surprise, not from someone using the key on purpose.
  • Cancel runs in progress, and answer or approve the ones that are waiting.

A key cannot change billing, invite anyone, read another team's anything, or do anything an admin can do.

If a key leaks

  1. Revoke it in Settings → API keys. Revocation is immediate: the next request with that key is a 401.
  2. Create a replacement and deploy it. There is no rotation window to wait out — revoke first, then replace.
  3. Assume everything the team owns was readable while the key was out, and check your credit balance and run history for activity you did not start.

Keeping keys safe

  • Server-side only. Never put a key in client-side code, a browser, a mobile app, a desktop app, or a public repository. Anything shipped to a user's device is readable by that user.
  • Never in a URL. Send it in the Authorization header, never a query string. URLs end up in access logs, proxy logs, Referer headers and browser history; headers do not. No endpoint here accepts a key any other way.
  • Always https://. Never send a key over plain HTTP.
  • One key per integration, named after it, so revoking one does not take down another.
  • Rotate when people leave. A key also stops working by itself when the person who created it leaves the team or their account is suspended.
  • The API never echoes a key back, not even in an error message.