# Authentication (/docs/api/authentication)



Overview [#overview]

All FeedHorizon API requests must include a valid API key in the `Authorization` header using the Bearer scheme.

```
Authorization: Bearer fh_YOUR_API_KEY
```

API key format [#api-key-format]

Keys follow this format:

| Part              | Description                               |
| ----------------- | ----------------------------------------- |
| `fh_`             | Fixed prefix identifying FeedHorizon keys |
| 64 hex characters | Random cryptographic payload              |

**Total length:** 67 characters (e.g. `fh_aee6d4242ae1d2ff857b4a900904f9e5beac1f2168a6c83da9f63053fda8caf8`)

Keys are hashed with SHA-256 before storage. The plaintext key is only shown once at creation.

Scopes [#scopes]

Each key carries a list of scopes. A scope names a resource and an access level, for example `posts:write`. A `:write` scope includes `:read` for the same resource.

| Resource         | Scopes                                            |
| ---------------- | ------------------------------------------------- |
| Profiles         | `profiles:read`, `profiles:write`                 |
| Social accounts  | `accounts:read`, `accounts:write`                 |
| Posts            | `posts:read`, `posts:write`                       |
| Webhooks         | `webhooks:read`, `webhooks:write`                 |
| API keys         | `api_keys:read`, `api_keys:write`                 |
| Platform invites | `platform_invites:read`, `platform_invites:write` |

A call without the needed scope answers `403 insufficient_scope`. `GET /api/v1/me` and `GET /api/v1/platforms` work with any valid key; `/me` shows the scopes of the key you are using.

The `permission` field is a shorthand when you create a key: `readwrite` grants every scope, `readonly` every `:read` scope. If you send `scopes`, they win over `permission`. Keys created before scopes existed kept exactly what they could do.

Profile scope [#profile-scope]

Keys can be scoped to specific profiles:

* **Full access** — Can access all profiles (including future ones)
* **Specific profiles** — Only access posts and accounts belonging to the selected profiles

Objects in profiles the key cannot access answer `404`, as if they did not exist. Creating profiles and managing API keys needs a key with full access; other keys get `403 all_profiles_required`.

Key management [#key-management]

Create a key [#create-a-key]

```bash
curl -X POST https://feedhorizon.com/api/v1/api-keys \
  -H "Authorization: Bearer fh_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "CI/CD Pipeline", "permission": "readwrite" }'
```

A key that may only schedule posts:

```bash
curl -X POST https://feedhorizon.com/api/v1/api-keys \
  -H "Authorization: Bearer fh_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Scheduler", "scopes": ["posts:write", "profiles:read"] }'
```

The response includes the full `key` field — **store it immediately**, it won't be shown again.

List keys [#list-keys]

```bash
curl https://feedhorizon.com/api/v1/api-keys \
  -H "Authorization: Bearer fh_YOUR_API_KEY"
```

Returns key metadata with `keyPrefix` only (e.g. `fh_abc12345`).

Delete a key [#delete-a-key]

```bash
curl -X DELETE https://feedhorizon.com/api/v1/api-keys/KEY_ID \
  -H "Authorization: Bearer fh_YOUR_API_KEY"
```

Deletion is immediate and irreversible.

Expiring keys [#expiring-keys]

Set `expiresAt` when creating a key to automatically expire it:

```json
{
    "name": "Temp key",
    "expiresAt": "2026-12-31T23:59:59Z"
}
```

An expired key answers `401` with the code `invalid_api_key`, the same as a revoked or unknown key.

Authentication errors [#authentication-errors]

| Status | Code                    | Meaning                                   |
| ------ | ----------------------- | ----------------------------------------- |
| 401    | `missing_api_key`       | No `Authorization: Bearer` header         |
| 401    | `invalid_api_key`       | Unknown, revoked or expired key           |
| 403    | `insufficient_scope`    | The key lacks the scope for this endpoint |
| 403    | `all_profiles_required` | The endpoint needs a key with full access |

See [Error handling](/docs/api/errors) for the error format.

Security best practices [#security-best-practices]

1. **Never commit keys to source control** — Use environment variables
2. **Use read-only keys** when you only need to fetch data
3. **Scope keys to specific profiles** to limit blast radius
4. **Set expiry dates** for temporary integrations
5. **Rotate keys regularly** — Delete old keys and create new ones
6. **Monitor `lastUsedAt`** in the dashboard to detect unused keys
