Every MCP request carries a bearer credential — either an API key or an OAuth access token. The gateway authenticates it, builds a request context (your tenant + permission scopes), and filters which tools your agent can see and call.
Two ways to authenticate
API key (ls_...) |
OAuth | |
|---|---|---|
| Best for | CI, scripts, headless agents, any client without a native sign-in flow | Clients with a built-in "Connect"/"Sign in" button (Claude.ai's remote-MCP connector) |
| Setup | Copy a key from Settings → API Keys, paste it into a header or npx @vlozi/cli auth login |
Give the client https://mcp.vlozi.app/mcp and click Connect — no key ever created |
| Lifetime | 90 days by default, or never | ~1 hour access token, auto-refreshed for as long as the connection stays active |
| Revoke | Settings → API Keys | Settings → API Keys → Connected apps |
Both are scoped by the exact same permission catalogue — a tool either requires a permission your credential holds, or it doesn't appear in your tool list at all. See MCP clients for setup of either kind.
OAuth
For clients that speak the MCP authorization spec (OAuth 2.1 + PKCE, dynamic client registration), Vlozi is a full authorization server — no manual key required:
- Discovery:
https://mcp.vlozi.app/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource - Registration:
https://mcp.vlozi.app/oauth/register(RFC 7591, public clients only — PKCE is the proof of possession, no client secret) - Authorize:
https://mcp.vlozi.app/oauth/authorize— redirects to a Vlozi consent screen where you pick the permissions the client is granted - Token:
https://mcp.vlozi.app/oauth/token—authorization_codeandrefresh_tokengrants
A properly-behaving client handles all of this itself; you never construct these URLs by hand. The access token it ends up with is a short-lived, self-contained credential scoped to whatever you approved — the same PBAC permission strings API keys use, just granted through a consent screen instead of a key-creation form.
API key format
Secret keys start with ls_ and are issued from Settings → API Keys in the Vlozi Dashboard. Shown once at creation; stored hashed.
IMPORTANT
MCP uses the same ls_* keys as the REST API. Same key, same scopes, different endpoint.
Sending the key
Either header works:
# Bearer (preferred)
curl -H "Authorization: Bearer ls_xxxxx" https://mcp.vlozi.app/tools
# Or x-api-key
curl -H "x-api-key: ls_xxxxx" https://mcp.vlozi.app/toolsScopes
Each tool has exactly one required permission. The tool list is filtered to what your key can do — agents never see tools they can't call.
Blog
| Scope | Tools |
|---|---|
blog:posts.read |
list_posts, get_post, search_posts, list_tags, list_categories, get_analytics |
blog:posts.create |
create_draft, generate_draft, create_category, create_tag |
blog:posts.update |
update_post, update_category, update_tag |
blog:posts.publish |
publish_post, unpublish_post, unschedule_post |
blog:posts.delete |
delete_post, restore_post, delete_category, delete_tag |
Brain
| Scope | Tools |
|---|---|
brain:copilot.use |
query, ingest, list_memories, get_context |
brain:memory.delete |
delete_memory |
Contacts
| Scope | Tools |
|---|---|
contacts:contacts.read |
list_contacts, get_contact_profile, get_contact_channels, get_contact_timeline, export_contact, summarize_contact |
contacts:contacts.create |
create_contact |
contacts:contacts.delete |
merge_contacts, delete_contact |
contacts:memory.read |
list_memories |
contacts:memory.delete |
delete_all_memories |
contacts:context.read |
get_context |
contacts:context.write |
correct_memory |
contacts:outreach.read |
list_outreach, get_outreach_config |
contacts:outreach.create |
schedule_outreach, cancel_outreach, update_outreach_config, trigger_outreach_scan, draft_outreach_message |
contacts:analytics.read |
analytics_trends, analytics_churn, analytics_moods |
Newsletter
| Scope | Tools |
|---|---|
newsletter:subscribers.read |
list_subscribers |
newsletter:subscribers.create |
add_subscriber, unsubscribe_subscriber, tag_subscriber, create_segment |
newsletter:subscribers.delete |
remove_subscriber |
newsletter:templates.read |
list_templates |
newsletter:templates.create |
create_template |
newsletter:campaigns.create |
create_campaign, cancel_campaign, generate_subject |
newsletter:campaigns.send |
send_email, send_campaign |
Content (social calendar)
| Scope | Tools |
|---|---|
content:slots.read |
get_calendar, get_slot |
content:slots.write |
schedule_content, delete_slot, generate_caption, suggest_hashtags |
content:slots.approve |
approve_slot, reject_slot |
content:recurring.write |
create_recurring_schedule |
content:connections.read |
list_connections |
content:campaigns.read |
list_campaigns |
content:campaigns.write |
create_campaign |
content:labels.read |
list_labels |
content:labels.write |
create_label |
Chat (chatbot)
| Scope | Tools |
|---|---|
chatbot:config.read |
list_bots, get_config, list_custom_tools, list_tool_calls, get_audit_log |
chatbot:config.write |
create_bot, delete_bot, update_config, create_custom_tool, update_custom_tool, delete_custom_tool |
chatbot:channels.read |
list_channels |
chatbot:channels.write |
add_channel, connect_whatsapp, delete_channel, set_channel_status |
chatbot:sessions.read |
list_sessions, get_session_messages, summarize_session, draft_reply, analytics_overview, analytics_intents, analytics_models, analytics_sessions_breakdown, analytics_tools, analytics_timeseries |
chatbot:sessions.write |
reply_to_session, assign_session, resolve_session, escalate_session |
Comms (transactional email)
| Scope | Tools |
|---|---|
comms:send |
send_message |
comms:sender.read |
check_alias_availability, get_sender_settings, get_sender_identities |
comms:sender.write |
claim_alias, update_sender_settings, add_suppression, remove_suppression |
comms:logs.read |
list_logs, get_analytics, list_suppression |
Collections
| Scope | Tools |
|---|---|
collections:read |
list_collections, get_collection, list_entries, get_entry |
collections:write |
create_collection, update_collection, delete_collection, create_entry, update_entry, delete_entry, reorder_entries, bulk_delete_entries |
collections:entries.publish |
publish_entry, unpublish_entry |
Forms
| Scope | Tools |
|---|---|
forms:read |
list_forms, get_analytics, get_form, list_submissions, get_submission, list_webhook_deliveries |
forms:write |
create_form, update_form, delete_form, duplicate_form, update_submission, delete_submission, reply_submission, retry_webhook_delivery |
forms:submissions.export |
export_submissions |
Flow
| Scope | Tools |
|---|---|
flow:flows.read |
list_nodes, list_flows, get_flow, get_run_history |
flow:flows.create |
create_flow, update_flow |
flow:flows.manage |
enable_flow, disable_flow, delete_flow |
Billing
| Scope | Tools |
|---|---|
billing:coins.read |
get_balance, get_usage, list_transactions |
billing:plans.read |
get_summary, get_costs |
Identify your agent
Pass x-agent-id with a stable per-agent string ("claude-desktop", "n8n-publisher", "content-bot"). It shows up in your audit log so you can tell who did what.
curl https://mcp.vlozi.app/tools/blog.list_posts \
-X POST \
-H "Authorization: Bearer ls_xxxxx" \
-H "x-agent-id: claude-desktop" \
-H "content-type: application/json" \
-d '{}'Tenant resolution
You never send a tenant ID — the gateway derives it from your credential (API key or OAuth token). Both are scoped to exactly one workspace; cross-tenant access is structurally impossible.
Errors
{ "error": { "code": "...", "message": "..." } }| HTTP | code |
Cause |
|---|---|---|
| 401 | invalid_api_key |
Missing, wrong, revoked, or expired key |
| 401 | invalid_token |
Missing, malformed, or expired OAuth access token — a spec-compliant client silently refreshes and retries; you only see a new consent screen if the underlying connection itself was disconnected or fully expired |
| 403 | permission_denied |
Valid credential, missing required scope |
| 404 | tool_not_found |
Typo or removed tool |
| 400 | invalid_input |
Input failed validation |
| 400 | service_error |
Upstream product service errored |
| 429 | rate_limited |
See Retry-After header |
Permission changes propagate within 5 minutes (gateway cache TTL) for API keys. To force-invalidate an API key, rotate it; to force-invalidate an OAuth connection, disconnect it from Settings → API Keys → Connected apps.
Note the OAuth case is immediate for future activity but not instant for one already in flight: disconnecting revokes the refresh token right away, so the client can no longer silently refresh — but an access token it already holds keeps working for up to its remaining lifetime (≤1 hour), since access tokens are self-verified and don't require a database round-trip on every tool call.