MCP server

Connect Claude Code, Claude Desktop, Cursor, ChatGPT or any MCP client to AY Inbox with an OAuth sign-in or a scoped API key, and use your data as tools.

AY Inbox speaks the Model Context Protocol. Point any MCP client at AY Inbox's server and it can search contacts, check campaigns, read your inbox, draft replies, create draft campaigns and automations, and find and verify leads, all scoped to what you grant it.

  • URL: https://api.ayinbox.com/v1/mcp
  • Transport: streamable HTTP
  • Auth: an OAuth 2.1 access token (browser sign-in) or a static API key (bearer header).

There are two ways to connect. OAuth is the one-command path: you run a single add command, approve access in the browser, and the client holds a short-lived token it can refresh. An API key is the static path: you mint a key and paste it into the client config. Both reach the exact same tools, gated the exact same way.

An agent with no account and no person at hand can open its own workspace first: POST /v1/agents/register answers with a key, then it connects with that key as below. See For agents.

Connect with OAuth

This is the recommended path. You do not paste a key anywhere: the client discovers AY Inbox's authorization server, opens a browser for you to sign in and approve the scopes, and stores the resulting token itself.

Claude Code

claude mcp add --transport http ayinbox https://api.ayinbox.com/v1/mcp

Then run /mcp inside Claude Code and pick Authenticate. A browser opens, you sign in to AY Inbox, approve the requested scopes, and you are connected. No header, no key.

Claude Desktop and Cursor

Add the server with just its URL, and the client runs the OAuth flow on first use:

{
  "mcpServers": {
    "ayinbox": { "type": "http", "url": "https://api.ayinbox.com/v1/mcp" }
  }
}

ChatGPT and other clients

Any client that supports remote MCP servers over streamable HTTP, with OAuth discovery and dynamic client registration, connects the same way: give it https://api.ayinbox.com/v1/mcp and sign in when it opens the browser. In ChatGPT that means adding the URL as a custom connector, where your ChatGPT plan offers one. Client settings screens change, so follow the client's own instructions for where the URL goes; what AY Inbox serves is described in how OAuth discovery works. For function calling without MCP, see OpenAI-compatible agents.

What you are approving

The consent screen marks a self-registered client as Not affiliated with AY Inbox, names where approving sends access, and shows exactly which scopes the client will get: the ones it asks for that your role in the workspace covers, so a connected client never does more than you could yourself. Sending real mail is never one of them: SEND_CAMPAIGNS and API-key management are excluded from OAuth-connected MCP clients by design, and the assistant only ever drafts. You can revoke a connected client at any time from your dashboard under connected apps.

Connect with an API key

Prefer a static, long-lived credential (CI, a shared script, a client that does not implement OAuth)? Mint an API key and send it as a bearer token:

Authorization: Bearer aybox_...

Scope your key

Create a dedicated API key for each MCP client and grant it only the scopes it needs (see Permissions). For a model that reads your inbox, start from the Agent: read and draft preset, which reads everything and drafts with AI credits but holds no scope that writes or sends. Add AI_AGENT if it should search the web or read your playbooks.

Claude Code

claude mcp add --transport http ayinbox https://api.ayinbox.com/v1/mcp \
  --header "Authorization: Bearer aybox_..."

Claude Desktop

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "ayinbox": {
      "type": "http",
      "url": "https://api.ayinbox.com/v1/mcp",
      "headers": { "Authorization": "Bearer aybox_..." }
    }
  }
}

Cursor

Add to .cursor/mcp.json:

{
  "mcpServers": {
    "ayinbox": {
      "url": "https://api.ayinbox.com/v1/mcp",
      "headers": { "Authorization": "Bearer aybox_..." }
    }
  }
}

What is exposed

The server exposes exactly the tools your credential is allowed to use, whether that credential is an OAuth token or an API key. Each tool checks its own scope, and the member behind the credential also needs the matching organization permission. A credential with only read scopes sees only the read tools; one with write scopes sees those too, so an MCP client acts with precisely the permissions you granted, and nothing more.

This is a subset of what the in-product assistant can do. Over MCP and the REST tools there is no person in the loop, so these rules hold for every tool:

  • No tool puts mail on the wire. Tools that send a message to a recipient (send_reply, compose_email, run_placement_test, run_placement_batch) are never listed or callable here, whatever the scope. A key with WRITE_UNIBOX can still send through POST /unibox/reply over REST; a model is safer on a key that holds no write or send scope, like the agent preset below.
  • Booking a meeting is a send. The scheduling provider mails an invitation to a real person, so book_meeting exists only in the assistant, where it always asks, and an MCP client books with POST /booking/offers/:id/book and its key. The booking credential has no tool at all, because a secret never travels in a tool's arguments.
  • No tool lifts a person's stop or opens a lasting way out. remove_suppression (it can undo an unsubscribe or a complaint), create_webhook, update_webhook and rotate_webhook_secret (a stream of workspace events to any URL, or a secret in a transcript) stay with the dashboard assistant, which asks a person each time. fetch_url stays there too: an MCP client brings its own web tools.
  • A tool that starts mail needs SEND_CAMPAIGNS. add_segment_to_campaign and set_campaign_segments (with segments) into a campaign that is sending, set_automation_enabled with enabled: true, and update_automation when it turns one on or changes the trigger of one that is on, all take SEND_CAMPAIGNS (and send_campaigns for the member) on a key, the same rule find_leads follows. So do update_segment when it changes the conditions or match mode, and set_segment_members with include or auto, on a segment that feeds a sending campaign, because a wider audience is mailed straight away. Turning an automation off, enrolling into a draft or paused campaign, renaming a segment, and pinning contacts out of one do not.
  • Contact writes feed linked segments. Once a segment is linked to a sending campaign, a contact that comes to match it (through add_contact, update_contact_fields, add_tag, bulk_edit_contacts or an import) is enrolled and mailed, exactly as on REST. Those tools take WRITE_CONTACTS only, so linking the segment is the decision that needs SEND_CAMPAIGNS.
  • No tool writes what the AI reads as instructions. The workspace's product description, ideal customer notes and voice, and its playbooks, are read by every AI writing surface, so no tool changes them. A key with WRITE_WORKSPACE can set the first three over PATCH /organization/current.
  • No tool approves a first email or records consent. Those are a person's decisions; their REST routes stay for your own code.
  • Workspace governance stays with a person. Team, invitations and roles writes, workspace settings writes and billing are not tools.

tools/list returns only the tools your mask allows. The paid Looot company lookups (company_website, company_news, company_linkedin_posts) are not exposed over MCP either; only contact research and the assistant run them.

ToolDoesRequired scope
search_contactsFind contacts by textREAD_CONTACTS
get_contactRead one contactREAD_CONTACTS
update_contact_fieldsUpdate contact fieldsWRITE_CONTACTS
add_tag / remove_tagAdd or remove a label on a contactWRITE_CONTACTS
list_campaignsList campaignsREAD_CAMPAIGNS
get_campaign_statsCampaign stats, all time or for the emails sent from from to toREAD_ANALYTICS
list_campaign_leadsA campaign's leads with derived status + totalsREAD_CONTACTS
create_campaign_draftCreate a draft campaignWRITE_CAMPAIGNS
set_campaign_statusStart or stop a campaignSEND_CAMPAIGNS
set_lead_holdHold or resume one contact's flow in one campaign, without unsubscribing themWRITE_CAMPAIGNS
get_dashboard_analyticsSending stats for a period, org-wide or for chosen campaigns and campaign foldersREAD_ANALYTICS
list_mailboxesSender mailboxes with health + warmup stateREAD_ANALYTICS
list_recommendations / get_advisor_summaryThe Advisor's open findings with their evidence, filtered by area, category or one campaign, mailbox or step, and the workspace's Advisor health score with counts by severityREAD_ANALYTICS
list_placement_tests / get_placement_testInbox placement tests and where each copy landedREAD_ANALYTICS
list_placement_batches / get_placement_batchPlacement batches, their progress, and placement by sending domain, sending provider and recipient providerREAD_ANALYTICS
add_contact / delete_contactCreate or delete a contactWRITE_CONTACTS
bulk_edit_contactsLabel or subscribe many contacts at onceBULK_CONTACTS
find_leadsStart a Looot lead job: find people at company domains, verify them, and import the valid ones, within max_cost_usdINTEGRATIONS and BULK_CONTACTS, plus WRITE_CAMPAIGNS with campaign_id, and SEND_CAMPAIGNS when that campaign is sending
get_lead_job / list_lead_jobsA lead job's progress and spend, or the latest jobsINTEGRATIONS
verify_emailCheck one address through the workspace's verifier, saving nothingWRITE_CONTACTS
get_contact_timeline / get_contact_sent_emailsRead a contact's activity and sent mailREAD_CONTACTS
list_segments / get_segment / list_segment_fieldsRead saved audiences, and the fields a condition can nameREAD_CONTACTS
preview_segmentCount what a set of conditions would match, saving nothingREAD_CONTACTS
create_segment / update_segment / delete_segmentManage saved audiencesWRITE_CONTACTS, plus SEND_CAMPAIGNS to change who is in a segment that feeds a sending campaign
set_segment_membersPin contacts into or out of a segmentWRITE_CONTACTS, plus SEND_CAMPAIGNS to pin contacts in to a segment that feeds a sending campaign
list_campaign_segmentsRead the segments feeding a campaignREAD_CAMPAIGNS
set_campaign_segments / add_segment_to_campaignLink an audience to a campaign, or enrol its members onceWRITE_CAMPAIGNS, plus SEND_CAMPAIGNS into a sending campaign
list_forms / get_form / list_form_submissions / get_form_statsRead hosted forms, their submissions, and their funnelREAD_CONTACTS
create_form / update_form / delete_formManage hosted formsWRITE_CONTACTS
mint_form_linkGet one contact's personalized form URLWRITE_CONTACTS
get_campaign_formsHow a campaign's forms performed for its recipientsREAD_CAMPAIGNS
list_suppressionsRead the do-not-contact list, and why each entry is on itREAD_CONTACTS
add_suppressionsSuppress an address or domainWRITE_CONTACTS
list_pipelines / list_deals / get_deal / list_deals_by_contactRead CRM pipelines and dealsREAD_CRM
create_task / create_dealCreate CRM recordsWRITE_CRM
update_deal / move_deal_stage / delete_dealEdit, move, or delete a dealWRITE_CRM
list_tasks / list_task_typesRead CRM tasks and task typesREAD_CRM
update_task / complete_task / delete_taskEdit, complete, or delete a taskWRITE_CRM
add_contact_note / list_contact_notesAdd or read contact notesWRITE_CRM / READ_CRM
update_contact_note / delete_contact_noteEdit or delete a noteWRITE_CRM
create_pipeline / update_pipeline / delete_pipelineManage CRM pipelinesWRITE_CRM
create_pipeline_stage / update_pipeline_stage / delete_pipeline_stageManage pipeline stagesWRITE_CRM
get_campaign / list_campaign_senders / get_campaign_logsRead a campaign's config, senders, and logsREAD_CAMPAIGNS
update_campaign / delete_campaignEdit or delete a campaignWRITE_CAMPAIGNS
set_campaign_senders / verify_campaign_tracking_domainSet senders, verify tracking domainWRITE_CAMPAIGNS
list_campaign_stepsRead a campaign's sequence stepsREAD_CAMPAIGNS
add_campaign_step / update_campaign_step / delete_campaign_stepEdit a campaign's sequence stepsWRITE_CAMPAIGNS
list_threads / get_threadRead the unified inboxREAD_UNIBOX
get_inbox_overview / get_unread_countThe inbox at a glance, and unread countsREAD_UNIBOX
generate_reply_draftHave the AI write a reply grounded on the thread, the contact and the campaign brain that applies, else your voice. The result carries grounding when a brain applied. Charges AI credits; never sends. idempotency_key is requiredREAD_UNIBOX and AI_DRAFT
draft_replyPackage reply text you wrote with the thread's recipient and subject, checked against the campaign brain: the result carries flags, or check_unavailable when the check could not run. Calls no model and never sendsREAD_UNIBOX
list_agent_draftsThe inbox agent's suggested replies waiting for a personREAD_UNIBOX
discard_agent_draftDiscard one of those drafts so it is never sentWRITE_UNIBOX
list_brains / get_brain / preview_brainRead campaign brains: public facts, do-not-say phrases and source titles, or what a reply would read for a sample. get_brain takes id, campaign_id or thread_id; with a campaign or thread it adds applied, the merged values a draft reads with where each comes from (campaign, default, campaign_and_default or voice_profile). Never internal rules or internal materialREAD_CAMPAIGNS
get_brain_materialRead one public source's text, fenced and in pages (offset, next_offset)READ_CAMPAIGNS
get_brain_internalRead a brain's internal rules, one internal source (material_id) or one internal page (page_id). Write-risk, so the assistant always asksWRITE_CAMPAIGNS
list_brain_pages / get_brain_pageList a brain's pages, or read one public page by page_id or path with the pages it links to and the pages linking to it, fenced. Never an internal pageREAD_CAMPAIGNS
write_brain_pageCreate a page at a path (or from its title and type), or replace the page already there. Internal unless visibility is public. In the assistant, it always asksWRITE_CAMPAIGNS
create_brain / update_brain / delete_brainManage brains; editing or deleting the workspace default also needs manage_settings for the key's member. In the assistant, update_brain and delete_brain always askWRITE_CAMPAIGNS
add_brain_material / update_brain_material / remove_brain_material / refresh_brain_materialAdd pasted text or a public page, pin it, mark it OK to quote, remove or refetch it. Files are a REST upload. In the assistant, each always asksWRITE_CAMPAIGNS
set_campaign_brain / set_default_brainChoose a campaign's brain, or the workspace default (manage_settings)WRITE_CAMPAIGNS
get_booking_settings / get_booking_summaryRead the booking mode, follow-up bounds and countsREAD_UNIBOX / READ_CONTACTS
update_booking_settingsSet booking to off or draft, and the follow-up boundsINTEGRATIONS
enable_booking_autoTurn on automatic booking, or see what it needs. Always asks and needs a recent confirmationSEND_CAMPAIGNS
list_booking_types / create_booking_type / update_booking_type / delete_booking_typeRead or manage booking typesREAD_CONTACTS / INTEGRATIONS
list_booking_event_typesA connection's event types and what blocks each oneINTEGRATIONS
get_availabilityOpen times in the prospect's timezoneREAD_CONTACTS, plus READ_UNIBOX with a thread
list_meeting_offers / get_meeting_offerRead offers with their times and decision logREAD_UNIBOX
offer_meeting_timesPrepare a block of times for a reply; sends nothingWRITE_UNIBOX
stop_meeting_offer / resume_meeting_offer / reject_meeting_pickStop, resume or drop a pickWRITE_UNIBOX
get_auto_reply_settingsRead the workspace's auto-reply switch and whether this instance can run itREAD_UNIBOX
pause_auto_replyStop every automatic answer in the workspace nowWRITE_UNIBOX
enable_auto_replyTurn on the workspace auto-reply switch. In the assistant it always asks and needs a recent confirmationSEND_CAMPAIGNS
get_campaign_auto_replyRead a campaign's auto-reply mode, approved links and figures, checklist and resultsREAD_CAMPAIGNS
update_campaign_auto_replyReplace a campaign's auto-reply settings while it is off or Supervised (a field left out returns to its default), or lower an automatic campaignWRITE_CAMPAIGNS
enable_campaign_auto_replySet a campaign to answer automatically with exactly the settings given. Always asks and needs a recent confirmationSEND_CAMPAIGNS
list_auto_reply_runs / get_auto_reply_runRead what auto-reply did and why; the answer's body is returned fenced as dataREAD_UNIBOX
cancel_auto_reply_runStop one automatic answer before it sendsWRITE_UNIBOX
list_scheduled_sendsList queued outbound messagesREAD_UNIBOX
mark_thread_seen / set_thread_labels / move_thread_folderMark seen, set thread labels, move threads to a folderWRITE_UNIBOX
snooze_thread / unsnooze_thread / cancel_scheduled_sendSnooze a thread, cancel a queued sendWRITE_UNIBOX
get_mailbox / get_warmup_ban_statusRead a mailbox's config and warmup standingREAD_EMAILS
update_mailbox / set_mailbox_warmup / set_mailbox_send_hold / set_mailbox_tracking_domainEdit mailbox settings, warmup, the campaign hold, tracking domainWRITE_EMAILS
submit_warmup_appeal / disconnect_mailboxAppeal a warmup ban, disconnect a mailboxWRITE_EMAILS
create_automation_draft / list_automations / get_automationRead automations, create a disabled draftINTEGRATIONS
update_automation / set_automation_enabled / delete_automationEdit, enable, or delete an automationINTEGRATIONS, plus SEND_CAMPAIGNS to turn one on
get_org_settings / get_workspace_limitsThe workspace profile and what the AI is grounded on, and plan limits next to usageREAD_WORKSPACE
get_subscription / get_subscription_limitsThe plan, status and trial, without billing identifiersREAD_WORKSPACE
get_credit_balance / get_credit_usage / list_credit_transactionsAI credits: balance, spend by day and feature, and the ledgerREAD_WORKSPACE, and manage_billing for the member
list_labelsCampaign folders, mailbox tags or contact categories, by kindREAD_CAMPAIGNS, READ_EMAILS or READ_CONTACTS for that kind
list_api_keys / create_api_key / update_api_key / revoke_api_keyManage API keysAPI_KEYS
list_webhooks / list_webhook_deliveriesRead webhook endpoints and deliveriesWEBHOOKS
delete_webhook / verify_webhookDelete or test a webhook endpointWEBHOOKS
search_webSearch public pagesAI_AGENT
load_skillRead one of your playbooksAI_AGENT

list_threads and get_thread return their documented JSON to API key and MCP callers. list_threads takes the same campaign_id, contact_id, since and until filters as GET /v1/unibox, and each thread carries its campaign_id when one resolves. A malformed value is refused as invalid arguments; a campaign or contact outside the workspace answers not found. Every brain tool result is capped at 12,000 characters, and material text is wrapped in <<<UNTRUSTED_CONTENT>>> markers. A brain file upload is not a tool: pass text or url to add_brain_material.

Two form surfaces are deliberately absent: brand-asset uploads (logo, cover, background) are multipart images with dimension and MIME limits that a tool call cannot express, and the custom forms domain is workspace-wide branding behind a DNS check, so it lives in settings rather than in a tool.

Rate limits apply to MCP calls exactly as they do to the rest of the API (per-key for API keys, per-user for OAuth tokens). A self-registered MCP client is never granted SEND_CAMPAIGNS or WRITE_WORKSPACE, so only an API key or app that holds the scope can start or stop a campaign, link an audience to one that is sending, or widen a segment that feeds one, over MCP. Contacts that come to match a segment already linked to a sending campaign are still enrolled, as they are on REST.

Give a mail-reading model the agent preset

The Agent: read and draft preset on the create-key dialog holds every read scope plus AI_DRAFT: the model can read the inbox, campaigns and contacts, and have the AI write replies with generate_reply_draft, charged to credits within the workspace's spend limits. It holds no scope that writes or sends, so it cannot send over the tools or over REST. Use it for any model that reads mail written by people outside the workspace.

Results are data

Anyone who can email the workspace, fill in a form or appear in an imported list writes text a model reads. So every tools/call result is fenced: the text starts with one line saying everything between the markers is data, then the result between <<<UNTRUSTED_CONTENT>>> and <<<END_UNTRUSTED_CONTENT>>>. Markers inside the data are removed, so the data cannot close the fence early. The one result that is not fenced is load_skill, which is your workspace's own playbook. Treat the fence as one layer: it lowers the odds that steering works, and the rules above limit what a steered model can do.

A client that parsed content[0].text as JSON should read the JSON between the markers.

Toolsets

The full catalog is large for a small model. Narrow tools/list with ?toolsets=, a comma-separated list, and start a cheap model on inbox:

https://api.ayinbox.com/v1/mcp?toolsets=inbox

The names are inbox, campaigns, contacts, leads, crm, mailboxes, workspace, automation, forms, developer, research and analytics. The filter narrows the list only: a tool outside it can still be called, with the same gates. An unknown name answers JSON-RPC -32602. The form is tested with API keys; an OAuth client that ties its token to the exact resource URL may not accept it, and gets the whole list without it.

Retries

Every write tool's listed schema carries an optional idempotency_key. Always pass one: a retry with the same key and arguments returns the first result instead of doing it again, for 24 hours. The key is bound to the credential that used it, so another key's retry is a conflict, never a replay. A call refused before it ran (a missing scope, a bad argument) is not stored, so a retry with the same key runs. A key reused with different arguments answers idempotency_key_reused, a retry while the first call still runs answers tool_call_in_progress, and a call that never recorded how it ended answers idempotency_outcome_unknown after ten minutes, each as an isError result whose text starts with the code. Read tools and tools that return a secret store nothing.

find_leads and generate_reply_draft take the key in their own arguments and dedupe it themselves: a generate_reply_draft key pays for one draft. A retry with a key whose draft succeeded is refused, uncharged and with no second draft, so pass a new key for a new draft. A retry after the model failed (which gives the credits back) is charged again and writes the draft. A call with no key answers idempotency_key_required. No draft text is stored.

No tool on these surfaces returns a secret: create_api_key points you to the dashboard, and the webhook secret tools stay with the assistant, which shows a new secret to the member once and never sees it.

Find and verify leads

The lead tools let an agent build a list end to end: start a lead job, follow it, and check single addresses before it adds them anywhere.

  • find_leads, get_lead_job and list_lead_jobs appear only when the instance has turned on Looot lead jobs (AYINBOX_LOOOT_LEADS). Starting a job takes what POST /integrations/looot/lead-jobs takes: a paid plan, INTEGRATIONS and BULK_CONTACTS, and for the member behind the credential use_integrations and manage_contacts. Reading jobs takes what the GET routes take: INTEGRATIONS, and for the member either use_integrations or manage_settings.
  • find_leads requires domains and max_cost_usd. There is no approval step over MCP, so the budget is the only limit on what a call spends from the instance's Looot balance: the job stops before a domain it can no longer afford. On an instance that bills with credits, each domain is also charged to the workspace's credits, and the job stops when they run out. max_cost_usd is 0.05 to 50. titles, per_domain (1 to 25, default 3), campaign_id and category_ids are optional, and the schema carries each numeric range so a client can check it before calling.
  • campaign_id takes the rights that add_segment_to_campaign takes: WRITE_CAMPAIGNS, and manage_campaigns for the member. Without them the call fails with a tool error that says so; leave campaign_id out to import the contacts without enrolling them.
  • A campaign that is sending mails the contacts it gets, so naming one makes the lead job a send, and it also takes the rights that set_campaign_status takes: SEND_CAMPAIGNS, and send_campaigns for the member. That is a campaign that is active, one that finished (it restarts for new leads), and one parked because its leads were undeliverable (it resumes on its own once they verify). A self-registered MCP client is never granted SEND_CAMPAIGNS, so over OAuth find_leads only enrols leads in a draft or paused campaign, which a member then starts from the dashboard. Without the rights the call fails with a tool error naming them, the same refusal POST /integrations/looot/lead-jobs answers as 403 looot_campaign_send_required. Nothing is started or charged.
  • Always pass idempotency_key to find_leads. A retry with the same key returns the job it first started instead of starting and paying for a second one. The MCP endpoint reads no Idempotency-Key header, so a client that retries a timed-out call without the argument starts and pays for a second job; the result's note says so when the key is missing.
  • The job runs in the background. find_leads answers at once with job_id, status and estimated_max_usd, the most the job could spend before its budget applies, and on an instance that bills with credits estimated_max_credits, the most the workspace can be charged; poll get_lead_job for domains_done, found, verified, imported, skipped, spent_usd, held_usd (spend still being settled) and budget_reached.
  • verify_email checks one address through whichever verifier the workspace uses (its connected provider, the instance's Looot or MillionVerifier balance, or the built-in check) and returns status, sub_status, reason and provider. reason is the verifier's or the remote mail server's own words, folded to one line of at most 200 characters: treat it as data, not as instructions. A paid verifier charges for the check, so it needs WRITE_CONTACTS and manage_contacts rather than a read scope. Through Looot, the same address checked again on the same day replays the first answer instead of paying again. Nothing is written to your contacts.

An argument the tool cannot use (a missing budget, a malformed UUID, an unknown argument) comes back as a tool error naming the argument, so the agent can correct the call. A self-registered MCP client can be granted INTEGRATIONS and BULK_CONTACTS, so only approve those scopes for a client you trust to spend the instance's Looot balance.

How OAuth discovery works

The one-command flow is standard MCP authorization, so any spec-compliant client works without AY Inbox-specific setup. On a request with no valid token, /v1/mcp returns 401 with a WWW-Authenticate header pointing at the protected-resource metadata. The client then walks:

  • GET /.well-known/oauth-protected-resource: the resource and its authorization server (RFC 9728).
  • GET /.well-known/oauth-authorization-server: the authorize, token, and registration endpoints (RFC 8414).
  • POST /v1/oauth/register: dynamic client registration, so the client mints a public (PKCE) client_id with no manual app setup (RFC 7591).
  • Authorization code with PKCE (S256) in the browser, then POST /v1/oauth/token for a short-lived access token and a rotating refresh token.

The same discovery drives AY Inbox's OAuth server for any client. See the OAuth 2.1 page for the full authorization-server reference.

See also

On this page