Elaichi MCP OAuth API

The MCP OAuth API from Elaichi — 5 operation(s) for mcp oauth.

GitHub OpenAPI

Operations 6

POST /oauth/register Register an MCP client (RFC 7591) #
GET /oauth/authorize Start an authorization (browser redirect) #
POST /oauth/token Exchange a code or refresh token for an access token #
POST /oauth/revoke Revoke a token (RFC 7009) #
GET /oauth/userinfo Get user info (OIDC) #
POST /oauth/userinfo Get user info (OIDC) #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/elaichi-ai:elaichi-ai-mcp-oauth-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

elaichi-ai-mcp-oauth-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Elaichi MCP OAuth API
  version: 0.1.0
  description: 'Control-plane HTTP API for Elaichi, the MCP control plane for SaaS teams. Authenticate with an organization API token (create one in the Elaichi app under Settings → API tokens) sent as a bearer token, plus the `X-Organization-Id` header on org-scoped routes; the MCP and OAuth endpoints use their own protocols. A few actions are deliberately not available to an API token and must be done by a person in the Elaichi app: managing API and SCIM tokens, creating an organization, approving an access request, deleting an organization, and changing the two-factor requirement. The operations concerned say so. Endpoints used only by the Elaichi app itself (sign-in, consent screens, browser redirects, inbound webhooks) are not documented.'
servers:
- url: https://api.elaichi.ai
  description: Production
tags:
- name: MCP OAuth
paths:
  /oauth/register:
    post:
      operationId: registerOAuthClient
      summary: Register an MCP client (RFC 7591)
      tags:
      - MCP OAuth
      responses:
        '201':
          description: Client registered
          content:
            application/json:
              schema:
                type: object
                properties:
                  client_id:
                    type: string
                    description: OAuth client id (`ocli_…`).
                  client_secret:
                    type: string
                    description: Present only for confidential clients. Returned exactly once — store it now.
                  client_id_issued_at:
                    type: integer
                    description: Unix seconds.
                  client_secret_expires_at:
                    type: integer
                    description: '`0` — the secret does not expire.'
                  client_name:
                    type: string
                  redirect_uris:
                    type: array
                    items:
                      type: string
                  grant_types:
                    type: array
                    items:
                      type: string
                  response_types:
                    type: array
                    items:
                      type: string
                  token_endpoint_auth_method:
                    type: string
                  client_uri:
                    type: string
                  logo_uri:
                    type: string
                  scope:
                    type: string
                    description: Space-delimited list of every scope this server supports.
        '400':
          description: 'RFC 6749 error body: `invalid_client_metadata` or `invalid_redirect_uri`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited (`temporarily_unavailable`), with `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
      description: 'Dynamic client registration. Public and unauthenticated by protocol — any MCP client may register itself, which is why every field is length- and count-bounded and every redirect URI is scheme-checked. The response is the only time `client_secret` is ever returned; it is stored hashed and cannot be read back. Omitting `token_endpoint_auth_method` (or sending `none`) registers a public client that authenticates with PKCE alone, which is how every native MCP client works. Registration grants nothing on its own: a registered client can only act after a user approves it at `GET /oauth/authorize`. Rate limited per client IP.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - redirect_uris
              properties:
                client_name:
                  type: string
                  maxLength: 200
                  description: Shown on the consent screen. Defaults to "MCP client". Not verified — see `client_name` on the grant schema.
                redirect_uris:
                  type: array
                  minItems: 1
                  maxItems: 10
                  items:
                    type: string
                    maxLength: 2048
                  description: 'Where authorization codes may be delivered, matched later by exact string. Accepted: any `https` URI; `http` only on loopback (`localhost`, `127.0.0.1`, `[::1]`), the RFC 8252 native-app pattern; or a private-use reverse-DNS scheme such as `com.example.app:/callback`. Fragments and wildcards are rejected.'
                grant_types:
                  type: array
                  maxItems: 8
                  items:
                    type: string
                    enum:
                    - authorization_code
                    - refresh_token
                  description: Defaults to both. Any other value is rejected.
                response_types:
                  type: array
                  maxItems: 8
                  items:
                    type: string
                    enum:
                    - code
                  description: Only `["code"]` is supported.
                token_endpoint_auth_method:
                  type: string
                  enum:
                  - none
                  - client_secret_post
                  - client_secret_basic
                  description: Defaults to `none` (public client, PKCE only). Choosing either `client_secret_*` method issues a secret that the token and revocation endpoints will then require.
                client_uri:
                  type: string
                  maxLength: 2048
                logo_uri:
                  type: string
                  maxLength: 2048
                  description: 'Rendered on the consent screen beside the app name, `https:` only — an `http:`, `data:` or `blob:` value falls back to a generated monogram, as does one that fails to load. It is fetched with `referrer-policy: no-referrer` and without credentials. Note that it proves nothing: registration is unauthenticated, so this is a mark the registrant chose, exactly like `client_name`, and the consent screen says so to the user. `redirect_uris` remains the only identity signal a lookalike cannot forge.'
                scope:
                  type: string
                  maxLength: 200
                  description: Accepted and ignored; scopes are chosen per authorization.
  /oauth/authorize:
    get:
      operationId: startOAuthAuthorization
      summary: Start an authorization (browser redirect)
      tags:
      - MCP OAuth
      responses:
        '302':
          description: Redirect to the consent screen (or to login, then the consent screen), or back to the registered redirect URI carrying `error`/`error_description`/`state`.
        '400':
          description: 'Rendered error (never a redirect): unknown `client_id`, missing or unregistered `redirect_uri`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited (`temporarily_unavailable`), with `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
      description: 'Validates the request, parks it server-side, and redirects the browser to Elaichi''s consent screen — this endpoint never renders HTML and never returns a code directly. PKCE is mandatory: `code_challenge` with `code_challenge_method=S256`; an absent method is rejected rather than defaulting to `plain`. Nothing about the request travels through the browser afterwards — the redirect carries only an opaque request id, so there is nothing for a user-agent to tamper with. If the user is not signed in they are sent to login first and returned to the same consent URL. The consent screen then calls `GET /oauth/authorize-request/{id}` and one of approve/deny. Errors before the redirect URI is validated are rendered as an RFC 6749 error body (an unregistered URI must never be redirected to, or the error path becomes an open redirect); errors after it are appended to the registered redirect URI as `error`/`error_description` with `state` echoed back. Rate limited per `client_id`.'
      security: []
      parameters:
      - name: client_id
        in: query
        required: true
        description: From `POST /oauth/register` (`ocli_…`).
        schema:
          type: string
      - name: redirect_uri
        in: query
        required: true
        description: Must exactly match one of the URIs registered for this client.
        schema:
          type: string
      - name: response_type
        in: query
        required: true
        description: Only `code` is supported.
        schema:
          type: string
          enum:
          - code
      - name: code_challenge
        in: query
        required: true
        description: 'PKCE challenge: base64url, 43–128 characters.'
        schema:
          type: string
          minLength: 43
          maxLength: 128
      - name: code_challenge_method
        in: query
        required: true
        description: Must be `S256`. `plain` and an absent value are both rejected.
        schema:
          type: string
          enum:
          - S256
      - name: scope
        in: query
        required: false
        description: 'MCP scopes, space-delimited on the wire. `mcp:read` reads org data; `mcp:write` creates and changes it; `mcp:destructive` deletes and removes access in Elaichi; `mcp:tools` runs the third-party tools the user has connected, deletes included. `openid` and `email` are identity scopes: they authorize `GET/POST /oauth/userinfo` and grant no MCP access on their own — a grant holding only `openid email` reaches no `mcp:*` operation and no connected tool. Requesting `mcp:write` or `mcp:destructive` implies `mcp:read` — a client that can mutate what it cannot read is not a useful capability. Unknown scope values are rejected rather than silently dropped. A request with no `scope` at all defaults to `mcp:read`.'
        schema:
          type: string
      - name: state
        in: query
        required: false
        description: Opaque client value, echoed on every redirect back.
        schema:
          type: string
      - name: resource
        in: query
        required: false
        description: RFC 8707 audience. Must identify this server by origin; a resource on another origin is an audience-confusion attempt and is rejected with `invalid_target`.
        schema:
          type: string
          format: uri
  /oauth/token:
    post:
      operationId: issueOAuthToken
      summary: Exchange a code or refresh token for an access token
      tags:
      - MCP OAuth
      responses:
        '200':
          description: Tokens issued
          content:
            application/json:
              schema:
                type: object
                required:
                - access_token
                - token_type
                - expires_in
                - refresh_token
                - scope
                properties:
                  access_token:
                    type: string
                    description: Bearer token for `POST /mcp`. Short-lived; re-check `expires_in` rather than assuming.
                  token_type:
                    type: string
                    enum:
                    - Bearer
                  expires_in:
                    type: integer
                    description: Access token lifetime in seconds.
                  refresh_token:
                    type: string
                    description: Single-use — rotated on every refresh.
                  scope:
                    type: string
                    description: Space-delimited granted scopes.
        '400':
          description: '`invalid_request`, `invalid_grant` (including replay, which also revoked the grant), `invalid_scope`, `invalid_target` or `unsupported_grant_type`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: '`invalid_client` — unknown client, or client authentication failed.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited (`temporarily_unavailable`), with `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
      description: 'Accepts `application/x-www-form-urlencoded` (RFC 6749) or `application/json`. Public clients authenticate with PKCE alone; confidential clients MUST present their secret, by HTTP Basic or `client_secret` in the body.


        `grant_type=authorization_code` requires `code`, `redirect_uri` and `code_verifier`. The redirect URI and the client must match those the code was issued to.


        `grant_type=refresh_token` requires `refresh_token`, and rotates it — the presented token is consumed and a new one returned. An optional `scope` may narrow the grant, never widen it; the grant row is the authority, not the copy on the presented token.


        Replay is treated as compromise, not as a plain error: presenting an authorization code or a refresh token twice REVOKES THE WHOLE GRANT (OAuth 2.1 §4.1.3 / §6.1), invalidating every token derived from it. The user must re-authorize from `GET /oauth/authorize`. Responses are `Cache-Control: no-store`. Rate limited per `client_id`.'
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - grant_type
              properties:
                grant_type:
                  type: string
                  enum:
                  - authorization_code
                  - refresh_token
                client_id:
                  type: string
                client_secret:
                  type: string
                  description: Confidential clients only; HTTP Basic is also accepted.
                code:
                  type: string
                  description: authorization_code grant.
                redirect_uri:
                  type: string
                  description: authorization_code grant; must match the code.
                code_verifier:
                  type: string
                  description: authorization_code grant; PKCE verifier for the S256 challenge.
                refresh_token:
                  type: string
                  description: refresh_token grant.
                scope:
                  type: string
                  description: refresh_token grant; may only narrow the grant.
                resource:
                  type: string
                  description: RFC 8707 audience; must identify this server.
          application/json:
            schema:
              type: object
              additionalProperties:
                type: string
  /oauth/revoke:
    post:
      operationId: revokeOAuthToken
      summary: Revoke a token (RFC 7009)
      tags:
      - MCP OAuth
      responses:
        '200':
          description: Always returned on a well-formed, client-authenticated request. Body is `{}`.
          content:
            application/json:
              schema:
                type: object
        '400':
          description: '`invalid_request` — `token` is missing.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: '`invalid_client` — client authentication failed.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited (`temporarily_unavailable`), with `Retry-After`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
      description: Revoking either an access token or a refresh token kills the ENTIRE grant it belongs to, not just the token presented — a client the user disconnected must not keep a live refresh token. Accepts form-encoded or JSON and authenticates the client exactly as `POST /oauth/token` does. Always answers 200, including for unknown, already-revoked, or another client's token, so this endpoint cannot be used as a token oracle. To revoke from the user side instead, use `DELETE /oauth/grant/{id}`.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - token
              properties:
                token:
                  type: string
                  description: An access token or a refresh token.
                token_type_hint:
                  type: string
                  description: Accepted and ignored; both types are looked up.
                client_id:
                  type: string
                client_secret:
                  type: string
          application/json:
            schema:
              type: object
              additionalProperties:
                type: string
  /oauth/userinfo:
    get:
      operationId: getUserinfo
      summary: Get user info (OIDC)
      tags:
      - MCP OAuth
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                - sub
                properties:
                  sub:
                    type: string
                    description: 'Stable, opaque subject identifier (`usr_…`). Same value for every client — `subject_types_supported: ["public"]`.'
                  email:
                    type: string
                    format: email
                    description: Present only when the grant holds the `email` scope.
                  email_verified:
                    type: boolean
                    description: Present only alongside `email`. Derived from how the account authenticates (a consumed email code, a signed SSO assertion over a DNS-verified org domain, an identity provider that publishes and enforces its own verification signal) — never a self-reported flag.
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: 'No credential supplied → `invalid_request`, no `error` parameter on the challenge (RFC 6750 §3.1: a request carrying no credential has failed nothing yet). A bad, expired or revoked credential → `invalid_token`, with `error="invalid_token"` on the challenge.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
          headers:
            WWW-Authenticate:
              description: RFC 6750 §3 challenge, `error`/`error_description` present only when a credential was supplied.
              schema:
                type: string
        '403':
          description: A valid token whose grant does not hold the `openid` scope. `insufficient_scope` rather than 401 — re-authenticating would not help, the client must send the user back through consent for more.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
          headers:
            WWW-Authenticate:
              description: Carries `error="insufficient_scope"` and `scope="openid"`.
              schema:
                type: string
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: 'Returns claims about the user who authorized the grant behind the bearer token. Bearer-authenticated by the same `verifyMcpAccessToken` function `POST /mcp` uses, so a revoked grant, a deactivated user and an expired token are all refused exactly as they are there. Never cached: this is the one response in this router that can carry a person''s address.


        `sub` is always present — the stable `usr_…` primary key, never reassigned and never pairwise. `email` and `email_verified` are returned together, and only when the grant holds the `email` scope; a client that asked only for `openid` gets a subject and no contact detail. The two never appear separately, since an `email` without the flag beside it would invite a relying party to assume verification.'
      security:
      - bearerAuth: []
    post:
      operationId: postUserinfo
      summary: Get user info (OIDC)
      tags:
      - MCP OAuth
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                required:
                - sub
                properties:
                  sub:
                    type: string
                    description: 'Stable, opaque subject identifier (`usr_…`). Same value for every client — `subject_types_supported: ["public"]`.'
                  email:
                    type: string
                    format: email
                    description: Present only when the grant holds the `email` scope.
                  email_verified:
                    type: boolean
                    description: Present only alongside `email`. Derived from how the account authenticates (a consumed email code, a signed SSO assertion over a DNS-verified org domain, an identity provider that publishes and enforces its own verification signal) — never a self-reported flag.
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: 'No credential supplied → `invalid_request`, no `error` parameter on the challenge (RFC 6750 §3.1: a request carrying no credential has failed nothing yet). A bad, expired or revoked credential → `invalid_token`, with `error="invalid_token"` on the challenge.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
          headers:
            WWW-Authenticate:
              description: RFC 6750 §3 challenge, `error`/`error_description` present only when a credential was supplied.
              schema:
                type: string
        '403':
          description: A valid token whose grant does not hold the `openid` scope. `insufficient_scope` rather than 401 — re-authenticating would not help, the client must send the user back through consent for more.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
          headers:
            WWW-Authenticate:
              description: Carries `error="insufficient_scope"` and `scope="openid"`.
              schema:
                type: string
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      description: 'Returns claims about the user who authorized the grant behind the bearer token. Bearer-authenticated by the same `verifyMcpAccessToken` function `POST /mcp` uses, so a revoked grant, a deactivated user and an expired token are all refused exactly as they are there. Never cached: this is the one response in this router that can carry a person''s address.


        `sub` is always present — the stable `usr_…` primary key, never reassigned and never pairwise. `email` and `email_verified` are returned together, and only when the grant holds the `email` scope; a client that asked only for `openid` gets a subject and no contact detail. The two never appear separately, since an `email` without the flag beside it would invite a relying party to assume verification.'
      security:
      - bearerAuth: []
components:
  schemas:
    Error:
      type: object
      required:
      - error
      description: The Elaichi error envelope. The MCP OAuth endpoints under `/oauth` are the exception — they return RFC 6749 errors instead (see `OAuthError`).
      properties:
        error:
          type: object
          required:
          - message
          - code
          properties:
            message:
              type: string
              description: Human-readable. 5xx messages are deliberately generic.
            code:
              type: string
              description: Stable machine-readable code, e.g. `not_found`, `subscription_required`.
            details:
              description: Structured detail for codes that carry one, e.g. `toolboxes_need_resolution`.
            feature:
              type: string
              description: The gated feature, on entitlement failures.
            plan:
              type:
              - string
              - 'null'
              enum:
              - personal
              - gold
              - black
              - null
          additionalProperties: true
    OAuthError:
      type: object
      required:
      - error
      properties:
        error:
          type: string
          description: RFC 6749 / 7591 / 8707 error code, e.g. `invalid_grant`, `invalid_client`, `invalid_redirect_uri`.
        error_description:
          type: string
      additionalProperties: true
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: session or API token
      description: '`Authorization: Bearer <token>` with an org-scoped API token (`elch_{org_id}_{64 hex}`), created in the Elaichi app under Settings → API tokens. The Elaichi app itself signs in with a session cookie, which is also accepted but is not meant for integrations. Two other credential families exist and are NOT interchangeable with this one: an MCP OAuth access token (`POST /mcp` only) and a SCIM token (`/scim/v2/*` only).'
    mcpOAuth:
      type: oauth2
      description: MCP OAuth access token, used only by `POST /mcp` — not interchangeable with `bearerAuth`. Discovered per RFC 9728/8414 at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`.
      flows:
        authorizationCode:
          authorizationUrl: https://api.elaichi.ai/oauth/authorize
          tokenUrl: https://api.elaichi.ai/oauth/token
          scopes:
            mcp:read: Anything you can already see in Elaichi. Never secret values. It can also ask an admin for access to something restricted for you.
            mcp:write: Create and change teams, roles, toolboxes, templates and connections, and share access.
            mcp:destructive: Permanently delete data and remove access in Elaichi. This cannot be undone.
            mcp:tools: Run tools from the toolboxes you choose next, deleting in your connected apps included. It can also ask an admin for access to something restricted for you.
            openid: That you are signed in, and the user ID we use for you. Nothing else.
            email: Your email address, and whether it is verified. Apps use this to check your domain.