Deadsimple Email Calendar API

The Calendar API from Deadsimple Email — 8 operation(s) for calendar.

Operations 11

GET /v1/calendar/events List calendar events across the account #
GET /v1/inboxes/{inbox_id}/calendar/events List an inbox's calendar events #
POST /v1/inboxes/{inbox_id}/calendar/events Create a calendar event #
GET /v1/inboxes/{inbox_id}/calendar/events/{event_id} Get a calendar event #
PATCH /v1/inboxes/{inbox_id}/calendar/events/{event_id} Update a calendar event #
DELETE /v1/inboxes/{inbox_id}/calendar/events/{event_id} Delete a calendar event #
POST /v1/inboxes/{inbox_id}/calendar/events/{event_id}/invite Send the event to its attendees as an invitation #
POST /v1/inboxes/{inbox_id}/calendar/events/{event_id}/rsvp Answer an invitation #
GET /v1/inboxes/{inbox_id}/calendar/feed Get the inbox's calendar subscription URL #
POST /v1/inboxes/{inbox_id}/calendar/feed/rotate Rotate the calendar feed token #
GET /v1/inboxes/{inbox_id}/calendar.ics The calendar feed (iCalendar) #

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-calendar-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-calendar-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Dead Simple Email Calendar 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: Calendar
paths:
  /v1/calendar/events:
    get:
      summary: List calendar events across the account
      description: 'Every event on every inbox the key can see, sorted by start_at. Same filters as the per-inbox listing plus an optional comma-separated `inbox_id` list. Scoped to the key''s workspace when it has one. SDK: `calendar.list_all` / `calendar.listAll`.'
      tags:
      - Calendar
      parameters:
      - name: from
        in: query
        schema:
          type: string
        description: ISO 8601 date or datetime. Events starting at or after this.
      - name: to
        in: query
        schema:
          type: string
        description: ISO 8601 date or datetime. Events starting at or before this (a bare date means the end of that day).
      - name: status
        in: query
        schema:
          type: string
          enum:
          - confirmed
          - tentative
          - cancelled
      - name: inbox_id
        in: query
        schema:
          type: string
        description: One or more inbox ids, comma-separated.
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
          maximum: 100
      - name: cursor
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Events.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarEventList'
      operationId: getV1CalendarEvents
      x-operation-id-source: derived
  /v1/inboxes/{inbox_id}/calendar/events:
    get:
      summary: List an inbox's calendar events
      description: 'Sorted by start_at. `from`/`to` bound the start time; `status` filters. Cursor pagination like the rest of the API. SDK: `calendar.list`.'
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      - name: from
        in: query
        schema:
          type: string
      - name: to
        in: query
        schema:
          type: string
      - name: status
        in: query
        schema:
          type: string
          enum:
          - confirmed
          - tentative
          - cancelled
      - name: limit
        in: query
        schema:
          type: integer
          default: 50
          maximum: 100
      - name: cursor
        in: query
        schema:
          type: string
      responses:
        '200':
          description: Events.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarEventList'
      operationId: getV1InboxesByInboxIdCalendarEvents
      x-operation-id-source: derived
    post:
      summary: Create a calendar event
      description: 'Puts an event on the inbox''s calendar. Nothing is sent: call `invite` to send it to the attendees as a real invitation. The inbox is the organizer unless `organizer` says otherwise. Emits `calendar.event.created`. SDK: `calendar.create`.'
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
              - $ref: '#/components/schemas/CalendarEventInput'
              - required:
                - title
                - start
      responses:
        '201':
          description: The event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarEvent'
        '400':
          description: Missing title or start, a naive time with no timezone, an unknown zone, end before start, or a duplicate ical_uid on this inbox.
      operationId: postV1InboxesByInboxIdCalendarEvents
      x-operation-id-source: derived
  /v1/inboxes/{inbox_id}/calendar/events/{event_id}:
    get:
      summary: Get a calendar event
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      - name: event_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: The event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarEvent'
        '404':
          description: No such event on this inbox.
      operationId: getV1InboxesByInboxIdCalendarEventsByEventId
      x-operation-id-source: derived
    patch:
      summary: Update a calendar event
      description: 'Partial update; fields left out keep their values, and a moved `start` keeps the event''s length unless `end` or `duration_minutes` is given. Once the event has been sent as an invitation, every update raises `sequence`, so the next `invite` goes out as a revision that calendar clients apply to the existing entry. Setting `status` to cancelled emits `calendar.event.cancelled`; anything else emits `calendar.event.updated`. SDK: `calendar.update`.'
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      - name: event_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CalendarEventInput'
      responses:
        '200':
          description: The updated event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarEvent'
      operationId: patchV1InboxesByInboxIdCalendarEventsByEventId
      x-operation-id-source: derived
    delete:
      summary: Delete a calendar event
      description: 'Removes the row. Attendees are not told: to withdraw an invitation you sent, PATCH `status` to cancelled and call `invite` (which then sends a CANCEL) before deleting. Emits `calendar.event.cancelled` unless the event was already cancelled. SDK: `calendar.delete`.'
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      - name: event_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '204':
          description: Deleted.
      operationId: deleteV1InboxesByInboxIdCalendarEventsByEventId
      x-operation-id-source: derived
  /v1/inboxes/{inbox_id}/calendar/events/{event_id}/invite:
    post:
      summary: Send the event to its attendees as an invitation
      description: 'Sends a real iCalendar invitation (METHOD:REQUEST) from the inbox to every attendee, through the normal send path (quota, guardrails and suppression apply; needs `messages.send`). The recipient sees Accept/Decline and the event lands on their calendar. The event keeps its `ical_uid`, so calling this again after a PATCH sends an update with a higher SEQUENCE that replaces the earlier entry rather than duplicating it. When the event''s status is cancelled the message goes out as METHOD:CANCEL instead. Marks the event `source: sent` and links the message. The inbox must be the organizer. SDK: `calendar.invite`.'
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      - name: event_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                subject:
                  type: string
                  description: 'Defaults to "Invitation: {title}", "Updated invitation: {title}" or "Cancelled: {title}".'
                text_body:
                  type: string
                  description: Covering text. Defaults to a line naming the event, when and where.
                html_body:
                  type: string
                attendees:
                  type: array
                  description: Replace the attendee list before sending.
                  items:
                    oneOf:
                    - type: string
                      format: email
                    - $ref: '#/components/schemas/CalendarAttendee'
                in_reply_to:
                  type: string
                  description: A message_id to thread the invitation under.
      responses:
        '201':
          description: Sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarInviteResult'
        '400':
          description: No attendees, or the inbox is not the event's organizer (answer with rsvp instead).
      operationId: postV1InboxesByInboxIdCalendarEventsByEventIdInvite
      x-operation-id-source: derived
  /v1/inboxes/{inbox_id}/calendar/events/{event_id}/rsvp:
    post:
      summary: Answer an invitation
      description: 'Sends an iCalendar REPLY to the event''s organizer with this inbox''s PARTSTAT (through the normal send path; needs `messages.send`) and records the answer on the event''s attendee entry. Only for events the inbox does not organize, which includes every invitation that arrived by email. Emits `calendar.event.updated`. SDK: `calendar.rsvp`.'
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      - name: event_id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - response
              properties:
                response:
                  type: string
                  enum:
                  - accepted
                  - declined
                  - tentative
                comment:
                  type: string
                  description: Sent as the body of the reply.
                subject:
                  type: string
                  description: 'Defaults to "Accepted: {title}" etc.'
      responses:
        '201':
          description: Replied.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarRsvpResult'
        '400':
          description: Invalid response, no organizer, or the inbox organizes this event itself.
      operationId: postV1InboxesByInboxIdCalendarEventsByEventIdRsvp
      x-operation-id-source: derived
  /v1/inboxes/{inbox_id}/calendar/feed:
    get:
      summary: Get the inbox's calendar subscription URL
      description: 'Returns the .ics feed URL (https and webcal) for this inbox, creating its feed token on first request. The same URL comes back on every call until it is rotated. Hand it to a human to add the agent''s calendar to Google Calendar, Apple Calendar or Outlook. The token is the only credential on the feed, so treat the URL as a secret. SDK: `calendar.feed`.'
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Feed URLs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarFeed'
      operationId: getV1InboxesByInboxIdCalendarFeed
      x-operation-id-source: derived
  /v1/inboxes/{inbox_id}/calendar/feed/rotate:
    post:
      summary: Rotate the calendar feed token
      description: 'Issues a new feed token and invalidates the old URL immediately. Anyone subscribed to the old URL stops receiving updates. SDK: `calendar.rotate_feed` / `calendar.rotateFeed`.'
      tags:
      - Calendar
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '201':
          description: New feed URLs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarFeed'
      operationId: postV1InboxesByInboxIdCalendarFeedRotate
      x-operation-id-source: derived
  /v1/inboxes/{inbox_id}/calendar.ics:
    get:
      summary: The calendar feed (iCalendar)
      description: A `text/calendar` VCALENDAR of every non-cancelled event on the inbox, for calendar apps to subscribe to. Authenticated by the `token` query parameter alone (no API key), rate limited per IP. Times are UTC instants; `X-WR-TIMEZONE` carries the zone most events were given in.
      tags:
      - Calendar
      security: []
      parameters:
      - name: inbox_id
        in: path
        required: true
        schema:
          type: string
      - name: token
        in: query
        required: true
        schema:
          type: string
        description: From GET /v1/inboxes/{inbox_id}/calendar/feed.
      responses:
        '200':
          description: The feed.
          content:
            text/calendar:
              schema:
                type: string
        '401':
          description: Missing or invalid token.
      operationId: getV1InboxesByInboxIdCalendarIcs
      x-operation-id-source: derived
components:
  schemas:
    CalendarInviteResult:
      allOf:
      - $ref: '#/components/schemas/CalendarEvent'
      - type: object
        properties:
          message_id:
            type: string
            description: The sent message.
          method:
            type: string
            enum:
            - REQUEST
            - CANCEL
            description: CANCEL when the event's status is cancelled; REQUEST otherwise.
          sent_to:
            type: array
            items:
              type: string
    CalendarRsvpResult:
      allOf:
      - $ref: '#/components/schemas/CalendarEvent'
      - type: object
        properties:
          message_id:
            type: string
            description: The REPLY message sent to the organizer.
          response:
            type: string
            enum:
            - accepted
            - declined
            - tentative
    CalendarFeed:
      type: object
      properties:
        inbox_id:
          type: string
        email:
          type: string
        feed_url:
          type: string
          description: https URL of the .ics feed, token included. Paste into Google Calendar ("From URL"), Apple Calendar or Outlook.
        webcal_url:
          type: string
          description: The same feed as a webcal:// link, which most calendar apps open as a subscription.
        token_created_at:
          type: string
        rotated:
          type: boolean
          description: True when this call issued a new token (the first request, or a rotate).
    CalendarOrganizer:
      type: object
      properties:
        email:
          type: string
          format: email
        name:
          type: string
    CalendarEvent:
      type: object
      properties:
        event_id:
          type: string
        inbox_id:
          type: string
        workspace_id:
          type: string
          nullable: true
        ical_uid:
          type: string
          description: Stable across updates. What an update, cancellation or reply is matched on, on both ends.
        title:
          type: string
        description:
          type: string
        location:
          type: string
        start_at:
          type: string
          description: ISO 8601 UTC, e.g. 2026-09-15T19:00:00Z
        end_at:
          type: string
          description: ISO 8601 UTC. Exclusive for all-day events.
        timezone:
          type: string
        all_day:
          type: boolean
        status:
          type: string
          enum:
          - confirmed
          - tentative
          - cancelled
        organizer:
          $ref: '#/components/schemas/CalendarOrganizer'
        attendees:
          type: array
          items:
            $ref: '#/components/schemas/CalendarAttendee'
        source:
          type: string
          enum:
          - api
          - sent
          - inbound
          description: api = created here; sent = created here and sent as an invitation; inbound = arrived by email.
        sequence:
          type: integer
        message_id:
          type: string
          nullable: true
          description: The invitation we sent, or the inbound message that last created, updated or cancelled the event.
        invited_at:
          type: string
          nullable: true
        created_at:
          type: string
        updated_at:
          type: string
    CalendarAttendee:
      type: object
      required:
      - email
      properties:
        email:
          type: string
          format: email
        name:
          type: string
        response:
          type: string
          enum:
          - needs-action
          - accepted
          - declined
          - tentative
          description: The attendee's answer as last seen (our own rsvp, or their REPLY).
        optional:
          type: boolean
          description: OPT-PARTICIPANT rather than REQ-PARTICIPANT on the invitation.
    CalendarEventInput:
      type: object
      description: 'Fields for creating or (all optional, on PATCH) updating an event. Times are ISO 8601 and need a zone: a trailing Z, an offset, or `timezone`. A bare date makes an all-day event. Stored and returned as UTC instants.'
      properties:
        title:
          type: string
          maxLength: 500
        start:
          type: string
          description: '"2026-09-15T15:00:00Z", "2026-09-15T15:00:00-04:00", "2026-09-15T15:00:00" plus timezone, or "2026-09-15".'
        end:
          type: string
          description: Omit to use duration_minutes (default 30). On PATCH, a moved start keeps the event's length.
        duration_minutes:
          type: integer
          minimum: 1
        timezone:
          type: string
          description: IANA zone for a local start/end with no offset, e.g. America/New_York. Also sets X-WR-TIMEZONE on the feed.
        all_day:
          type: boolean
        description:
          type: string
        location:
          type: string
        status:
          type: string
          enum:
          - confirmed
          - tentative
          - cancelled
          default: confirmed
        organizer:
          $ref: '#/components/schemas/CalendarOrganizer'
          description: Defaults to the inbox itself. Only events the inbox organizes can be sent with invite; only events it does not can be answered with rsvp.
        attendees:
          type: array
          maxItems: 100
          items:
            oneOf:
            - type: string
              format: email
            - $ref: '#/components/schemas/CalendarAttendee'
        ical_uid:
          type: string
          description: Create only. Supply your own iCalendar UID; otherwise one is generated as {hex}@deadsimple.email.
        sequence:
          type: integer
          description: iCalendar SEQUENCE. Managed for you; set it only to take over from a calendar system that already sent this event.
    CalendarEventList:
      type: object
      properties:
        events:
          type: array
          items:
            $ref: '#/components/schemas/CalendarEvent'
        count:
          type: integer
        cursor:
          type: string
          description: Present when there is another page. Pass it back as ?cursor=.
  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).'