mails-ai Connected Apps API

The apps connected by signing in (the mails CLI, Claude, ChatGPT …): list and revoke them.

Operations 2

GET /v1/oauth/grants List connected apps #
DELETE /v1/oauth/grants/{id} Revoke a connected app #

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/mails-ai:mails-ai-connected-apps-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

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

mails-ai-connected-apps-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Mails.ai Connected Apps API
  version: 0.2.0
  description: 'The Mails.ai API gives AI agents a real email identity: send and receive

    transactional email. Mail received within your plan''s limits is scanned for prompt injection and

    reaches you as a typed event. With classification turned on (a paid-plan feature), new mail can also

    be read for intent; a reply in a thread never is.


    ## Authentication

    All endpoints take a Bearer API key:


    ```

    Authorization: Bearer mk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    ```


    Keys carry one or more scopes — `send`, `read`, `draft` (writes drafts, but can neither send nor

    schedule one) and `manage` — and a mode: **live** (`mk_live_…`) or **test** (`mk_test_…`).


    ## Test mode

    A `mk_test_` key exercises the entire API — real validation, persisted resources, real

    `message.sent`/`*.received` events and webhooks — without sending real email or incurring cost. Build and

    test your whole integration on a test key, then flip one character to `mk_live_` to go live.

    `POST /v1/test/inbound` (test key only) lets you simulate inbound mail end-to-end.


    ## Idempotency

    Send endpoints accept an `Idempotency-Key` header; a repeated key replays the stored response instead of

    sending twice, with an `Idempotent-Replay: true` header.


    ## Pagination

    List endpoints are cursor-paginated: pass `limit` (1–100) and the `next_cursor` from the previous page.


    ## Errors

    Errors use a stable envelope: `{ "error": { "type", "code", "message", "param", "request_id" } }`. Every

    response carries an `X-Request-Id` header for support correlation.'
  contact:
    name: Mails.ai Support
    url: https://mails.ai
    email: support@mails.ai
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
- url: https://api.mails.ai
  description: Production
security:
- bearerAuth: []
tags:
- name: Connected Apps
  description: 'The apps connected by signing in (the mails CLI, Claude, ChatGPT …): list and revoke them.'
paths:
  /v1/oauth/grants:
    get:
      tags:
      - Connected Apps
      summary: List connected apps
      operationId: listConnectedApps
      description: '**Requires scope:** `manage`. The apps connected to this workspace by signing in, whoever in it connected them: the mails CLI and apps such as Claude, ChatGPT or Cursor. Only those that can still act are listed, newest first: one revoked, or whose tokens have all expired or been signed out, is not. Settings › Connected apps in the dashboard shows the same apps to everyone in the workspace. There, a member can revoke the apps they connected, and an owner or admin can revoke any of them. Closed to apps connected by sign-in (403 `connected_app_not_allowed`), and to a key tied to one agent (403): use an API key with `manage` that is not tied to an agent, or the mails CLI.'
      parameters:
      - name: limit
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 25
        description: Page size (1–100).
      - name: cursor
        in: query
        required: false
        schema:
          type: string
        description: Opaque cursor from a previous page's `next_cursor`.
      responses:
        '200':
          description: A page of connected apps.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ConnectedApp'
                    description: The items on this page, at most `limit` of them.
                  has_more:
                    type: boolean
                    description: 'True when more items follow this page: pass `next_cursor` as `cursor` to get them.'
                  next_cursor:
                    type: string
                    description: Pass as `cursor` to fetch the next page. Absent on the last page.
                required:
                - data
                - has_more
        '400':
          description: Invalid request — malformed JSON or a field failed validation. Also `feature_not_enabled` when this server has a feature switched off (custom domains before they are available).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The API key lacks the required scope (`insufficient_scope`), or the token belongs to an app connected by sign-in and this operation is closed to connected apps (`connected_app_not_allowed`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-required-scope: manage
  /v1/oauth/grants/{id}:
    delete:
      tags:
      - Connected Apps
      summary: Revoke a connected app
      operationId: revokeConnectedApp
      description: '**Requires scope:** `manage`. Ends the sign-in, as Revoke in Settings › Connected apps does: every access and refresh token it was issued stops working at once, and the app has to be approved again to get back in. The API keys and webhook endpoints it created, and the webhook signing secrets it was shown, stay; the response counts them, so revoke and rotate those yourself. (Only when one of its refresh tokens is used twice, a sign of theft, does mails.ai remove them as well, and rotate those secrets again.) Idempotent: revoking an app already revoked returns its existing `revoked_at`. Closed to apps connected by sign-in (403 `connected_app_not_allowed`), and to a key tied to one agent (403): use an API key with `manage` that is not tied to an agent, or the mails CLI.'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
        description: The connected app's id (`oag_…`), from List connected apps.
      responses:
        '200':
          description: The revoked app, with what it left behind.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectedAppRevoked'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: The API key lacks the required scope (`insufficient_scope`), or the token belongs to an app connected by sign-in and this operation is closed to connected apps (`connected_app_not_allowed`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Resource not found in this workspace, or the agent a send names is archived (`agent_archived`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-required-scope: manage
components:
  schemas:
    ConnectedAppRevoked:
      type: object
      properties:
        id:
          type: string
          description: 'The sign-in''s id (`oag_…`): the id in the path.'
        client_id:
          type: string
          description: 'The app''s OAuth client id: the id it registered with, or the address of its client-ID document.'
        client_name:
          type:
          - string
          - 'null'
          description: The name the app gave itself, at most 80 characters, or null when it gave none; `mails CLI` for the mails CLI.
        first_party:
          type: boolean
          description: '`true` for the mails CLI, `false` for an app somebody connected by signing in.'
        scope:
          type: string
          description: 'What the person had approved: `full_access`, `send` or `read`.'
        scopes:
          type: array
          items:
            type: string
            enum:
            - send
            - read
            - manage
          description: 'What its tokens could do until the revoke, in the API key scopes: `full_access` = `send` + `read` + `manage`, `send` = `send` + `read`, `read` = `read`. Never more than the role of the person who approved it gave an app: a Member''s apps never hold `manage`, and a Billing member''s hold `read` only.'
        created_at:
          type: string
          format: date-time
          description: When the app was first approved.
        approved_at:
          type: string
          format: date-time
          description: When it was last approved before this revoke.
        last_used_at:
          type:
          - string
          - 'null'
          format: date-time
          description: When one of its tokens was last used, recorded at most once a minute. Null when none was used.
        created_api_keys:
          type: integer
          description: API keys it created that still work after the revoke.
        created_webhooks:
          type: integer
          description: Webhook endpoints it created, still in place after the revoke.
        signing_secrets_known:
          type: integer
          description: Other webhook endpoints still signing with a secret it was shown.
        revoked_at:
          type: string
          format: date-time
          description: When the sign-in was ended and its tokens stopped working. For one that was already revoked, the time it ended then.
      required:
      - id
      - client_id
      - client_name
      - first_party
      - scope
      - scopes
      - created_at
      - approved_at
      - last_used_at
      - created_api_keys
      - created_webhooks
      - signing_secrets_known
      - revoked_at
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            type:
              type: string
              enum:
              - invalid_request_error
              - authentication_error
              - permission_error
              - rate_limit_error
              - resource_error
              - payment_error
              - abuse_error
              - api_error
              description: High-level error category.
            code:
              type: string
              enum:
              - invalid_field
              - missing_field
              - missing_param
              - invalid_param_value
              - method_not_allowed
              - invalid_idempotency_key
              - duplicate_resource
              - slug_invalid
              - slug_reserved
              - slug_taken
              - agent_name_conflict
              - body_too_large
              - missing_authorization
              - missing_api_key
              - invalid_api_key
              - expired_api_key
              - revoked_api_key
              - invalid_access_token
              - email_not_verified
              - insufficient_scope
              - connected_app_not_allowed
              - sso_required
              - workspace_mismatch
              - agent_not_owned
              - rate_limit_exceeded
              - daily_limit_exceeded
              - hourly_limit_exceeded
              - monthly_limit_exceeded
              - agent_not_found
              - agent_paused
              - agent_archived
              - resource_not_found
              - recipient_suppressed
              - recipient_not_allowed
              - domain_not_verified
              - ses_role_not_assumable
              - ses_role_external_id_not_required
              - ses_permission_denied
              - ses_setup_failed
              - ses_message_rejected
              - ses_events_not_confirmed
              - payment_required
              - payment_method_failed
              - subscription_canceled
              - billing_state_inconsistent
              - free_tier_exceeded
              - plan_limit_exceeded
              - cold_email_prohibited
              - agent_suspended
              - new_workspace_fanout_exceeded
              - resource_cap_exceeded
              - workspace_cap_exceeded
              - feature_not_enabled
              - test_mode_required
              - live_mode_required
              - unknown_test_address
              - workspace_not_approved
              - spam_pattern_detected
              - classifier_rejected
              - high_injection_score
              - complaint_threshold_exceeded
              - bounce_threshold_exceeded
              - internal_error
              - internal_server_error
              - upstream_error
              - upstream_unavailable
              - service_unavailable
              description: Stable machine-readable error code.
            message:
              type: string
              description: Human-readable explanation.
            param:
              type:
              - string
              - 'null'
              description: The offending request field, when applicable.
            request_id:
              type: string
              description: Echo of the X-Request-Id header for support correlation.
            errors:
              type: array
              items:
                $ref: '#/components/schemas/FieldError'
              description: On a validation failure, EVERY offending field (not just the first). The top-level code/message/param mirror errors[0] for backward compatibility.
            resets_at:
              type:
              - string
              - 'null'
              format: date-time
              description: 'On every 429: when the limit lets the request through again (UTC). Null when waiting will not help, for a cap that only an upgrade or a deletion lifts.'
            retry_after_seconds:
              type:
              - integer
              - 'null'
              description: 'On every 429: the seconds until `resets_at`, null when it is null. On any other error that says how long to wait, such as a 503 `upstream_unavailable`: the seconds its `Retry-After` header gives.'
            upgrade_url:
              type: string
              format: uri
              description: 'On every 402: the dashboard page where the plan can be changed, `https://app.mails.ai/billing`.'
          required:
          - type
          - code
          - message
          description: 'The error, as every error response carries it: `type`, `code`, `message`, `param` and `request_id`, plus `errors` on a validation failure, `resets_at` and `retry_after_seconds` on a 429, `retry_after_seconds` on any other error with a `Retry-After`, and `upgrade_url` on a 402. A failed item in a batch send is the same error, and `retry_after_seconds` is how it says how long to wait, since an item has no headers of its own.'
      required:
      - error
    ConnectedApp:
      type: object
      properties:
        id:
          type: string
          description: The sign-in's id (`oag_…`). Approving the same app again in the same workspace keeps it.
        client_id:
          type: string
          description: 'The app''s OAuth client id: the id it registered with, or the address of its client-ID document. The mails CLI''s is `https://api.mails.ai/oauth/clients/cli`.'
        client_name:
          type:
          - string
          - 'null'
          description: The name the app gave itself; `mails CLI` for the mails CLI.
        first_party:
          type: boolean
          description: '`true` for the mails CLI, `false` for an app somebody connected by signing in (Claude, ChatGPT, Cursor …).'
        scope:
          type: string
          description: 'What the person approved: `full_access`, `send` or `read`.'
        scopes:
          type: array
          items:
            type: string
            enum:
            - send
            - read
            - manage
          description: 'What its tokens may do, in the API key scopes: `full_access` = `send` + `read` + `manage`, `send` = `send` + `read`, `read` = `read`. Never more than the role of the person who approved it gives an app now: a Member''s apps never hold `manage`, and a Billing member''s hold `read` only.'
        created_at:
          type: string
          format: date-time
          description: When it was first approved.
        approved_at:
          type: string
          format: date-time
          description: When it was last approved.
        last_used_at:
          type:
          - string
          - 'null'
          format: date-time
          description: When one of its tokens was last used.
        created_api_keys:
          type: integer
          description: 'API keys it created that still work (only the mails CLI, and keys it made, can create any). Revoking the app leaves them working: revoke them under API keys.'
        created_webhooks:
          type: integer
          description: Webhook endpoints it created (only the mails CLI, and keys it made, can create any). Revoking the app leaves them in place.
        signing_secrets_known:
          type: integer
          description: 'Other webhook endpoints still signing with a secret it was shown. Revoking the app leaves those secrets in use: rotate them (`POST /v1/webhooks/{id}/rotate-secret`).'
      required:
      - id
      - client_id
      - client_name
      - first_party
      - scope
      - scopes
      - created_at
      - approved_at
      - last_used_at
      - created_api_keys
      - created_webhooks
      - signing_secrets_known
    FieldError:
      type: object
      properties:
        code:
          type: string
          enum:
          - invalid_field
          - missing_field
          - missing_param
          - invalid_param_value
          - method_not_allowed
          - invalid_idempotency_key
          - duplicate_resource
          - slug_invalid
          - slug_reserved
          - slug_taken
          - agent_name_conflict
          - body_too_large
          - missing_authorization
          - missing_api_key
          - invalid_api_key
          - expired_api_key
          - revoked_api_key
          - invalid_access_token
          - email_not_verified
          - insufficient_scope
          - connected_app_not_allowed
          - sso_required
          - workspace_mismatch
          - agent_not_owned
          - rate_limit_exceeded
          - daily_limit_exceeded
          - hourly_limit_exceeded
          - monthly_limit_exceeded
          - agent_not_found
          - agent_paused
          - agent_archived
          - resource_not_found
          - recipient_suppressed
          - recipient_not_allowed
          - domain_not_verified
          - ses_role_not_assumable
          - ses_role_external_id_not_required
          - ses_permission_denied
          - ses_setup_failed
          - ses_message_rejected
          - ses_events_not_confirmed
          - payment_required
          - payment_method_failed
          - subscription_canceled
          - billing_state_inconsistent
          - free_tier_exceeded
          - plan_limit_exceeded
          - cold_email_prohibited
          - agent_suspended
          - new_workspace_fanout_exceeded
          - resource_cap_exceeded
          - workspace_cap_exceeded
          - feature_not_enabled
          - test_mode_required
          - live_mode_required
          - unknown_test_address
          - workspace_not_approved
          - spam_pattern_detected
          - classifier_rejected
          - high_injection_score
          - complaint_threshold_exceeded
          - bounce_threshold_exceeded
          - internal_error
          - internal_server_error
          - upstream_error
          - upstream_unavailable
          - service_unavailable
          description: Stable machine-readable code for this field.
        message:
          type: string
          description: Human-readable explanation for this field.
        param:
          type:
          - string
          - 'null'
          description: The offending request field (dot-path), or null.
      required:
      - code
      - message
      - param
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: mk_live_<32 hex>, mk_test_<32 hex>, or moa_<48 hex>
      description: 'A Mails.ai API key, or an OAuth access token (`moa_…`, valid for 1 hour) from signing in through an app such as the CLI, Claude or ChatGPT. OAuth discovery: https://api.mails.ai/.well-known/oauth-authorization-server and https://api.mails.ai/.well-known/oauth-protected-resource. An access token''s OAuth scope maps onto these API scopes: `full_access` = `send` + `read` + `manage` (not `draft`: the draft routes take those three too), `send` = `send` + `read`, `read` = `read`. Scopes (`send`/`read`/`manage`/`draft`) are noted per operation via `x-required-scope`; when more than one scope opens an operation (the draft routes also take `draft`), `x-accepted-scopes` lists them all. `inbox` appears there too, on the triage routes (changing and trashing threads, labels, cancelling a scheduled send): only a signed-in dashboard session holds it (a workspace Member''s), never an API key or OAuth token, and `x-session-scopes` names it.'