Developers

Build on the engine.

A JSON API, a live stream, signed webhooks and an MCP connector for the same engine documents you see in the Workspace, projected to your plan.

What you can build

Dashboards

Pull the dashboard and calendar documents into your own screen, with ETag caching or the live stream.

Bots

Receive signed alert deliveries at your own https endpoint and route them to chat, a queue or a spreadsheet.

AI assistants

Connect an MCP client so your assistant can read the calendar, the regime and the Academy while you work.

Quickstart

Base URL
https://priceisall.com/api/v1
Authentication

Sign in with POST /auth/login and send the access token as Authorization: Bearer .... Refresh with POST /auth/refresh. The keys on your Workspace keys page are for AI models, not for this API.

Responses

Success is { "data": ... }. Failure is { "error": { "code", "message" } } with a stable code.

curl
# 1. Sign in
curl -s https://priceisall.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"..."}'
# Copy data.tokens.access_token from the reply.
# Refresh later with:
#   curl -s https://priceisall.com/api/v1/auth/refresh -H "Content-Type: application/json" \
#     -d '{"refresh_token":"..."}'

# 2. Read the dashboard for your plan
curl -s https://priceisall.com/api/v1/engine/dashboard \
  -H "Authorization: Bearer $ACCESS_TOKEN"
JavaScript
const BASE = "https://priceisall.com/api/v1";

const login = await fetch(`${BASE}/auth/login`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: "[email protected]", password: "..." }),
}).then((r) => r.json());
const token = login.data.tokens.access_token;

const res = await fetch(`${BASE}/engine/dashboard`, {
  headers: { Authorization: `Bearer ${token}` },
});
const { data } = await res.json(); // { error: { code, message } } on failure
Python
import requests

BASE = "https://priceisall.com/api/v1"

login = requests.post(f"{BASE}/auth/login",
                      json={"email": "[email protected]", "password": "..."})
token = login.json()["data"]["tokens"]["access_token"]

res = requests.get(f"{BASE}/engine/dashboard",
                   headers={"Authorization": f"Bearer {token}"})
data = res.json()["data"]  # {"error": {...}} on failure

Endpoint reference

Sign in

Sessions are a short-lived access token plus a refresh token.

MethodPathAuthWhat it does
POST/auth/loginNoneExchange email and password for an access token and a refresh token.
POST/auth/refreshRefresh tokenRotate a session. Send {"refresh_token": "..."} as JSON. Returns a new token pair.
POST/auth/logoutRefresh tokenEnd a session.
GET/meBearerYour account profile.

Engine documents

The same documents the Workspace renders, projected to your plan.

MethodPathAuthWhat it does
GET/engineBearerManifest: version, publish time, your entitlements, and an ETag per document as you see it.
GET/engine/{key}BearerOne document, projected to your plan. Keys: dashboard, calendar, calendar_history. Supports If-None-Match.
GET/engine/streamBearerServer-sent events: the same documents, pushed when they change.
GET/public/engine/{key}NoneThe official calendar, signed out (calendar and calendar_history only). Mounted only when the public calendar is switched on.

Alerts

Follows, channels and the inbox behind the Alerts page.

MethodPathAuthWhat it does
GET/alerts/followsBearerThe markets you follow.
PUT/alerts/followsBearerReplace the markets you follow.
GET/alerts/channelsBearerChannel states: in-app and webhook.
PUT/alerts/channels/{channel}BearerTurn a channel on or off.
GET/alerts/inboxBearerIn-app alerts. Pass ?after=<seq> to page forward.
GET/alerts/deliveriesBearerDelivery history, metadata only.

Webhook endpoints

Manage where signed alert deliveries go. Up to 3 endpoints per account.

MethodPathAuthWhat it does
GET/alerts/webhooksBearerList your endpoints. A secret is never returned here. (Maestro)
POST/alerts/webhooksBearerCreate an endpoint from {"url": "https://..."}. The reply carries the signing secret once. (Maestro)
PUT/alerts/webhooks/{id}BearerPause or resume an endpoint with {"enabled": true}. (Maestro)
DELETE/alerts/webhooks/{id}BearerDelete an endpoint and its secret. (Maestro)
POST/alerts/webhooks/{id}/rotateBearerReplace the secret at once. The new one is shown once. (Maestro)
POST/alerts/webhooks/{id}/testBearerSend a signed ping and report the outcome. (Maestro)

Catalog

MethodPathAuthWhat it does
GET/plansNoneThe public plan catalog.

Document reads return an ETag and answer 304 to If-None-Match. Headers X-Readmodel-Version and, when the data is old, X-Readmodel-Stale: 1 come with them.

Live stream

GET /engine/stream keeps one connection open and sends a frame whenever your projection changes. Resume with the Last-Event-ID header or the last_event_id query parameter.

event: doc

A document changed for your projection. Data is the document envelope.

event: gone

A document you had was removed. Data: key and version.

event: alert

An in-app alert, when alerts are on. Carries no id line.

event: bye

The server is closing. Reasons: shutdown, max_age, unauthorized, unavailable, entitlements_changed.

Webhooks

Alerts can post to your own https endpoint. Create one with the API or on the Alerts page, keep the secret it shows once, and verify every request before you trust it.

Plan

Webhook alerts need Maestro (engine.reads).

Endpoint rules

https only, port 443, a public host name (no IP addresses), at most 2048 characters. Up to 3 endpoints.

Headers

PriceIsAll-Signature, PriceIsAll-Delivery and Idempotency-Key. User agent: PriceIsAll-Webhooks/1.

Signature

t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>." followed by the raw body, keyed with your secret>. Reject a timestamp more than 300 seconds old.

Retries

A non-2xx answer or no answer is retried after 10 s, 30 s, 2 m, 10 m, 30 m and 1 h: 7 attempts in all. Each attempt times out after 5 seconds.

Auto-off

After 5 dead deliveries in a row the endpoint switches off. Send a successful test to turn it back on.

Deduplicate

Use delivery_id or the Idempotency-Key header. A retry carries the same ids and a fresh signature timestamp.

Create an endpoint

curl -s https://priceisall.com/api/v1/alerts/webhooks \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/priceisall"}'
# The reply includes the signing secret once. Then send a test:
#   curl -s -X POST https://priceisall.com/api/v1/alerts/webhooks/$ID/test \
#     -H "Authorization: Bearer $ACCESS_TOKEN"

Test ping body

{
  "api_version": "pia-webhook/1",
  "delivery_id": "ping_0f3a9c2e...",
  "sent_at": "2026-10-17T09:30:00Z",
  "type": "ping"
}

Alert body

{
  "api_version": "pia-webhook/1",
  "data": { ... },
  "delivery_id": "...",
  "read_id": "...",
  "recorded_at": "2026-10-17T09:30:00.123Z",
  "type": "alert.post_read"
}

data is the same alert content you see in your Inbox. Keys are sorted. The body is signed byte for byte, so verify the raw body before parsing it.

Verify a delivery

JavaScript (Node)
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body as a string, exactly as received.
export function verify(secret, header, rawBody, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const want = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const got = Buffer.from(parts.v1 ?? "", "hex");
  const exp = Buffer.from(want, "hex");
  return got.length === exp.length && timingSafeEqual(got, exp);
}
// Read the header from "PriceIsAll-Signature".
Python
import hashlib, hmac, time

def verify(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts.get("t", "0"))
    if not t or abs(time.time() - t) > tolerance:
        return False
    signed = f"{t}.".encode() + raw_body
    want = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, parts.get("v1", ""))
# Read the header from "PriceIsAll-Signature".

MCP for AI assistants

A read-only MCP server for assistants that support remote MCP connectors, such as Claude or ChatGPT style clients. The assistant signs you in through OAuth, you approve what it may read on a consent screen, and you can revoke it from your account at any time. It has no tool that places a trade or spends your balance.

Server URL
https://mcp.priceisall.com/mcp
  1. Open your assistant's settings and add a custom connector or remote MCP server.
  2. Paste the server URL below. No API key is needed.
  3. Your assistant opens a PriceIsAll sign-in. Approve what it may read on the consent screen.
  4. Ask a question such as "What does the official calendar show for this week?"
  5. Revoke the app any time from Connected apps in your account.
ToolReadsPlan
get_regimeThe market regime as your plan sees it.All plans
get_track_recordPublished Ledger entries.All plans
verifyCheck a Ledger entry or batch by id.All plans
get_calendarThe official calendar for a window of up to 21 days.All plans
get_release_historyHistory of one scheduled release.All plans
get_newsOfficial news headlines.All plans
search_academySearch Academy lessons.All plans
get_lessonOne Academy lesson.All plans
get_glossaryOne glossary term.All plans
search_academy_sectionsSearch bounded Academy sections.All plans
get_academy_sectionOne Academy section by id.All plans
get_marketOne market from the board.Maestro
get_market_mapThe market map, by asset class and horizon.Maestro
explainExplain one Ledger call by id.Maestro

For client authors

/mcpThe MCP endpoint your client connects to.
/.well-known/oauth-protected-resourceOAuth resource metadata. Clients read this first.
/.well-known/oauth-authorization-serverOAuth server metadata: authorize, token and revoke endpoints.
/oauth/authorizeWhere you sign in and approve the app.
/oauth/tokenToken exchange and refresh.
/oauth/revokeToken revocation.

Desktop agents

Claude Desktop, Claude Code and Cursor reach the same MCP connector directly. Nothing else needs to be installed. The connector is read-only for every client. It has no tool that places a trade, spends a balance or changes your account, and you can revoke any app from Connections.

Claude Desktop

  1. Open Settings, then Connectors, and choose to add a custom connector.
  2. Paste https://mcp.priceisall.com/mcp as the server URL. Leave any advanced client fields empty.
  3. Claude opens a PriceIsAll sign-in in your browser. Approve the read-only access on the consent screen.

Claude connects through its own connector service, which is why no client id or secret is asked for here.

Claude Code

  1. Add the server with the pre-registered client and a fixed callback port. The server has no dynamic registration, so the client id is required.
  2. Run /mcp inside Claude Code, or claude mcp login priceisall in your shell, to open the browser sign-in.
  3. Approve the read-only access on the consent screen.
claude mcp add --transport http \
  --client-id pia-cursor --callback-port 8080 \
  priceisall https://mcp.priceisall.com/mcp

The client is public, so no secret is set. Any callback port works because loopback redirects ignore the port.

Cursor

  1. Open ~/.cursor/mcp.json (or the project's .cursor/mcp.json) and add the server below.
  2. Cursor shows the server as needing sign-in. Choose connect and approve the read-only access in your browser.
{
  "mcpServers": {
    "priceisall": {
      "url": "https://mcp.priceisall.com/mcp",
      "auth": { "CLIENT_ID": "pia-cursor" }
    }
  }
}

Cursor's desktop app returns to http://localhost:8787/callback, which the pia-cursor client allows.

What each plan sees

Apprentice11 tools: regime, track record, calendar, news and the Academy.
MaestroAll 14 tools, adding market, market map and explain.

What each plan includes

CapabilityApprenticeMaestro
Dashboard headline and official calendar (API and stream)YesYes
Regime composite and multiplierNoYes
Board documents: pairs, assets, overlap, coverageNoYes
Webhook alert endpointsNoYes
MCP: calendar, regime, Academy, news, track record toolsYesYes
MCP: market, market map and explain toolsNoYes

Apprentice

$0
engine.headline

generated_at, source and regime.state on the dashboard, plus the official calendar.

Maestro

$39 / month
engine.headlineengine.regimeengine.readsengine.board

Everything in Apprentice, plus regime.composite and regime.multiplier, and the board: pairs, assets, asset_overlap and coverage. Post-Read items arrive through alerts, not as an /engine document.

The server only removes fields. It never rounds, reorders or recomputes a value, and a field outside your plan is absent, not null. Your own entitlements are listed in the GET /engine manifest.

Limits

Streams

Defaults (ENGINE_STREAM_PER_USER, ENGINE_STREAM_MAX): 4 open streams per account, 1000 per server. A stream ends after about 15 minutes with a bye event; reconnect with last_event_id to resume.

Sign in

5 requests per minute per address, burst 5. Login is also limited per email.

Refresh

60 per minute per address, burst 20 (the default).

Public calendar

60 per minute per address, burst 30 (the default).

MCP connector

60 requests per minute per connected app, burst 20 (the default, MCP_RATE_PER_MIN and MCP_RATE_BURST). Access tokens last 60 minutes and refresh tokens 30 days by default.

Webhook endpoints

3 endpoints per account. A test ping is limited to 3 at once, then 1 per minute.

Document reads

No per-request limit is defined in code. Use ETag and If-None-Match, or the stream, instead of polling.

Errors

{
  "error": {
    "code": "unauthorized",
    "message": "Authentication is required."
  }
}
400bad_requestMalformed request body (login, refresh).
401unauthorizedNo valid Bearer token, or bad credentials at login ("Invalid email or password."), or an invalid or expired refresh token.
403forbiddenLogin for a suspended account.
422validation_failedA field failed validation, for example a missing refresh_token.
404not_foundThe key is not a customer document.
404engine_doc_missingThe current version has no such document.
429rate_limitedToo many requests from one address, or too many sign-in attempts for one email.
429engine_stream_limitToo many open streams for the account or the server.
403not_entitledWebhook endpoints need Maestro.
409webhook_limitYou already have 3 webhook endpoints.
409webhook_needs_testAn endpoint switched off after failures. Send a successful test to turn it back on.
409webhook_secret_unreadableRotate the secret, then send a test.
503webhook_busyToo many test pings are running. Try again in a few seconds.
503engine_unavailableNo read-model version has been published yet.
503engine_not_configuredThe read API is not switched on for this server.

Embeddable widgets

No code needed for the public data. Pick a widget, preview it and copy the embed snippet from the widgets gallery. Embeds carry only data that is already public and a link back to PriceIsAll.

Start from the Workspace.

A free account reads the headline and the official calendar. Manage AI keys and connected apps from your Workspace.