Authentication
How to authenticate with the Pendium MCP server and REST API.
Pendium supports two authentication methods: OAuth (for all MCP connections — Claude.ai, ChatGPT, coding agents, and any MCP client) and API keys (for the REST API and custom integrations).
OAuth (all MCP connections)
All MCP connections use OAuth 2.1 with PKCE. When you add Pendium's MCP server to any client, the platform handles authentication automatically — you sign in with your Pendium account and the platform receives a token.
No API key is needed for MCP. The client registers itself, redirects you to sign in, and exchanges an authorization code for an access token — all behind the scenes.
Tokens are stored hashed, and re-auth revokes the old token. Pendium only persists a SHA-256 hash of each OAuth access token, never the raw value. Signing in again from a new client issues a fresh token and revokes the previous one ("last login wins"), and a revoked token stops authenticating immediately. The platform handles refresh transparently, so you rarely notice.
API key as a bearer-token fallback. Some MCP clients can't run the OAuth flow — Databricks Unity Catalog "Bearer token" connections, n8n, Bruno, and similar low-code tools only let you set a static
Authorization: Bearerheader. For these, you can connect by passing a personal API key (pendium_…from /settings/api-keys) as the bearer token instead of an OAuth token. OAuth is still the richer, recommended path; use the API key only when OAuth isn't an option.
Supported platforms:
- Claude.ai (web and desktop app)
- ChatGPT
- Claude Code
- Cursor
- Windsurf
- Any MCP-compatible client
See the quick start for setup instructions.
OAuth endpoints (for reference — platforms discover these automatically):
| Endpoint | URL |
|---|---|
| Protected Resource Metadata | https://pendium.ai/.well-known/oauth-protected-resource |
| Authorization Server Metadata | https://pendium.ai/.well-known/oauth-authorization-server |
| Dynamic Client Registration | https://pendium.ai/register |
| Authorization | https://pendium.ai/authorize |
| Token Exchange | https://pendium.ai/token |
API keys (REST API only)
For custom integrations, scripts, and no-code tools that use the REST API directly, authenticate with an API key.
Generate an API key from your Pendium dashboard. Log in, click your avatar in the sidebar, then API Keys. Or visit pendium.ai/mcp and click Get API Key. The same dialog lists your existing keys by their short prefix and shows when each was last used, so you can spot and revoke a key you no longer recognize.
Keys are stored hashed. Pendium only persists a SHA-256 hash of your key, never the raw value. The full key (
pendium_…) is shown once at creation and can't be recovered afterward — copy it then. If you lose it, revoke it and generate a new one. Revoked keys stop authenticating immediately.
Passing your API key
Include your API key in either of these request headers — pick whichever your HTTP client supports:
The x-api-key header is the canonical form; Authorization: Bearer is accepted for tools that only support bearer-style auth (Databricks Unity Catalog HTTP connections, Bruno, Insomnia, Postman defaults, n8n).
See the REST API docs for endpoint details and examples.
How auth works
- MCP connections: The server extracts the bearer token from the
Authorization: Bearerheader. It accepts either an OAuth access token (pendium_oauth_…, the default) or a personal API key (pendium_…, for clients that can't run OAuth). Either is validated against Pendium's database, resolving to a user account. - REST API: The server extracts the API key from either the
x-api-keyheader or anAuthorization: Bearer pendium_...header. The key is validated by hash lookup, resolving to a user account. OAuth-issued tokens (pendium_oauth_*) are never treated as API keys — those go through the MCP auth path only. - For tools that operate on a specific brand agent (identified by
syntheticId), an ownership check verifies the authenticated user has access to that agent. - Admin users can access any agent.
Error responses
When authentication fails, the server returns a 401 with a descriptive error:
| Scenario | Error message |
|---|---|
| No token provided | Authentication required. Connect via OAuth. |
| Invalid or expired token | Invalid or expired OAuth token. Re-authenticate via OAuth. |
| No access to agent | Agent with syntheticId=X not found or you don't have access. Use the get_account tool to see your agents. |
For MCP clients, a 401 response includes a WWW-Authenticate header that triggers the platform to re-authenticate automatically.
Agent access model
Each OAuth token or API key is tied to a Pendium user account. That user owns one or more brand agents (identified by syntheticId). Tools that require a syntheticId parameter will verify that the authenticated user owns that agent before executing.
Use the get_account tool to list all agents accessible to your account, along with their syntheticId values and latest scores.
Content scope
The MCP's blog-post tools (list_blog_posts, publish_blog_post, regenerate_blog_post, generate_blog_post_image, schedule_blog_post, unschedule_blog_post, move_blog_post_to_draft, delete_blog_post, update_blog_post_body, update_blog_post_title, update_blog_post_slug) operate on blog posts only. Social posts are managed in the Pendium app, not over MCP — a blog-post tool given a social postId returns an error directing you there. Blog content itself is generated via the workflow tools (create_workflow → run_workflow_pipeline).
Tool name compatibility
Several tool families were renamed to match the canonical names used everywhere else in Pendium, so one concept has one name across the app, the REST API, and MCP:
| Current name | Former name (deprecated alias) |
|---|---|
list_visibility_personas | list_personas |
add_visibility_persona | add_persona |
update_visibility_persona | update_persona |
delete_visibility_persona | delete_persona |
list_visibility_topics | list_topics |
add_visibility_topic | add_topic |
update_visibility_topic | update_topic |
delete_visibility_topic | delete_topic |
update_visibility_query | update_query |
delete_visibility_query | delete_query |
list_blog_posts | list_posts |
regenerate_blog_post | regenerate_post |
generate_blog_post_image | generate_post_image |
publish_blog_post | publish_post |
schedule_blog_post | schedule_post |
unschedule_blog_post | unschedule_post |
move_blog_post_to_draft | move_to_draft |
delete_blog_post | delete_post |
update_blog_post_body | update_caption |
update_blog_post_title | update_title |
update_blog_post_slug | update_slug |
Only the current names appear in tools/list, but the server still accepts the former names — a tools/call using an old name is transparently routed to its replacement. Existing integrations keep working; new ones should use the current names.