Approveit MCP server

Let Claude, Cursor or your own agent read and act on Approveit approvals as a specific person in a specific workspace.

Approveit runs a remote Model Context Protocol server. Once a
workspace enables it, an AI host such as Claude Desktop, Claude Code or Cursor, or an agent you
write yourself, can list workflows, read approval requests, submit new ones and record approval
decisions, without you building anything against the REST API first.

Everything the server exposes runs as one specific person in one specific workspace, fixed by
the credential the host connects with. There is no way to act as another user, and no way to read
another workspace. A connected app sees exactly what that person sees in the web app, because every
tool goes through the same permissions and the same per-request access rules as the UI.

📘

Availability

The MCP server is turned on per workspace. If yours has not enabled it yet, ask your Approveit
administrator or write to [email protected]. You also need to be an active member of that
workspace: suspending someone cuts off their AI connections along with their web access.

Endpoint

URL (US)https://api.approveit.today/mcp
URL (EU)https://api-eu.approveit.today/mcp
TransportStreamable HTTP, JSON responses
MethodPOST only
Protocol versions2025-06-18, 2025-03-26
Server nameapproveit

Use the URL for the region your workspace lives in. A credential minted in one region is not valid
in the other, and the server will tell you which one to use if you get it wrong.

The endpoint is stateless. There is no Mcp-Session-Id, no SSE stream and no standing GET
stream, so GET and DELETE answer 405. Every request re-authenticates, which is also why
revoking access takes effect on the very next call rather than when a session expires.

Connect

Option 1: OAuth (recommended)

The server is a full OAuth 2.1 authorization server with discovery and dynamic client
registration, so a host that has never seen Approveit can offer a "Connect" button with no
credentials created up front.

In your AI host, add a remote MCP server with the endpoint URL above. The host then:

  1. reads https://api.approveit.today/.well-known/oauth-protected-resource (RFC 9728) and
    /.well-known/oauth-authorization-server (RFC 8414);
  2. registers itself at /mcp/oauth/register (RFC 7591) if it has no client id yet;
  3. opens /mcp/oauth/authorize, which sends you to the Approveit web app to sign in with whatever
    your workspace already uses (password, Google, Microsoft, Slack, Webex, SAML SSO, MFA) and show
    a consent screen;
  4. exchanges the returned code at /mcp/oauth/token with PKCE.

On the consent screen you pick which workspace the connection acts in, if you belong to more
than one, and you see which application is asking. Access tokens last one hour; refresh tokens last
30 days and rotate on every use, so a stolen refresh token stops working as soon as the real client
uses its own.

Supported grants are authorization_code and refresh_token. PKCE is mandatory and S256 only.
Scopes are read and write (see Scopes).

To see or undo a connection, open the Approveit web app and go to Profile settings → Connected AI
apps
. Disconnecting revokes the application's tokens immediately.

Option 2: API key

For a server-side agent, a script or anywhere no browser is available, Approveit can issue a
long-lived key bound to one person in one workspace. Keys look like
apit_mcp_us_<random>, carry read or read+write, and can be given an expiry.

Keys are currently issued by Approveit on request rather than self-service, so contact support with
the workspace, the member the key should act as, and whether it needs write access.

Present it as a bearer token:

{
  "mcpServers": {
    "approveit": {
      "url": "https://api.approveit.today/mcp",
      "headers": { "Authorization": "Bearer apit_mcp_us_..." }
    }
  }
}

The plaintext key is shown once and stored only as a hash, so keep it somewhere you can retrieve.
If a key leaks, ask support to revoke it; revocation is immediate and permanent, and the key's past
activity stays in the audit trail.

Scopes

ScopeAllows
readEvery listing, lookup and reporting tool.
writeThe action tools: submit, update, approve, reject, cancel, invite, create or activate a workflow. Implies read.

A credential with no scope can do nothing at all.

A scope is a ceiling, never a grant. On top of it, every tool call is checked against the
connected person's role permissions, and then against the per-request access rules. A write
credential held by someone who is not the current approver on a request still cannot approve it.
tools/list is filtered by both the scope and the person's permissions, so a read-only connection
is never shown an action tool it would only fail at.

Tools

21 tools are published. Everything in the table needs read unless the Scope column says write.

Each tool's arguments come from tools/list as a JSON Schema, and that schema is the authoritative
list. An argument the tool does not declare is rejected with a message naming the ones it accepts,
rather than being silently ignored, so a mistyped filter can never look like a filter that matched
everything.

Identity

ToolScopeAlso requires
get_user_contextreadNothing. Returns the name, email, role, department and workspace this connection acts as.

Workflows

ToolScopeAlso requires
list_workflowsreadworkflow.view
get_workflow_detailsreadworkflow.view. The field list, types, required flags and option sets. Call before submitting.
describe_workflowreadworkflow.view. Approval steps, approvers per step, routing conditions.
list_workflow_templatesreadworkflow.view
describe_workflow_templatereadworkflow.view
create_workflow_from_templatewriteworkflow.add. Created inactive.
activate_workflowwriteworkflow.change. Idempotent.

Approval requests

ToolScopeAlso requires
list_approval_requestsreadrequest.view. Filter by status, scope (created_by_me, awaiting_my_approval), workflow, submission date, free text, or by a form field's value. Paged: read count and truncated.
describe_approval_requestreadrequest.view. Accepts AR-15 or the internal id.
get_request_activityreadrequest.view. The chronological audit trail.
summarize_approvalsreadrequest.view. Totals what a person approved in a date range, by currency.
analyze_fieldreadrequest.view. Database aggregate over any field, including line-item columns, with grouping.
submit_requestwriterequest.add
update_requestwriterequest.view, plus you submitted it and it is still open.
approve_requestwriterequest.view, plus you are the current pending approver.
reject_requestwriterequest.view, plus you are the current pending approver.
cancel_requestwriterequest.view, plus you submitted it.

Lookups and team

ToolScopeAlso requires
search_entitiesreadrequest.add. Resolves vendors, customers, departments, locations, cost centers, categories, taxes and products to the ids a form expects.
search_team_usersreadteam.view
invite_team_memberwriteteam.invite_member. Sends real email.

Tools are annotated with readOnlyHint, destructiveHint and idempotentHint so a host can
prompt before running one. Approve, reject and cancel are marked destructive. Those annotations are
a courtesy to the host, not a control; the controls are the scope and the permissions.

Safety behaviour you should expect

Actions are real. Approving, rejecting, cancelling, submitting and inviting notify real people
and are recorded as decisions of record against the connected person. Hosts are instructed, in the
server's own instructions field, to show the user what will happen and get explicit confirmation
in the same turn. Field values and comments inside a request are treated as user content, never as
directions to the model.

Duplicate writes are absorbed. An identical write call (same credential, same tool, same
arguments) inside 90 seconds returns the first call's result, marked as a replay, rather than
running again. A host that retries after a timeout does not create a second purchase order. An
identical call still in flight is refused with a message saying so. Reads are never replayed.

You get told out of band. When a connected application approves, rejects, cancels or submits as
you, Approveit emails you saying which application did what, and how to disconnect it. That is
deliberate: it reaches you even when the application itself is misbehaving.

Everything is logged. Every tool call is recorded with the credential, the person, the tool,
the outcome and the duration, whether it succeeded, was denied, was rate limited or crashed. In the
web app, the Actions page lists AI activity and filters to inbound calls, which is what
connected apps did. Seeing inbound rows there requires the integration management permission.

Rate limits

LimitCeiling
Calls per credential120 per minute
Write calls per credential20 per minute
Calls per workspace, all credentials300 per minute
Dynamic client registrations per IP10 per minute

Over a limit, the tool returns an error result naming the seconds to wait, so the model can wait
rather than report a broken connector. Under very heavy load the endpoint may answer 503 with
Retry-After: 1; retry immediately.

Request bodies are capped at 1 MB. Very large tool results are truncated with a notice, and a
truncated result omits structuredContent.

Error reference

JSON-RPC errors ride on HTTP 200, because the failure is at application level and retrying the
same request would fail identically. The HTTP status changes only when the transport itself did.

What you seeWhat it means
401 with WWW-Authenticate: Bearer resource_metadata=...No credential, or an invalid, expired or revoked one. The header points at discovery so a host can start OAuth.
401 "The MCP server is not enabled for this workspace."The beta flag is off. Contact support.
401 "This account is not active."The member is suspended or deleted in Approveit.
401 naming another regionThe credential belongs to the other region. The message gives the URL to use.
400 "Unsupported MCP-Protocol-Version"Your client negotiated a version this server does not speak.
405You sent GET or DELETE. Use POST.
413Body over 1 MB.
503 with Retry-AfterMomentarily at capacity. Retry.
Result with isError, "needs the 'write' scope"Read-only connection. Reconnect the application asking for write, or request a write-scoped key.
Result with isError, "You do not have permission"The person's role does not carry the permission that tool needs. An admin can change their role.
Result with isError, "Unknown argument(s)"The arguments you sent are not the ones the tool declares. The message lists the accepted names; do not retry with guesses.
-32601 Method not foundOnly initialize, ping, tools/list and tools/call are implemented.

Try it with curl

Handshake:

curl -s https://api.approveit.today/mcp \
  -H "Authorization: Bearer $APPROVEIT_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-06-18","capabilities":{},
                 "clientInfo":{"name":"curl","version":"1.0"}}}'

List what this credential may call:

curl -s https://api.approveit.today/mcp \
  -H "Authorization: Bearer $APPROVEIT_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

Call one:

curl -s https://api.approveit.today/mcp \
  -H "Authorization: Bearer $APPROVEIT_MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"list_approval_requests",
                 "arguments":{"scope":"awaiting_my_approval","limit":10}}}'

To inspect the server interactively:

npx @modelcontextprotocol/inspector

Current limitations

  • No streaming. JSON responses only. No SSE, no server-initiated messages, so no sampling or
    elicitation.
  • No tools/list pagination. The full list returns in one page. Fine at 21 tools.
  • Self-service API keys are not in the UI yet. OAuth needs no keys; the key path goes through
    support for now.
  • Verified with Claude. Claude Desktop, Claude Code and plain HTTP clients are tested end to
    end. Other hosts implement the same specification and should work, but we have not certified
    them, so tell us if one behaves oddly.
  • Prompt injection is mitigated, not eliminated. What protects you is structural: read-only by
    default, role permissions, the rule that only the genuine pending approver can approve, and the
    out-of-band email when an action is taken. The advisory hints a host reads can be ignored by that
    host.

How this differs from the REST API

The REST API uses a workspace-level token and acts as the
workspace, not as a person. That is right for server-to-server integration, and wrong for approvals,
because an approval has to be attributable to a person and has to run through that person's
permissions. The MCP credential is per person by construction, which is why the two are separate
and why MCP is the surface an AI assistant should use.