# Authentication

Get started

Authenticate every request with your API key in a header. One key works for every endpoint and platform.

Send your API key in the `x-api-key` header of every request:

Shell:

```shell
curl -H "x-api-key: $SOCIALTAP_API_KEY" "https://socialtap.dev/v1/tiktok/profile?..."
```

`authorization: Bearer <key>` works too (what most HTTP and MCP clients send); when both are sent, `x-api-key` wins.

> [!WARNING]
>
> The API is for server-side use. The key is a secret: call SocialTap from your server or backend jobs, never from code that ships to browsers or apps, and rotate the key if it leaks. Browsers on other sites can't call it anyway: answers carry no CORS headers for other origins, so the browser blocks a request from your web page. Have your page call your server, and your server call SocialTap.

## Keys and scopes

Get a key on the [signup page](https://socialtap.dev/signup/); AI agents get one for you with [auth.md](https://socialtap.dev/auth.md): you approve it in your browser. Keys start with `st_live_` and make real requests, paid with your account's credits (new accounts start with 100 free). Each key has scopes (what it may call) and can have a spend limit, an expiry and an IP allowlist; a key that may not make a request gets [`forbidden`](https://socialtap.dev/docs/errors/index.md#forbidden) (HTTP 403) with the reason. Manage keys in the [dashboard](https://socialtap.dev/dashboard/) or with the API keys routes.

## Request headers

| Header | Type | Description |
| --- | --- | --- |
| `x-api-key` | `string` | Your API key. |
| `authorization` | `string` | Bearer \<your API key>: the same key as x-api-key, in the form most HTTP and MCP clients send (x-api-key wins when both are sent). |
| `x-account-id` | `string` | Dashboard session only: the account (acct_…) to act on, one of Session.accounts (default: the person's own); one they aren't on is refused (forbidden, reason not_a_member). API keys ignore it. |
| `x-client-request-id` | `string` | Your own ID for the request (1–128 printable ASCII characters): echoed back and logged with ours, so support can find a request whose response never reached you. |
| `traceparent` | `string` | W3C Trace Context of your trace: recorded with the request (its trace ID) so a trace can be matched to ours; SocialTap starts its own trace. |
| `idempotency-key` | `string` | On any POST: your unique key for the request (1–255 printable ASCII characters; a UUID is ideal), kept 24 hours. Retrying with the same key and body never repeats the work: the answer is the first request's result, in its current state. |
| `prefer` | `string` | RFC 7240. respond-async on any endpoint request runs it as a one-request job: the answer is 202 with the job (Location: its URL) instead of the result. |
| `cache-control` | `string` | max-age=N accepts a cached response up to N seconds old, like the max_age parameter (max_age wins when both are sent). Other directives are ignored. |

## Response headers

| Header | Type | Description |
| --- | --- | --- |
| `x-request-id` | `string` | Unique request ID; include it when contacting support. |
| `x-credits-charged` | `int` | Credits charged for this request (0 for errors and cache hits). |
| `x-credits-remaining` | `int` | Account balance after this request; for a busy account an estimate that may not yet subtract concurrent requests (about a second of usage), never below the real balance. |
| `retry-after` | `int` | Seconds to wait before retrying; sent only with the codes whose retry_after is set in the error codes list (never on credit errors). |
| `allow` | `string` | Methods the endpoint accepts (method_not_allowed only). |
| `age` | `int` | Seconds since the data was fetched (cache hits only). |
| `x-client-request-id` | `string` | Your x-client-request-id, echoed (also meta.client_request_id). |
| `ratelimit-policy` | `string` | The account's runaway-protection ceiling as an IETF RateLimit-Policy field, as enforced: a token bucket of q requests (twice the ceiling in requests per second, at least 10) that refills in w seconds: "account";q=\<requests>;w=\<seconds>. Not a plan limit. |
| `ratelimit` | `string` | What is left of the ceiling (IETF RateLimit field): "account";r=\<requests that may be sent at once now>;t=\<seconds until the bucket is full again>. |
| `deprecation` | `string` | RFC 9745: the endpoint is deprecated, as of this date (@\<unix seconds>; may be in the future). Also in meta.warnings. |
| `sunset` | `string` | RFC 8594: when a deprecated endpoint stops answering (HTTP-date); from then on it answers endpoint_retired. |
| `link` | `string` | RFC 8288 links: rel="next" is the next page of a list (while page.has_more), the same request with cursor set; rel="deprecation" is a deprecated endpoint's notice. |
| `location` | `string` | On 202 answers: the job's URL (GET it, free, for its status). |
| `preference-applied` | `string` | RFC 7240: respond-async when Prefer: respond-async turned the request into a job (202). |
| `idempotent-replayed` | `bool` | true when the answer replays an earlier request with the same Idempotency-Key (it shows the resource's current state). |

`x-credits-charged` and `x-credits-remaining` are on every response to an authenticated request, successful or not: errors show a charge of 0 and your unchanged balance. A request rejected for its key has neither, because there is no account to report on.

## Rotate a key

`POST /v1/api_keys/{id}/rotate_secret` gives a key a new secret, shown once; its ID, scopes, limits and usage stay. The old secret works for `previous_secret_expires_in` more seconds (default 1 day, at most 7 days; the key's `previous_secret_expires_at`), but only for calls that need no `admin` scope: others answer [`forbidden`](https://socialtap.dev/docs/errors/index.md#forbidden), reason `previous_secret`. Rotating again ends the oldest secret, and revoking ends both, within a minute everywhere. The dashboard's API keys page has a Rotate button.

Rotate when a secret may have been exposed and nothing suggests it was used: send 0 to end the old one now. After a leak, **revoke** the key instead (`DELETE /v1/api_keys/{id}`). A rotation keeps the key, its jobs and every key it made; revoking stops its jobs and takes the `admin` keys below it along, and its answer lists the keys it leaves working (`left_standing`), which the dashboard offers to revoke too. Then check your webhook endpoints.

If the answer to your own rotation is lost, only a person can recover: the old secret can't manage the key, and replaying the answer to it would hand the new secret to whoever holds the old one. Sign in and rotate the key in the dashboard, or rotate it with another `admin` key.

## Missing or invalid keys

A request without a key, or with an unknown or revoked one, fails with [`unauthorized`](https://socialtap.dev/docs/errors/index.md#unauthorized) (HTTP 401), carries no credit headers and costs nothing.

## Rate limits

Plans do not limit request rate: you pay for credits (see [Pricing](https://socialtap.dev/pricing/index.md)), and you can send requests as fast and as many in parallel as your balance covers.

Each account has a high safety ceiling on requests per second that only stops runaway clients, such as a retry loop without backoff. Above it, requests fail with [`rate_limited`](https://socialtap.dev/docs/errors/index.md#rate_limited) (HTTP 429) and cost nothing; wait the number of seconds in the `retry-after` header, then retry. If your sustained traffic needs more, ask [support](mailto:support@socialtap.dev) and the ceiling is raised for your account. An account that has never paid has a lower ceiling, which its first payment removes within a minute.

Failed requests are free, and they stay free within a limit: if your failures cost far more upstream in an hour than your successful requests paid for, requests answer [`rate_limited`](https://socialtap.dev/docs/errors/index.md#rate_limited) until that passes, and the message says so. Failures alongside a normal share of successes never meet it, and neither do failures that cost nothing to find out.

Your account's ceiling is `max_rps` in `GET /v1/account`. To see how close you run to it, `GET /v1/usage/rate` reports the busiest second (`peak_rps`), the average rate and the requests refused with [`rate_limited`](https://socialtap.dev/docs/errors/index.md#rate_limited), per 10 minutes, hour or day, and the dashboard's Usage page charts them against the ceiling.

---

[HTML version](https://socialtap.dev/docs/authentication/) · [Docs index for AI agents](https://socialtap.dev/llms.txt) · [All docs in one file](https://socialtap.dev/llms-full.txt)
