Developer Docs

EventStrand API

EventStrand exposes a REST API for reading and writing strand and event data. Use it to build agents, integrations, and tooling on top of the open .rcal format.

All endpoints return JSON. Authentication uses API keys issued from your account settings. The API is the same interface the EventStrand web app uses — there are no private endpoints.

ℹ️ Public strand and QR endpoints require no authentication. All user-specific endpoints require an API key or session token.

Base URL

All API requests are made to:

Base https://api.eventstrand.com

Endpoints are prefixed with /api/. For example, the upcoming events endpoint is at https://api.eventstrand.com/api/dashboard/upcoming.

Rate Limits

Auth endpoints are limited to 30 requests per 15-minute window. All other /api/ endpoints are limited to 200 requests per minute per IP. Limits apply across API key and session token usage equally.

Responses include standard rate limit headers. When exceeded, the API returns 429 Too Many Requests.

Authentication — API Keys

API keys allow agents and external tools to authenticate without a user session. Generate keys from your EventStrand account under Account → API Keys.

⚠️ Keys are shown once at generation and cannot be retrieved again. Store them immediately in a secrets manager or environment variable.

All keys are prefixed with esk_ and stored as SHA-256 hashes server-side. Revoking a key is immediate — any tool using it loses access instantly.

Scopes

Each key carries one or more scopes that determine what it can access. Request only the scopes your use case requires.

portal:read Read the authenticated user's upcoming event feed and dashboard data.
strand:read Read strand details, event lists, and metadata for strands the user owns or subscribes to.
subscriptions:read List the strands and braids the user is subscribed to, including workspace breakdown.
strand:write Create and update strands owned by the authenticated user.
events:create Add new events (recurring, one-off, or date list) to strands the user owns.
events:update Modify existing events — update schedules, add exceptions, cancel occurrences.

Using a Key

Pass the key as a Bearer token in the Authorization header on every request:

HTTP
Authorization: Bearer esk_your_key_here
JavaScript
// Example: fetch upcoming events with an API key
const res = await fetch('https://api.eventstrand.com/api/dashboard/upcoming?days=7', {
  headers: {
    'Authorization': `Bearer ${process.env.EVENTSTRAND_API_KEY}`
  }
});
const data = await res.json();
// data.events — array of upcoming event objects

REST API

Upcoming Events

GET /api/dashboard/upcoming
Returns upcoming events from all strands the authenticated user subscribes to, within a given time window. Results are sorted by date ascending and capped at 100 events.

Required scope: portal:read

ParameterTypeRequiredDescription
daysintegeroptionalDays ahead to include. Min 1, max 365. Default 60.
workspaceIdstringoptionalFilter to a specific workspace. Omit for all workspaces.
Response
{
  "events": [
    {
      "title":       "Friday Jazz Night",
      "date":        "2026-05-02",
      "time":        "21:00",
      "venue":       "The Rusty Nail",
      "address":     "123 W Division St, Chicago",
      "strandId":    "664f...",
      "strandTitle": "The Rusty Nail",
      "publisher":   "rusty-nail",
      "color":       "#6C8FFF",
      "vibes":       ["mellow", "social"]
    }
  ]
}

Subscriptions

GET /api/user/subscriptions
Returns all strands and braids the authenticated user is subscribed to.

Required scope: subscriptions:read

ParameterTypeRequiredDescription
workspaceIdstringoptionalFilter results to a specific workspace.
Response
{
  "strands": [
    {
      "_id":             "664f...",
      "title":           "The Rusty Nail",
      "venue":           "The Rusty Nail",
      "publisherHandle": "rusty-nail",
      "color":           "#6C8FFF"
    }
  ],
  "braids": []
}

My Strands

GET /api/strands/mine
Returns all strands owned and published by the authenticated user.

Required scope: strand:read

Public Strand

GET /api/public/strand/:handle/:strandId
Returns public strand data by publisher handle and strand ID. No authentication required. Returns 403 for protected strands — pass a passcode query param if you have one. If :handle is an old handle the publisher has since changed, this returns {"redirect": "newhandle"} instead of strand data — re-request with the returned handle.
ParameterTypeRequiredDescription
handlestringrequiredPublisher handle (e.g. rusty-nail).
strandIdstringrequiredThe strand's ID.
passcodestringoptionalRequired for protected strands.
Response 200
{
  "strand":          { "..." },
  "publisherHandle": "rusty-nail",
  "subscribed":      false
}
Response 200 — old handle
{
  "redirect": "new-handle"
}
⚠️ Handles can change. Requests using one of a publisher's 10 most recent previous handles get the redirect response above instead of a 404 — your client needs to follow it and retry with the new handle. Beyond those 10, an old handle 404s as Publisher not found. Prefer looking up and storing the strand ID for anything you keep long-term; handles still need to be current to resolve it.

Generate API Key

POST /api/apikeys
Generates a new API key. The raw key value is returned once in this response and cannot be retrieved again. Requires an active user session (JWT) — not an API key.
Request body
{
  "label":  "My Agent",
  "scopes": ["portal:read", "strand:read"]
}
Response 201
{
  "id":        "664f...",
  "label":     "My Agent",
  "prefix":    "esk_a1b2c3d4",
  "scopes":    ["portal:read", "strand:read"],
  "createdAt": "2026-04-26T12:00:00.000Z",
  "key":       "esk_a1b2c3d4..."  // shown once — store immediately
}

List API Keys

GET /api/apikeys
Returns all active (non-revoked) keys for the authenticated user. Raw key values are never returned — only prefix, label, scopes, and last-used timestamp.

Revoke API Key

DELETE /api/apikeys/:id
Immediately revokes an API key. Any agent or tool using it loses access instantly. This action cannot be undone.

MCP Server Coming Soon

EventStrand will expose a Model Context Protocol server, allowing any MCP-compatible agent or AI tool to connect to your EventStrand data natively — without custom integration code.

Once available, you'll be able to add EventStrand as a connector in Claude, Claude Code, Cursor, or any MCP-compatible client by pointing it at the server URL.

🔗 The MCP server authenticates using the same API keys described above. Generate a key with the scopes you need, and provide it as the Bearer token when connecting.

Planned Tools

The MCP server will expose the following tools to agents:

es_get_upcoming_events Fetch upcoming events from the user's portal for a given time window and optional vibe filter.
es_get_subscriptions List all strands and braids the user is subscribed to, with workspace breakdown.
es_get_strand Fetch full details for a strand — events, schedule, venue, vibes — by ID or public slug.
es_search_strands Search public strands by location, vibe, or keyword.
es_get_my_strands List strands owned by the authenticated user.
es_create_event Add a new event (recurring, one-off, or date list) to a strand you own.
es_update_event Modify an existing event's schedule, title, or vibes.
es_cancel_event Add an exception to cancel a specific occurrence or date range.

Blue = attendee read · Purple = venue write

OAuth 2.0

EventStrand implements OAuth 2.0 with the Authorization Code flow. This is the authentication mechanism used when connecting EventStrand to Claude.ai or any MCP-compatible client that needs to act on behalf of a user.

If you are building an agent integration using API keys directly, you do not need OAuth — see API Keys above. OAuth is required for the Claude.ai connector and any third-party app that needs delegated user authorization.

ℹ️ The OAuth callback URLs for Claude.ai are https://claude.ai/api/mcp/auth_callback and https://claude.com/api/mcp/auth_callback. Both must be registered when setting up a client.

Authorization Endpoint

GET /api/oauth/authorize
Redirects the user to the EventStrand consent screen. After the user approves, EventStrand redirects to your redirect_uri with a short-lived authorization code.
ParameterTypeRequiredDescription
client_idstringrequiredYour OAuth application client ID.
redirect_uristringrequiredMust exactly match a registered callback URL for your client.
response_typestringrequiredMust be code.
scopestringrequiredSpace-separated list of scopes. See Scopes below.
statestringrecommendedRandom value to protect against CSRF. Returned unchanged in the callback.
Example
GET https://api.eventstrand.com/api/oauth/authorize
  ?client_id=your_client_id
  &redirect_uri=https://claude.ai/api/mcp/auth_callback
  &response_type=code
  &scope=portal:read+strand:read
  &state=random_csrf_token

Token Endpoint

POST /api/oauth/token
Exchanges an authorization code for an access token and refresh token. Also used to refresh an expired access token.

Exchange authorization code:

Request body
{
  "grant_type":    "authorization_code",
  "code":          "the_auth_code",
  "redirect_uri":  "https://claude.ai/api/mcp/auth_callback",
  "client_id":     "your_client_id",
  "client_secret": "your_client_secret"
}
Response
{
  "access_token":  "esk_...",
  "token_type":    "Bearer",
  "expires_in":    3600,
  "refresh_token": "ref_...",
  "scope":         "portal:read strand:read"
}

Scopes

Request only the scopes your application requires. The user sees each scope on the consent screen before approving.

portal:read Read the user's upcoming event feed and dashboard data.
strand:read Read strand details and event lists for strands the user owns or subscribes to.
subscriptions:read List strands and braids the user is subscribed to.
strand:write Create and update strands owned by the user.
events:create Add new events to strands the user owns.
events:update Modify existing events — update schedules, add exceptions, cancel occurrences.

Refreshing Tokens

Access tokens expire after 1 hour. Use the refresh token to obtain a new one without requiring the user to re-authorize.

Request body
{
  "grant_type":    "refresh_token",
  "refresh_token": "ref_...",
  "client_id":     "your_client_id",
  "client_secret": "your_client_secret"
}
⚠️ Refresh tokens expire after 30 days of inactivity. If expired, the user must re-authorize through the consent flow.
EventStrand
The open platform for recurring real-world life.
Home .rcal Spec Privacy Terms