API Keys
Genfeed API keys are long-lived gf_ bearer credentials for server-to-server
calls, automation, CI, and MCP clients. They authenticate the same REST API as a
Better Auth session token, but they belong to the user and organization that
minted them rather than to a browser session.
Keys are shown once at creation or rotation, stored hashed, and can be revoked at any time.
Key Format
| Environment | Prefix | Example |
|---|---|---|
| Production | gf_live_ | gf_live_xxx |
| Non-production | gf_test_ | gf_test_xxx |
Any bearer token starting with gf_ is routed to API-key authentication;
everything else is validated as a Better Auth JWT.
Create a Key in the App
- Open organization settings at
/{orgSlug}/~/settings/api-keys. - In Genfeed API keys, enter a label and an optional description.
- Pick a scope preset — MCP, Read, or Content — or select individual scopes.
- Optionally set an expiry date, a per-key rate limit, and an IP allowlist.
- Create the key and copy it immediately. The plaintext value is displayed once and cannot be retrieved again.
MCP clients can use the guided Connect flow in your instance’s web app
(/connect, for example http://localhost:3000/connect) instead, which
creates an MCP-scoped key and runs an authenticated connection check. See
MCP Server.
Store the key where your integration runs:
export GENFEED_API_KEY=gf_live_xxxTreat API keys like passwords. Do not commit them or paste them into shared logs.
Create a Key via the API
Key management endpoints authenticate with a Better Auth session token:
curl -X POST http://localhost:3010/v1/api-keys \
-H "Authorization: Bearer $GENFEED_JWT" \
-H "Content-Type: application/json" \
-d '{
"label": "Publishing automation",
"category": "genfeedai",
"description": "Nightly draft + schedule job",
"scopes": ["videos:read", "videos:create", "posts:draft"],
"expiresAt": "2027-01-01T00:00:00Z",
"allowedIps": ["203.0.113.10"]
}'Responses are JSON:API documents. Creation is the only time the plaintext key
attribute is returned:
{
"data": {
"type": "api-key-full",
"id": "apikey_123",
"attributes": {
"key": "gf_live_xxx",
"label": "Publishing automation",
"scopes": ["videos:read", "videos:create", "posts:draft"],
"expiresAt": "2027-01-01T00:00:00.000Z",
"isRevoked": false,
"usageCount": 0
}
}
}Every other endpoint serializes as api-key and omits the key attribute.
| Field | Required | Notes |
|---|---|---|
label | yes | 1–100 characters. |
category | yes | genfeedai for platform keys. |
description | no | Up to 500 characters. |
scopes | no | Subset of the self-service scopes below. Defaults to video, image, and analytics scopes. |
expiresAt | no | ISO 8601 date. Omit for a non-expiring key. |
rateLimit | no | Per-key requests per minute. Leave unset for no ceiling. |
allowedIps | no | Exact client IPs allowed to use the key. Empty means no IP restriction. |
Each user may hold 10 active keys. Creating an eleventh returns
400 API Key Limit Reached — revoke or rotate an existing key first.
Manage Keys
| Method | Path | Purpose |
|---|---|---|
POST | /api-keys | Create a key. Returns the plaintext value once. |
GET | /api-keys | List active keys with page, limit, and search query parameters. |
GET | /api-keys/{apiKeyId} | Fetch one key’s metadata. |
PATCH | /api-keys/{apiKeyId} | Update label, description, scopes, expiry, rate limit, or IP allowlist. |
POST | /api-keys/{apiKeyId}/rotate | Issue a replacement key and revoke the original. |
POST | /api-keys/{apiKeyId}/verify-mcp | Run a bounded tool-discovery check against the configured MCP service. |
DELETE | /api-keys/{apiKeyId} | Revoke a key immediately. |
POST | /api-keys/validate | Check whether a key is currently valid. |
Rotation has no grace period: the replacement is issued and the original is revoked in the same request, so deploy the new value before rotating. Revocation takes effect on the next request — revoked and expired keys fail authentication.
Use a Key
Both bearer schemes are accepted:
Authorization: Bearer gf_live_xxxAuthorization: ApiKey gf_live_xxxcurl http://localhost:3010/v1/posts \
-H "Authorization: Bearer $GENFEED_API_KEY"The request authenticates as the key’s user and organization for tenancy.
Authorization is the key’s explicit scopes — never the issuer’s org-admin
role. Present the key only in the Authorization header; a key in the URL
path or query string is rejected.
The same key authenticates your instance’s MCP server at http://localhost:3014/mcp.
MCP OAuth tokens issued through a browser consent flow are bound to the MCP
resource and are rejected on the REST API.
Key surfaces
| Surface | Credential | Presentation | Effective authorization |
|---|---|---|---|
| Public REST | User gf_ key | Authorization: Bearer or ApiKey | Explicit key scopes. Tenancy is the issuing user and org. Org-admin membership is not inherited. |
MCP /mcp | Same gf_ key or MCP OAuth session | Bearer header only | MCP user unless the key has an explicit admin scope and the issuer is org admin or owner. |
/auth/whoami | Session or gf_ key | Bearer | API keys never advertise owner/admin without admin scope. Scopes never default to *. |
Telegram /connect | User gf_ key | Private-chat command, then cached apiKeyId | findByKey at connect. Each later command re-reads the row; a revoked or expired key drops the chat identity. |
| Agent Auth.md registration | User gf_ key minted after claim | Bearer after exchange | Same REST/MCP rules. Revoke goes through findByKey then revoke. |
| Internal files, notifications, Slack, MCP server-to-server | GENFEEDAI_API_KEY env | Bearer | Service identity, not a tenant org-admin. |
| Provider webhooks (HeyGen, Kling, OpusPro, Leonardo) | Vendor shared secret | ?token= and/or x-webhook-token | Not a gf_ key. Vendors that have no HMAC only hit the registered URL. HMAC webhooks (Stripe) stay header-only. |
A revoked gf_ key fails findByKey / findActiveById on every surface above. MCP does not cache successful gf_ whoami lookups.
Per-endpoint @RequiredScopes coverage is separate from this contract. Missing scope metadata on a collection does not restore implicit org-admin.
Scopes
Scopes are checked per endpoint. Requests missing a required scope return
401 Insufficient permissions.
| Family | Scopes |
|---|---|
| Videos | videos:read, videos:create, videos:update, videos:delete |
| Images | images:read, images:create, images:update, images:delete |
| Prompts | prompts:read, prompts:create, prompts:update, prompts:delete |
| Articles | articles:read, articles:create |
| Posts | posts:draft, posts:schedule, posts:approve, posts:publish |
| Workspace | brands:read, credits:read, analytics:read |
The four self-service presets are:
| Preset | Intent |
|---|---|
read | Read-only access to content, brands, credits, and analytics. |
content | Read plus content creation, including drafts. No scheduling or publishing. |
mcp | Content creation plus scheduling and approval. Approval-first: excludes direct publishing. |
full | Every self-service scope, including posts:publish and destructive updates. |
posts:create remains as a backward-compatible alias for draft creation.
Privileged scopes (admin, credits:provision, managed-inference:execute)
and wildcards are rejected at creation — they exist only for keys minted
server-side.
Rate Limits
Each API-key request is counted in a 60-second sliding window per key. A
self-hosted instance has no plans, so the per-key rateLimit is honored.
Leaving it unset means no ceiling.
Exceeding the ceiling returns 401 Rate limit exceeded, the same status class as
other API-key authentication failures. Back off and retry after the window
closes.
Key-management endpoints are limited separately and return 429 Too Many Requests with X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset,
and Retry-After headers: 5 requests per minute for create and rotate, 10 for
MCP verification, and 30 for validation.
Errors
| Status | Condition |
|---|---|
401 | Missing Authorization header, unsupported scheme, or malformed value. |
401 | Invalid, expired, or revoked key. |
401 | Required scope missing for the endpoint. |
401 | Client IP outside the key’s allowlist. |
401 | Per-key rate-limit ceiling exceeded. |
401 | MCP OAuth token used outside the MCP resource. |
400 | API Key Limit Reached — the user already holds 10 active keys. |
Without a login wall
If your instance runs with the Better Auth login wall turned off (see Configuration), requests without a token resolve to the default local identity, so a key is only required when you want explicit, scoped credentials. Once you expose the instance beyond localhost, enable the login wall and use scoped keys; see Reverse Proxy and HTTPS.