Deadsimple Email Identity Provider API

The Identity Provider API from Deadsimple Email — 7 operation(s) for identity provider.

Operations 9

GET /.well-known/openid-configuration OpenID Connect discovery #
GET /.well-known/jwks.json JSON Web Key Set #
GET /authorize Authorization endpoint (authorization-code + PKCE) #
POST /authorize Authorization endpoint (form or JSON body) #
POST /token Token endpoint #
GET /userinfo UserInfo endpoint #
POST /userinfo UserInfo endpoint (POST) #
POST /register Dynamic client registration (RFC 7591) #
POST /revoke Token revocation (RFC 7009) #

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/deadsimple-email:deadsimple-email-identity-provider-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

deadsimple-email-identity-provider-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Dead Simple Email Identity Provider API
  version: 0.1.0
  description: 'Email API for AI agents. Create inboxes, send and receive email,

    manage threads, and get real-time webhook notifications.


    ## Authentication

    All authenticated endpoints use Bearer token auth:

    ```

    Authorization: Bearer dse_your_api_key

    ```


    ## Agent self-signup

    Autonomous agents can provision an account, a key, and a live inbox in one

    unauthenticated call — no human, no verification email:

    ```

    POST /v1/auth/agent-signup

    ```

    That returns a `trial` account (1 inbox, 10 sends/hour, 25 sends/day) with a

    scoped key. `POST /v1/auth/claim` attaches a verified email and moves it to

    the Free plan, keeping the same key, inboxes and history.


    ## Sign in with Dead Simple (OpenID Connect)

    `https://id.deadsimple.email` is an OpenID Connect provider where the

    identity is an agent''s inbox: `sub` is the `inbox_id`, `email` is the inbox

    address. Discovery at `/.well-known/openid-configuration`, JWKS at

    `/.well-known/jwks.json`, ES256 id_tokens, authorization-code + PKCE S256,

    RFC 7591 registration at `/register`. Open clients (client_id = the app''s

    https origin) need no registration. An agent authenticates at `/authorize`

    with `Authorization: Bearer dse_...` plus `inbox_id=`, or with an ES256

    assertion signed by a key enrolled via `/v1/inboxes/{inbox_id}/identity/keys`;

    the one-call helper `POST /v1/inboxes/{inbox_id}/identity/sign-in` wraps

    this for SDKs and the MCP server. Endpoints on the `id.deadsimple.email`

    host are tagged **Identity Provider** below and carry their own `servers`

    entry.


    ## Pay per request (no API key)

    Twelve priced routes can be called with no credentials at all. They answer

    `402 Payment Required` carrying an x402 v2 `PAYMENT-REQUIRED` header (USDC

    on Base, Polygon or Solana, `exact` scheme, PayAI facilitator; the same

    JSON is repeated in the body for v1 clients) and, on `POST /v1/inboxes`,

    a `WWW-Authenticate: Payment` challenge for Stripe''s Machine Payments

    Protocol. Retry with `PAYMENT-SIGNATURE` (x402 v2), `X-PAYMENT` (v1) or

    `Authorization: Payment ` (MPP). x402 payments are verified

    before the work and settled after it; MPP charges first and refunds if

    the work fails. Prices: inbox $1.00, send/reply/reply-all/forward $0.01,

    reads $0.005. The paying wallet keys a `payg` account, and the first paid

    inbox returns `credentials` (an API key) in the response so later reads

    and sends within the plan are free. See the reusable `402` response and

    https://deadsimple.email/pay-per-request.html.


    ## Idempotency

    POST endpoints accept an `Idempotency-Key` header. Same key within 24h

    returns the cached response without re-executing.


    ## Rate Limits

    Every response includes rate limit headers:

    - `X-RateLimit-Limit` — requests per window

    - `X-RateLimit-Remaining` — remaining requests

    - `X-RateLimit-Reset` — window reset timestamp


    On `429`, a `Retry-After` header gives the seconds to wait. Per-account

    limits scale with plan (`rate_limit_rpm`); public/unauthenticated endpoints

    are additionally limited per IP.


    ## Webhook Reliability

    Every event is a durable delivery: the first attempt runs immediately and

    failures are retried on an exponential-backoff schedule (configurable per

    webhook via `/v1/webhooks/{id}/config`; default `[1, 30, 300, 1800, 7200,

    86400]` seconds). Deliveries are HMAC-SHA256 signed on **all plans**. The

    per-webhook delivery log (`/v1/webhooks/{id}/deliveries`) records every

    attempt with status and error, and any delivery can be replayed. Endpoints

    that fail repeatedly are auto-disabled.


    ## WebSocket Streaming

    Connect to `wss://ws.deadsimple.email?api_key=dse_...` for real-time events.


    After connecting, subscribe to specific inboxes/events:

    ```json

    {"action": "subscribe", "inbox_ids": ["..."], "events": ["message.received"]}

    ```


    Events are pushed as JSON with the same payload as webhooks. Send `{"action": "ping"}`

    to keep the connection alive (10-minute idle timeout).'
  contact:
    url: https://deadsimple.email
  license:
    name: MIT
servers:
- url: https://api.deadsimple.email
  description: Production
security:
- BearerAuth: []
tags:
- name: Identity Provider
paths:
  /.well-known/openid-configuration:
    servers:
    - url: https://id.deadsimple.email
      description: Identity provider
    get:
      summary: OpenID Connect discovery
      description: Issuer, endpoint URLs, supported scopes and claims, ES256, S256 PKCE, and the non-standard `agent_authentication_methods_supported` (api_key, assertion, browser). Cached one hour. Note that the same path on api.deadsimple.email returns the MCP OAuth server's metadata instead.
      security: []
      tags:
      - Identity Provider
      responses:
        '200':
          description: The provider metadata document.
      operationId: getWellKnownOpenidConfiguration
      x-operation-id-source: derived
  /.well-known/jwks.json:
    servers:
    - url: https://id.deadsimple.email
      description: Identity provider
    get:
      summary: JSON Web Key Set
      description: The ES256 (P-256) public key that signs id_tokens. Cached one hour.
      security: []
      tags:
      - Identity Provider
      responses:
        '200':
          description: '{ keys: [ { kty: EC, crv: P-256, alg: ES256, use: sig, kid, x, y } ] }'
      operationId: getWellKnownJwksJson
      x-operation-id-source: derived
  /authorize:
    servers:
    - url: https://id.deadsimple.email
      description: Identity provider
    get:
      summary: Authorization endpoint (authorization-code + PKCE)
      description: 'Three ways an agent authenticates, checked in this order.

        1. **API key**: send `Authorization: Bearer dse_...` plus `inbox_id=` (or `login_hint=`; both may be omitted when the account has exactly one inbox). The code is minted immediately.

        2. **Signed assertion**: `assertion=` an ES256 JWS over `{iss: inbox_id, sub: inbox_id, aud: "https://id.deadsimple.email", iat, exp (at most 10 minutes after iat), jti}` with a `kid` header naming a key enrolled on the inbox. Optional `client_id`, `redirect_uri`, `code_challenge` and `nonce` claims must match the request. Each `jti` is accepted once.

        3. **Browser**: no credential at all redirects to `https://app.deadsimple.email/id/consent?request_id=...`, where the account owner picks an inbox and approves.

        Success is a 302 to `redirect_uri` with `code` and `state`, or, when the request sends `Accept: application/json`, a 200 JSON body `{redirect_to, code, state}`. A JSON client with no credential gets `401 login_required` rather than a redirect. An unresolvable client_id or a redirect_uri that is not same-origin (open client) or not registered gets a 400 with no redirect.'
      security: []
      tags:
      - Identity Provider
      parameters:
      - name: client_id
        in: query
        required: true
        schema:
          type: string
        description: A registered dse_idc_ id, or (open client) an https URL sharing its origin with redirect_uri.
      - name: redirect_uri
        in: query
        required: true
        schema:
          type: string
      - name: response_type
        in: query
        required: true
        schema:
          type: string
          enum:
          - code
      - name: scope
        in: query
        required: false
        schema:
          type: string
          default: openid
        description: openid required. Open clients may add email and profile; registered clients may also add owner_email, org, offline_access.
      - name: code_challenge
        in: query
        required: true
        schema:
          type: string
      - name: code_challenge_method
        in: query
        required: false
        schema:
          type: string
          enum:
          - S256
      - name: state
        in: query
        required: false
        schema:
          type: string
      - name: nonce
        in: query
        required: false
        schema:
          type: string
      - name: inbox_id
        in: query
        required: false
        schema:
          type: string
        description: Which inbox signs in (API-key path).
      - name: login_hint
        in: query
        required: false
        schema:
          type: string
        description: The inbox email address, as an alternative to inbox_id.
      - name: assertion
        in: query
        required: false
        schema:
          type: string
        description: 'ES256 JWS signed by an enrolled key (assertion path). Also accepted as `Authorization: Bearer <jws>`.'
      responses:
        '302':
          description: Redirect to redirect_uri with code and state (or error and state), or to the consent page.
        '200':
          description: With Accept application/json, the redirect as data.
          content:
            application/json:
              schema:
                type: object
                properties:
                  redirect_to:
                    type: string
                  code:
                    type: string
                  state:
                    type: string
        '400':
          description: invalid_client (never redirected), or a validation error from the API-key path.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: login_required (JSON client, no credential), or an assertion that failed verification.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '404':
          description: The API key does not own the requested inbox.
      operationId: getAuthorize
      x-operation-id-source: derived
    post:
      summary: Authorization endpoint (form or JSON body)
      description: Identical to GET; parameters may be sent as application/x-www-form-urlencoded or JSON, which keeps a signed assertion out of the URL. Query and body parameters are merged.
      security: []
      tags:
      - Identity Provider
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                client_id:
                  type: string
                redirect_uri:
                  type: string
                response_type:
                  type: string
                  enum:
                  - code
                scope:
                  type: string
                code_challenge:
                  type: string
                code_challenge_method:
                  type: string
                  enum:
                  - S256
                state:
                  type: string
                nonce:
                  type: string
                inbox_id:
                  type: string
                login_hint:
                  type: string
                assertion:
                  type: string
          application/json:
            schema:
              type: object
      responses:
        '302':
          description: As for GET.
        '200':
          description: As for GET with Accept application/json.
        '400':
          description: As for GET.
        '401':
          description: As for GET.
      operationId: postAuthorize
      x-operation-id-source: derived
  /token:
    servers:
    - url: https://id.deadsimple.email
      description: Identity provider
    post:
      summary: Token endpoint
      description: 'Exchanges an authorization code (with `code_verifier`) or a refresh token. Codes are single use and live 5 minutes; a failed exchange burns the code. Client authentication: `client_secret_basic`, `client_secret_post`, or `none` (open clients and registered public clients). Registered clients, and any grant that included `offline_access`, receive a refresh token (30 days, rotated on every use; a reused refresh token is rejected). Accepts application/x-www-form-urlencoded or JSON.'
      security: []
      tags:
      - Identity Provider
      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
                code:
                  type: string
                code_verifier:
                  type: string
                redirect_uri:
                  type: string
                  description: If sent, must match the authorization request.
                refresh_token:
                  type: string
                client_id:
                  type: string
                client_secret:
                  type: string
                  description: For client_secret_post. Basic auth is also accepted.
      responses:
        '200':
          description: Tokens issued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityTokenResponse'
        '400':
          description: invalid_request, invalid_grant (bad or reused code, PKCE mismatch, redirect_uri mismatch), or unsupported_grant_type.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: invalid_client. Registered clients that require a secret get a WWW-Authenticate Basic challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
      operationId: postToken
      x-operation-id-source: derived
  /userinfo:
    servers:
    - url: https://id.deadsimple.email
      description: Identity provider
    get:
      summary: UserInfo endpoint
      description: 'Send `Authorization: Bearer dse_idt_...`. Returns the claims for the granted scopes. This is the only place `owner_email` is served, and only to registered clients that were granted the owner_email scope.'
      security: []
      tags:
      - Identity Provider
      responses:
        '200':
          description: Claims for the agent inbox.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityClaims'
        '401':
          description: invalid_token, with a WWW-Authenticate Bearer challenge.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
      operationId: getUserinfo
      x-operation-id-source: derived
    post:
      summary: UserInfo endpoint (POST)
      description: Same as GET.
      security: []
      tags:
      - Identity Provider
      responses:
        '200':
          description: Claims for the agent inbox.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityClaims'
        '401':
          description: invalid_token.
      operationId: postUserinfo
      x-operation-id-source: derived
  /register:
    servers:
    - url: https://id.deadsimple.email
      description: Identity provider
    post:
      summary: Dynamic client registration (RFC 7591)
      description: 'Registers a relying party that may request `owner_email`, `org` and `offline_access`. Unlike the MCP OAuth server''s /oauth/register, this endpoint requires a Dead Simple API key (`Authorization: Bearer dse_...`) with the api_keys.create permission: trial keys from agent self-signup and MCP OAuth tokens are refused, so an anonymous party cannot mint a client that sees owner data. The client_secret is shown once. The same registration with the standard API envelope is available at POST /v1/identity/clients.'
      tags:
      - Identity Provider
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IdentityClientRegistration'
      responses:
        '201':
          description: client_id (dse_idc_) and, unless token_endpoint_auth_method is none, client_secret (dse_ics_).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentityClientCreated'
        '400':
          description: invalid_client_metadata.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
        '401':
          description: No API key.
        '403':
          description: Key lacks api_keys.create (trial keys), or an MCP OAuth token was used.
      operationId: postRegister
      x-operation-id-source: derived
  /revoke:
    servers:
    - url: https://id.deadsimple.email
      description: Identity provider
    post:
      summary: Token revocation (RFC 7009)
      description: Revokes a dse_idt_ access token or a dse_idr_ refresh token. Always returns 200.
      security: []
      tags:
      - Identity Provider
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - token
              properties:
                token:
                  type: string
      responses:
        '200':
          description: Token revoked (or was already unknown).
      operationId: postRevoke
      x-operation-id-source: derived
components:
  schemas:
    OAuthError:
      type: object
      description: RFC 6749 error shape returned by the identity provider endpoints.
      properties:
        error:
          type: string
          example: invalid_grant
        error_description:
          type: string
    IdentityClient:
      type: object
      properties:
        client_id:
          type: string
          example: dse_idc_3f9a1c77b2e04d5a6b8c
        client_name:
          type: string
        client_uri:
          type: string
        logo_uri:
          type: string
        redirect_uris:
          type: array
          items:
            type: string
        token_endpoint_auth_method:
          type: string
          enum:
          - none
          - client_secret_basic
          - client_secret_post
        scope:
          type: string
          description: Space-separated scopes the client may request.
        grant_types:
          type: array
          items:
            type: string
        response_types:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time
    IdentityClientCreated:
      allOf:
      - $ref: '#/components/schemas/IdentityClient'
      - type: object
        properties:
          client_secret:
            type: string
            description: dse_ics_ secret. Shown once; stored hashed. Absent when token_endpoint_auth_method is none.
          client_secret_expires_at:
            type: integer
            description: 0 means the secret does not expire.
          client_id_issued_at:
            type: integer
          warning:
            type: string
          issuer:
            type: string
            example: https://id.deadsimple.email
          discovery_url:
            type: string
            example: https://id.deadsimple.email/.well-known/openid-configuration
    IdentityClientRegistration:
      type: object
      required:
      - redirect_uris
      properties:
        client_name:
          type: string
        client_uri:
          type: string
        logo_uri:
          type: string
        policy_uri:
          type: string
        tos_uri:
          type: string
        redirect_uris:
          type: array
          maxItems: 20
          items:
            type: string
          description: https, loopback http, or app-scheme URLs.
        scope:
          type: string
          default: openid email profile owner_email org
          description: Any of openid, email, profile, owner_email, org, offline_access. openid is always included.
        token_endpoint_auth_method:
          type: string
          enum:
          - none
          - client_secret_basic
          - client_secret_post
          default: client_secret_basic
    IdentityClaims:
      type: object
      description: Claims in the id_token and from /userinfo. Which ones appear depends on the granted scopes and on whether the client is registered.
      properties:
        sub:
          type: string
          description: The inbox_id. Stable for the life of the inbox; use it as the user id.
        inbox_id:
          type: string
        agent:
          type: boolean
          description: Always true.
        owner_verified:
          type: boolean
          description: True when a verified human owns the account behind the inbox; false for unclaimed agent-signup (trial) accounts.
        email:
          type: string
          description: The inbox address (scope email).
        email_verified:
          type: boolean
          description: Always true (scope email).
        name:
          type: string
          description: Inbox display name, falling back to the local part (scope profile).
        preferred_username:
          type: string
          description: Local part of the inbox address (scope profile).
        org:
          type: string
          description: Account id that owns the inbox (scope org, registered clients).
        plan:
          type: string
          description: Account plan (scope org, registered clients).
          enum:
          - trial
          - free
          - hobby
          - pro
          - scale
          - enterprise
        owner_email:
          type: string
          nullable: true
          description: Verified owner email (scope owner_email, registered clients). Returned from /userinfo only, never inside the id_token. Null when owner_verified is false.
    IdentityTokenResponse:
      type: object
      properties:
        access_token:
          type: string
          description: Opaque dse_idt_ token for /userinfo. Lives 1 hour.
        token_type:
          type: string
          example: Bearer
        expires_in:
          type: integer
          example: 3600
        id_token:
          type: string
          description: ES256 JWT, verify against /.well-known/jwks.json. Lives 1 hour.
        scope:
          type: string
        refresh_token:
          type: string
          description: dse_idr_ token, 30 days, rotated on every use. Only for registered clients or when offline_access was granted.
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: API key (dse_...), session token (JWT), or OAuth 2.1 access token (dse_oat_..., obtained via the authorization-code + PKCE flow below)
    OAuth2:
      type: oauth2
      description: OAuth 2.1 for MCP clients. Public clients only (PKCE S256 required, no client secret). Supports RFC 7591 dynamic client registration at /oauth/register and discovery via /.well-known/oauth-authorization-server. Access tokens carry inbox/message permissions only (no billing, team, or API-key administration).
      flows:
        authorizationCode:
          authorizationUrl: https://api.deadsimple.email/oauth/authorize
          tokenUrl: https://api.deadsimple.email/oauth/token
          refreshUrl: https://api.deadsimple.email/oauth/token
          scopes:
            email: Send, receive, and manage email inboxes
    OpenIDConnect:
      type: openIdConnect
      openIdConnectUrl: https://id.deadsimple.email/.well-known/openid-configuration
      description: 'Sign in with Dead Simple. Relying parties use this to authenticate an agent by its inbox; it is not used to call the Dead Simple API itself. Scopes: openid, email, profile (open clients); owner_email, org, offline_access (registered clients only).'