# Authentication (/docs/authentication) How to create, use, cap, and rotate SocialCrawl API keys with the x-api-key header Every request carries your API key in the `x-api-key` header. There is no OAuth flow, no token exchange, and no session: the key is the entire credential. Get a free API key at https://www.socialcrawl.dev/dashboard/api. New accounts start with 100 welcome credits and no card is needed. ```bash title="cURL" curl "https://www.socialcrawl.dev/v1/tiktok/profile?handle=charlidamelio" \ -H "x-api-key: YOUR_API_KEY" ``` ## What does a key look like? `sc_` followed by 32 random bytes, base64url-encoded. That makes every key 46 characters long. The full key is shown exactly once, in the dialog that appears when you create it. After that the dashboard shows only the last 4 characters. You can reveal it again yourself from the dashboard after a fresh sign-in, but support never sees the plaintext, so copy it into a secret manager before you close the dialog. ## How do I create one? 1. Sign up at [socialcrawl.dev](https://www.socialcrawl.dev). You get 100 welcome credits instantly. 2. Open **Dashboard → API Keys**. 3. Click **Create Key** and name it after where it runs (`production`, `staging`, `local-dev`). 4. Copy the key from the one-time reveal dialog. ## How do I send it? ```bash title="cURL" curl "https://www.socialcrawl.dev/v1/tiktok/profile?handle=charlidamelio" \ -H "x-api-key: $SOCIALCRAWL_KEY" ``` ```typescript title="TypeScript" const res = await fetch( "https://www.socialcrawl.dev/v1/tiktok/profile?handle=charlidamelio", { headers: { "x-api-key": process.env.SOCIALCRAWL_KEY! } }, ); ``` ```python title="Python" import os import requests res = requests.get( "https://www.socialcrawl.dev/v1/tiktok/profile", params={"handle": "charlidamelio"}, headers={"x-api-key": os.environ["SOCIALCRAWL_KEY"]}, ) ``` Never put the key in a URL or query string. It ends up in browser history, proxy logs, and referrer headers. ## What does a 401 mean? Two codes, and they need different fixes. | Code | Status | What happened | Fix | | ------------------------------------------------- | ------ | -------------------------------------------------- | ------------------------------------------------------- | | [`MISSING_API_KEY`](/docs/errors.md#missing-api-key) | 401 | No `x-api-key` header reached us | Add the header. Check that your proxy is not eating it. | | [`INVALID_API_KEY`](/docs/errors.md#invalid-api-key) | 401 | The key is malformed, unknown, revoked, or expired | Re-check the key in **Dashboard → API Keys** | Neither is retryable, and neither costs credits. No SocialCrawl error code maps to `403`. The two failures you might expect as a `403` are both `402`, because both are spend conditions rather than permission conditions: | Code | Status | What happened | Fix | | ----------------------------------------------------------- | ------ | ---------------------------------------------------------------- | --------------------------------- | | [`INSUFFICIENT_CREDITS`](/docs/errors.md#insufficient-credits) | 402 | The account balance is lower than the endpoint cost | Top up in **Dashboard → Billing** | | [`KEY_BUDGET_EXCEEDED`](/docs/errors.md#key-budget-exceeded) | 402 | This key spent its own credit limit. The account balance is fine | Raise or reset the key's limit | They need opposite responses, so branch on `error.type` rather than on the status. Topping up an account that was never short will not unblock a capped key. ## Key management | Rule | Value | | ----------------------- | ---------------------------------------------------------------------- | | Active keys per account | Up to 5 | | Storage | SHA-256 hash for auth lookup, AES-256-GCM encrypted for retrieval | | Revocation | Soft delete. Revoked keys return `401 INVALID_API_KEY` immediately | | Expiry | Optional per-key `expires_at` (unset means never) | | Credit limit | Optional per-key spend cap (unset means unlimited) | | Rate limits | Applied per key, not per account. See [Rate limits](/docs/rate-limits.md) | ### How do I rotate a key? Create the new key, deploy it, confirm traffic has moved, then revoke the old one. Revocation takes effect on the very next request, so revoking first means downtime. If a key has leaked, revoke it immediately and accept the downtime. A live key is billable by anyone holding it. ## Per-key credit limits [#per-key-credit-limits] Every key on your account draws on the same credit balance. That is usually what you want, until a test script loops, a CI job misfires, or a new integration is pointed at the wrong endpoint, and the balance your production traffic depends on is gone. A **credit limit** caps how many credits one key may ever spend. Set one on a key, and that key stops at the cap while every other key on the account keeps working normally. ```text title="Example" Production key no limit <- your real traffic, unaffected Test key 500 credit limit <- stops at 500, whatever it does ``` Set it in **Dashboard → API Keys**. When you create a key, tick **Set a credit limit for this key** and enter a number. Leave the box unticked for no limit, which is the default and what a production key normally wants. You can add, change, or remove a limit on an existing key at any time from its ⋯ menu. Once a capped key reaches its limit, its requests return [`402 KEY_BUDGET_EXCEEDED`](/docs/errors.md#key-budget-exceeded) and **nothing is deducted**. Your account balance is untouched, so the rest of your integration keeps running. It does not reset on its own. When a capped key runs out, raise its limit or hit **Reset usage** to give it the full allowance again. Credits refunded for an upstream failure are also given back to the key's counter, so a run of 404s or 502s never quietly eats a test key's budget. Cache hits and zero-cost endpoints do not count against the limit, exactly as they do not count against your balance. It immediately blocks that key. This is the fastest way to stop a key that is misbehaving right now, without revoking it and breaking whatever is holding it. It is a blast-radius control, not a billing plan. It never changes what you are charged, only which key is allowed to do the charging. Changes take effect on the key's very next request. There is no cache to wait out. ## Security guidance - **Never ship a key in client-side code.** Browser and mobile bundles are readable, and every request made with your key is billed to your account. Put a server route in front of the API and keep the key on the server. - **Use a separate key per environment.** Revoking a staging key should not take down production. - **Cap every non-production key.** A CI or test key with a [credit limit](#per-key-credit-limits) cannot drain the balance production runs on, however badly it misbehaves. - **Store keys in a secret manager**, not in git. Encrypted environment variables, AWS Secrets Manager, and 1Password all work. - **Set an `expires_at`** on any key you issue to a contractor or a short-lived job. ## Next steps - [Quickstart](/docs/quickstart.md): Make your first authenticated call in under a minute. - [Error handling](/docs/errors.md): Every error code, its status, and whether to retry it. - [Credits](/docs/credits.md): What each call costs, when credits are refunded, how to top up. - [Rate limits](/docs/rate-limits.md): Per-key request and concurrency limits, and how to stay under them.