API Reference

Base URL

All proxy requests follow this pattern:

https://www.perpetua.sh/api/proxy/{provider}/{path}

Where {provider} is the provider slug and {path} is the path you'd normally send to the provider's API.

Authentication

Include your API key as a Bearer token in the Authorization header:

Authorization: Bearer pk_your_key

Every request to the proxy must include this header. The API key identifies your account, and Perpetua injects the correct OAuth token for the upstream provider.

Supported providers

ProviderSlugUpstream base URLExample path
Oura Ringourahttps://api.ouraring.com/v2/usercollection/daily_activity
Google Calendargcalhttps://www.googleapis.com/calendar/v3/calendars/primary/events

More providers (Strava, Notion, and others) are coming soon.

Request forwarding

Perpetua forwards your request to the provider's API, including:

The upstream response is returned to you with its original status code and headers.

Multiple accounts

If you've connected more than one account for the same provider (e.g., two different Google Calendar accounts), use the account query parameter to choose which one:

curl -H "Authorization: Bearer pk_your_key" \
  "https://www.perpetua.sh/api/proxy/gcal/calendars/primary/events?account=work"

Without the account parameter, the default connection is used.

Error responses

StatusErrorDescription
401UnauthorizedMissing or invalid API key.
401TokenRefreshFailedThe provider's OAuth token could not be refreshed. Reconnect the provider from the dashboard.
404UnknownProviderThe provider slug is not recognized.
404NoConnectionNo connection exists for this provider (or account). Connect it from the dashboard.
429Too many requestsRate limit exceeded. Wait and retry.
502UpstreamErrorThe upstream provider API could not be reached.

Rate limits

API requests are rate-limited per IP address using a sliding window:

RouteLimit
Global (all API routes)100 requests / minute
Auth routes (/api/auth/connect/.../start)10 requests / minute
Key management (/api/keys)10 requests / minute
Billing routes (/api/stripe/*)20 requests / minute
MCP endpoint (/api/mcp)100 requests / minute (shared with global)

When rate-limited, the API returns a 429 status. Wait and retry after a few seconds.


MCP / Agent Integration

Perpetua exposes an MCP (Model Context Protocol) server so AI agents and IDEs like Claude Desktop, Cursor, and VS Code can discover your connected providers and make proxied API requests -- no OAuth code required.

Endpoint

https://www.perpetua.sh/api/mcp

Transport: Streamable HTTP. Auth: same Bearer pk_... API key used for the REST proxy.

Tools

The MCP server exposes three tools:

list_providers

List all available OAuth providers. Shows which providers are configured and whether you have an active connection for each.

Parameters: None.

Returns: JSON array of providers with slug, displayName, baseUrl, and connected (boolean).

list_connections

List your active OAuth connections. Shows provider, account label, granted scopes, and whether the access token is fresh or expired.

Parameters: None.

Returns: JSON array of connections with provider, account, scopes, and tokenStatus ("fresh", "expired", or "unknown").

proxy_request

Make an authenticated API request to a connected OAuth provider. Perpetua handles token refresh automatically.

ParameterTypeRequiredDescription
providerstringYesProvider slug (e.g. oura, gcal)
pathstringYesAPI path (e.g. /v2/usercollection/daily_sleep)
methodstringNoHTTP method: GET, POST, PUT, PATCH, DELETE. Defaults to GET.
headersobjectNoHeaders to send to the upstream provider (e.g. { "Notion-Version": "2022-06-28" })
bodystringNoRequest body (typically JSON string for POST/PUT/PATCH)
accountstringNoAccount label for multi-account providers
query_paramsobjectNoQuery parameters to append to the upstream URL

Returns: The upstream API response as text. JSON responses are returned as-is. Responses over 100KB are truncated. Binary responses return an error with the content type and size.

Why MCP?

// Before: agent manages tokens
const token = await getOAuthToken("oura", "personal");
const res = await fetch("https://api.ouraring.com/v2/usercollection/daily_sleep", {
  headers: { Authorization: `Bearer ${token}` }
});

// After: agent uses Perpetua (via MCP or REST proxy)
const res = await fetch("https://www.perpetua.sh/api/proxy/oura/v2/usercollection/daily_sleep", {
  headers: { Authorization: `Bearer ${PERPETUA_API_KEY}` }
});

With MCP, your agent discovers providers and makes calls through tool use -- it never sees an OAuth token.

Configuration examples

Claude Desktop

Add this to your Claude Desktop configuration (claude_desktop_config.json):

{
  "mcpServers": {
    "perpetua": {
      "type": "streamablehttp",
      "url": "https://www.perpetua.sh/api/mcp",
      "headers": {
        "Authorization": "Bearer pk_your_key"
      }
    }
  }
}

Cursor / VS Code (.mcp.json)

Add this to your project's .mcp.json file:

{
  "mcpServers": {
    "perpetua": {
      "type": "streamablehttp",
      "url": "https://www.perpetua.sh/api/mcp",
      "headers": {
        "Authorization": "Bearer pk_your_key"
      }
    }
  }
}

Testing with curl

curl -X POST https://www.perpetua.sh/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-03-26" \
  -H "Authorization: Bearer pk_your_key" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": { "name": "test", "version": "1.0" }
    }
  }'