API Access and Plans

Public access, issued API keys, rate limits, Decisioning availability, and commercial packaging.

Public Access

The Philidor API is public for evaluation, research, and development. Anonymous requests receive 10 requests per 60 seconds per IP address. Headline risk scores and tiers are public and stay public: /v1/vaults, per-vault detail, /v1/rwa, per-asset detail, stats, events, protocols, curators, and methodology all answer without a key.

First-party Philidor applications use a separate first-party budget of 60 requests per 60 seconds, so normal product traffic does not consume the anonymous public integration budget.

Keyless access is metered to discourage bulk scraping: list responses cap page size at 100 rows, anonymous pagination stops after the first 200 rows (a deeper offset answers 403 and names the key requirement), the incremental feed /v1/vaults/changes looks back 30 days at most, and a per-IP daily volume cap applies. Any API key removes these caps.

Every limit is a fixed 60-second window, not a sliding one: X-RateLimit-Reset is the Unix time the current window ends and the full allowance returns at once.

curl https://api.philidor.io/v1/vaults?limit=5

Endpoints That Require a Plan

Analytical and operational endpoints require an issued key on the plan shown in the generated Access Matrix. It lists every route, its minimum plan, anonymous and keyed outcomes, and rate bucket. Depending on the enforcing gate, an anonymous request answers 401 or 403; a valid key below the required plan answers 403.

The per-asset detail endpoints (/v1/assets/{chain_id}/{address} and /v1/rwa/{asset_id}) answer anonymously with the published score and tier, but the per-dimension values, risk_score_breakdown, and the attestation ledger require a key. Stripped responses name what was withheld in a restricted_fields array.

Limited-release Risk Graph endpoints additionally require an eligible Decisioning key. Public decision verification remains available when the Risk Graph API is enabled.

API Keys

API keys use the pk_live_ format and are sent with the Authorization header.

curl -H "Authorization: Bearer pk_live_your_key_here" \
  https://api.philidor.io/v1/vaults?limit=100

Keys are shown once, at minting, and stored as hashes. Authentication uses a SHA-256 digest of the 128-bit random key; existing bcrypt-only keys are verified in a bounded worker thread and acquire the digest after successful authentication. Each key carries its plan, its own rate limit, and its own usage counter.

Self-serve. An organisation on a plan mints and revokes its own keys in the dashboard under Settings → API keys. A self-serve key takes the plan's default limit. For a negotiated limit, have an admin provision the key with an explicit rate limit recorded as metadata.contracted_rpm; an existing key's negotiated limit does not transfer to a new self-serve key. Revoked keys stay listed with is_active: false when you ask for them, and a revoked key cannot be reactivated.

Rotation. Mint the replacement, move the integration to it, then revoke the old key. Revocation is immediate: the old key answers 401 on its next request, and any webhook subscriptions it created are disabled in the same step. Keys do not expire on their own.

To obtain a plan, use the contact form.

First Call With an Issued Key

After receiving a key, make a small request and confirm that rate-limit headers are present.

curl -i \
  -H "Authorization: Bearer pk_live_your_key_here" \
  "https://api.philidor.io/v1/vaults?limit=1"

Look for HTTP/2 200, X-RateLimit-Limit, X-RateLimit-Remaining, and a JSON body with data.

Your Key: GET /v1/me

Any key can read its own identity, effective limits, and usage. The response is never cached and only ever describes the calling key.

curl -H "Authorization: Bearer pk_live_your_key_here" https://api.philidor.io/v1/me
{
  "data": {
    "key": {
      "id": "3f6c1a2e-9b7d-4c1e-8f2a-1d2e3f4a5b6c",
      "prefix": "pk_live_0394f668",
      "partner_name": "Example Capital",
      "tier": "data",
      "created_at": "2026-09-11T10:00:00.000Z",
      "last_used_at": "2026-09-11T18:00:00.000Z",
      "org_id": null
    },
    "limits": {
      "requests_per_minute": 300,
      "shared_key": false,
      "subclient_requests_per_minute": null
    },
    "usage": {
      "day": "2026-09-11",
      "as_of_lag_seconds": 60,
      "total_count": 1234,
      "throttled_count": 5,
      "endpoints": [
        {
          "endpoint": "/v1/vaults/:chain/:address",
          "count": 40,
          "last_seen": "2026-09-11T18:00:00.000Z"
        }
      ]
    }
  }
}

key.id is the value that appears in request logs and audit records; quote it together with a request_id when reporting a problem. usage.endpoints covers the current UTC day and is flushed every 60 seconds, so the last minute may be missing. total_count counts served requests over the key's lifetime; requests answered 429 are counted in throttled_count instead. subclient_requests_per_minute is set only for shared-key integrations.

Plans

Tier rpm is a mint default; a negotiated key limit is recorded as metadata.contracted_rpm and survives tier alignment. GET /v1/me reports the effective limit. Route family budgets and SSE connection limits apply separately.

The per-route table — which plan unlocks each route, the rate bucket it is metered on, and the outcome per plan — is generated from the API's route policy: Access Matrix.

PlanIncludes
DataScores, vault and asset registry, events and signals, baskets, exports, issued keys with elevated limits, and support
DecisioningEverything in Data plus the Risk Graph family, portfolio look-through, pre-trade checks with signed decisions, breach and incident exposure, and signed webhooks (published events, retractions, rating changes, security events, basket lifecycle)
EmbeddedCustom integrations, white-label data, contractual terms, custom webhooks, and custom limits

Decisioning capabilities are available by arrangement while the Risk Graph API is in limited release.

Keys are provisioned as data, decisioning, or embedded. Decisioning and embedded keys unlock Decisioning capabilities when enabled for the customer arrangement.

Support channels and service commitments are defined in each agreement. Embedded agreements can include uptime and support commitments.

Headers

Request headers the API reads:

HeaderUse
AuthorizationBearer pk_live_... on any request
X-Philidor-SubclientDownstream client id on a shared-key integration (ignored on ordinary keys)
Last-Event-IDResume cursor for /v1/events/stream (see Events)
If-None-MatchConditional request against a cached response's ETag (/v1/openapi.json)

Response headers:

HeaderDescription
X-Request-IdRequest identifier; also in every error body as request_id
X-RateLimit-LimitMaximum requests allowed per window
X-RateLimit-RemainingRequests remaining in the window
X-RateLimit-ResetUnix timestamp for window reset
X-RateLimit-WindowWindow duration (60s)
X-RateLimit-Subclient-LimitShared-key integrations only: the calling sub-client's own allowance
X-RateLimit-Subclient-RemainingShared-key integrations only: what is left of it
Retry-AfterPresent on 429 and on 503/504 fail-safe and timeout responses
X-Cache, AgeCache outcome (HIT, MISS, BYPASS, STALE-ERROR) and seconds since the copy was cached
Warning: 111With X-Cache: STALE-ERROR: a stale copy served because the fresh read failed
X-Philidor-FailSafe-ModeOn risk endpoints while the platform is in a fail-safe mode

Calling From a Browser

Cross-origin requests are served only to Philidor's own origins. A browser application on another origin does not receive Access-Control-Allow-Origin and must call the API through its own backend, which also keeps the key out of the client. Server-to-server calls (no Origin header) are unaffected.

Anonymous Limits By Path

The Access Matrix lists each route's anonymous outcome and rate bucket, including public graph verification and snapshot exceptions. Public /v1/* reads normally allow 10 requests per minute per IP; gated routes require the plan shown in the matrix.

Keyed Limits

A key's aggregate allowance is metered per key, so an integration that runs behind several egress IPs or a shared NAT keeps its whole allowance. Route families also charge a per-IP budget and reject only when both the key and IP budgets reject; the IP budget can allow a request after the key family budget is exhausted.

The Access Matrix lists the keyed budget for each route. Public catch-all routes use the key's rpm; family reads use the larger of the key's rpm and the family cap, while graph checks and basket webhook writes use the family cap.

Private /v1/indices* and /v1/rates* routes require separate index tokens; a commercial API key does not grant access.

Each family is a separate budget: a burst against the Risk Graph does not reduce the allowance left for /v1/vaults. A rejected key (malformed, unknown, revoked, expired, or with the wrong secret) answers 401 and consumes the public 10/minute budget of the calling IP, including on /v1/events/stream. These responses carry rate limit headers; once that budget is exhausted, requests answer 429 with Retry-After. Invalid credentials never fall back to anonymous access.

Shared-key integrations

A single key that serves several downstream clients (for example a hosted MCP server) is provisioned as a shared key. Such an integration sends X-Philidor-Subclient: <client id> on each request and every declared client is metered on its own bucket of 25% of the key's rate limit, reported in X-RateLimit-Subclient-Limit and X-RateLimit-Subclient-Remaining; requests without the header share one pooled bucket. Ordinary keys are not shared keys: the header is ignored and the subclient headers are never sent.

401 and 403 Troubleshooting

StatusCodeCommon causeAction
401UNAUTHORIZED (legacy_code: invalid_api_key)A presented pk_live_ key is malformed, unknown, revoked, expired, or has the wrong secretSend the complete key: pk_live_ followed by 32 lowercase hex characters; rotate a revoked key
401UNAUTHORIZED (legacy_code: tier_required)No authenticated key on a plan-gated endpointSend an issued key for the required plan
403FORBIDDENA valid key is below the endpoint's required tierRequest access for the required capability
403FORBIDDENDashboard session lacks feature accessCheck the active organization and plan

Every one of these bodies carries request_id; the same value is in the X-Request-Id response header. During the one-release migration, error.legacy_code preserves the former invalid_api_key or tier_required value. See Errors for the envelope.

Fail-Safe Responses

Risk endpoints can return 503 when the platform is in fail_closed mode. These responses include Retry-After and X-Philidor-FailSafe-Mode.

Baskets Caching

Current basket routes return an ETag and use s-maxage=60 with stale-while-revalidate. Immutable version routes use Cache-Control: public, max-age=31536000, immutable.

If-None-Match returns 304 when the supplied ETag still matches.

Events Stream

GET /v1/events/stream returns text/event-stream and requires a Data-plan key. The stream sends heartbeat comments every 20 seconds and allows 200 concurrent connections per key and 5 per end viewer reported with X-Philidor-Client-Ip. Reconnect with Last-Event-ID and missed frames are replayed (24 hours, 1000 frames); see Events.

Browser EventSource cannot send an Authorization header, so browser consumers should connect through a server-side relay that holds the key. Server-side consumers pass the key on the underlying request:

// Node 20+ with the `eventsource` package (v3):
import { EventSource } from 'eventsource';

const source = new EventSource('https://api.philidor.io/v1/events/stream', {
  fetch: (input, init) =>
    fetch(input, {
      ...init,
      headers: { ...(init?.headers ?? {}), Authorization: 'Bearer pk_live_your_key_here' },
    }),
});
source.addEventListener('published', (event) => {
  console.log(JSON.parse(event.data));
});

Event frames use published, promoted, or retracted.

Webhooks

/v1/webhooks (generic families: event.published, event.retracted, rating.changed, security_event.published) and /v1/baskets/{id}/webhooks (basket lifecycle) are available to Decisioning and Embedded keys. Subscriptions belong to your organisation and survive key rotation; see Webhooks.

Outbound deliveries include X-Philidor-Signature in this format.

t=<unix_ts>,v1=<hex>

Philidor signs the string timestamp.body with HMAC-SHA-256. Receivers should enforce a five-minute tolerance. Multiple v1 segments are accepted to support secret rotation. The signing secret is shown only at creation.

On this page

Raw