Authentication
Pharlo supports three authentication mechanisms depending on how you access it.
API Key Authentication
The primary method for programmatic access. Every tenant receives an ApiClient with a key in the format:
ds_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxSending the key
Pass the key as a Bearer token in every request:
curl https://pharlo.io/api/v1/connections \
-H "Authorization: Bearer $DELIVERY_API_KEY"Finding your key
Go to Settings → Credentials (opens in a new tab) in the console. Your key is shown there and can be rotated from the same page.
Security best practices
- Store the key in an environment variable, never hardcode it
- Never send it to client-side (browser) code
- Rotate immediately if you suspect it has been compromised
- Use the console to rotate: old key is invalidated, new key is issued
JWT Authentication (console only)
Used by the console SPA and by custom UIs built on top of the console API. Not intended for server-to-server integrations — use API keys for those.
Register
curl -X POST https://pharlo.io/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "secret"}'Response includes a JWT:
{"token": "eyJhbGciOiJSUzI1NiJ9..."}Login
curl -X POST https://pharlo.io/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "secret"}'Using the JWT
curl https://pharlo.io/api/v1/auth/me \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9..."Tokens expire after a fixed period. Re-login to get a new token.
MCP OAuth (AI clients)
MCP clients (Claude.ai, and any MCP-compatible AI assistant) authenticate via OAuth 2.1 with PKCE. This is handled automatically by the client; you only need a valid API key to complete the consent step.
OAuth endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /.well-known/oauth-authorization-server | Server metadata discovery (RFC 8414) |
GET | /.well-known/oauth-protected-resource | Protected resource metadata (RFC 9728) |
POST | /oauth/mcp/register | Dynamic client registration (RFC 7591) |
GET POST | /oauth/mcp/authorize | Authorization + consent page |
POST | /oauth/mcp/token | Token exchange |
POST | /oauth/mcp/revoke | Token revocation (RFC 7009) |
Authorization flow
MCP client discovers the server
Reads /.well-known/oauth-authorization-server to obtain server metadata.
Dynamic registration
Client registers itself at POST /oauth/mcp/register.
Authorization
Browser opens /oauth/mcp/authorize. User enters their ds_live_... API key to grant consent.
Token exchange
POST /oauth/mcp/token returns access + refresh tokens.
Requests
Client calls /_mcp with Authorization: Bearer <access_token>.
Scopes
| Scope | Access |
|---|---|
assignments:read | Read assignments |
assignments:write | Create, update, retry, cancel assignments |
connections:read | Read connections and stats |
connections:write | Update connections |
platforms:read | Read platform capabilities |
webhooks:read | Read webhook delivery logs |
stats:read | Read all analytics and stats |
Token lifetimes
| Token | TTL |
|---|---|
| Authorization code | 5 minutes |
| Access token | 1 hour |
| Refresh token | 30 days |
Troubleshooting
invalid_grant on token exchange
- Authorization code is single-use. Restart the OAuth flow if it was already consumed.
- Check that
redirect_uriexactly matches the URI used during registration. - Verify the
code_verifierproduces the correct SHA-256 challenge.
401 on /_mcp requests
- Access token expired — use the refresh token to get a new one.
- Access token revoked — re-authorize.
For Claude.ai setup, see the MCP Connection Setup guide.