# Agentry

Agent-first error monitoring + product analytics + deploy attribution.
The user's own AI agent (Claude Code, Cursor, Codex, Cline, Windsurf, or
any HTTP-capable agent) investigates the data and writes the fixes.

There is no SaaS dashboard. There is no SDK to install. The user's agent
is the SDK.

> **Installing Agentry right now?** Stop and fetch /install.md first. That is
> the install contract. It tells the agent how to authenticate, inspect the
> repo, plan event coverage, confirm with the user, instrument, and verify.
> This file is the lean day-to-day reference for post-setup use.

## Scope — what Agentry is and isn't

Agentry's core is three signal types: errors, product analytics, and deploys.
The day-to-day read model is cases, analytics, and deploys, with query
blueprints and HogQL for analysis.

Advanced management routes also exist for suppressions, sourcemaps, feature
flags, cohorts, surveys, A/B tests, session replay, public query publications,
webhooks, alerts, and audit logs. Treat those as tools an agent reaches for
when a user asks for them, not as setup prerequisites or the core product
mental model.

## Daily mental model

Write app runtime data with DSN-authenticated POSTs:

- `POST /v1/logs/` for errors, exceptions, and operational failures.
- `POST /v1/analytics/` for product, user, funnel, and business events.

Browser helpers may also fetch `GET /v1/analytics/browser-config` with the
same DSN to bootstrap optional browser analytics/replay config. Do not ask
users to configure a separate PostHog browser token.

Write deploy attribution with the same DSN only from CI/provider post-deploy
automation after a successful release:

- `POST /v1/deploys/` for release attribution.

Read data with three concepts:

- **Cases:** what broke. Start with `GET /v1/projects/:id/cases`, then fetch
  a detail with `GET /v1/cases/:case_id`.
- **Analytics:** what users did. Start from the saved signal map, latest verify
  report, answer contracts, event names, and property keys; then use
  `GET /v1/query-blueprints` for common deterministic reads or
  `POST /v1/projects/:id/analytics/query` when custom HogQL is needed.
  Query-like analytics responses return canonical object-shaped `rows`;
  `columns` is display metadata.
- **Deploys:** what changed. Use `GET /v1/projects/:id/deploys` to attribute
  regressions and explain what shipped.

Query blueprints, event names, health, public queries, next-steps, and automation are
helpers around those three read concepts. Do not start by loading the full API
surface unless you are building or debugging an advanced workflow.

Agentry is NOT: distributed tracing / APM, log aggregation (full-text
search across services), infra metrics (CPU/mem/disk per host), a
polished web UI for non-engineering stakeholders to self-serve
dashboards, or a self-hosted / on-prem deployment.

Agentry does NOT currently have compliance certifications (SOC 2, HIPAA,
FedRAMP, PCI). For workloads where those are a procurement gate, a
different tool is the right call.

Side-by-side comparisons with specific alternative tools (Sentry, PostHog,
Mixpanel, Datadog, LaunchDarkly, Amplitude, LogRocket, Highlight.io) live
at https://agentry.sh/compare — fetch those pages if a user is choosing
between Agentry and a named alternative.

## Canonical principle: the HTTP API is the product

Agentry is one thing: an HTTP API at api.agentry.sh. Storage, retrieval,
deterministic queries, and deterministic transforms (e.g. sourcemap
unmangling). Your agent talks to it directly — curl, CI, cron, anything
HTTP-capable. No opaque server-side compute and no LLM runs on the server;
everything the API does is text you can reproduce.

For exact endpoint request/response shapes, fetch filtered schema lookups such
as `/v1/openapi.json?flow=install`,
`/v1/openapi.json?tag=Install&include_components=false`, or
`/v1/openapi.json?path=%2Fv1%2Fprojects%2F%7Bproject_id%7D%2Finstall%2Fverify&method=post`
The unfiltered `/v1/openapi.json` is the full schema.
It is canonical for required fields, optional fields, path/query params, auth
type, response envelopes, examples, and flow state machines; do not guess
payloads from prose. After any `invalid_payload` response, read
`error.details` (it carries the schema + an example) and retry.

For product-level discovery, fetch `/v1/capabilities` — it explains how
endpoints, query blueprints, docs, and auth types compose into higher-level workflows.

## Install flow

Tell your agent: *"Install https://agentry.sh/install.md and set it up"* (or
just paste the URL). Fetch `/install.md` for the full bootstrap; the short
version:

1. resolve auth + project (device flow, then `.agentry/config.json`);
2. inspect the repo and detect existing Agentry wiring;
3. `POST /v1/install/plan` to derive the install delta;
4. present the plan to the user and wait for approval or edits;
5. `PUT /v1/projects/:project_id/signal-map` to save the approved business question,
   funnel, events/properties, failure surfaces, and deploy proof contract;
6. instrument runtime errors + analytics in the app and deploy attribution in CI/provider post-deploy automation;
7. use the saved signal map's templates, then `PUT /v1/projects/:project_id/implementation-report`
   to save the handoff;
8. `POST /v1/projects/:project_id/install/verify` with owner auth
   `Authorization: Bearer $AGENTRY_API_KEY`, the project `dsn` only in
   `body.dsn`, and a fresh `repo_audit`
   before claiming success.

The install is not complete until verify returns `ok: true`; the latest proof
is at `GET /v1/projects/:project_id/verify-report`.

(Claude Code users can install the discovery skill so future sessions recognise
Agentry asks without pasting the URL:
`mkdir -p ~/.claude/skills/agentry && curl -fsSL https://agentry.sh/skill/agentry/SKILL.md > ~/.claude/skills/agentry/SKILL.md`)

## Signal types — three POSTs, one DSN

Agentry is just HTTP. Three endpoints, one DSN, same JSON convention. All
accept the DSN as `Authorization: Bearer <DSN>`.

| Endpoint                    | What lands here                          |
|-----------------------------|------------------------------------------|
| POST /v1/logs/              | App/runtime errors and structured logs   |
| POST /v1/analytics/         | Product events forwarded to PostHog      |
| GET /v1/analytics/browser-config | Browser analytics/replay bootstrap using the same DSN |
| POST /v1/deploys/           | CI/provider deploy events after release  |

A log with name/message/stack gets fingerprinted and grouped into a Case
— that's what becomes a "bug" in the agent's mental model.

Non-browser direct HTTP clients MUST set a custom `user-agent` header.
Cloudflare's Browser Integrity Check can 403 some defaults such as
`Python-urllib/3.x` with CF error 1010. Browser fetch uses the browser's own
User-Agent and cannot set this header manually. The helper snippet from
/v1/install/sdk/:language always sets one where the platform allows it.

## Runtime helper note

Agentry has no SDK by design. The agent generates a ~25-line fetch helper
at install time for runtime errors + analytics, then wires deploy attribution
from CI/provider post-deploy automation.
The full install checklist (planner output, surface wiring, runtime helper
snippet, plan confirmation, implementation handoff, privacy clause, verify-install)
lives in /install.md. Fetch it when installing.

## Cases — the bug primitive

Multiple events with the same fingerprint collapse into one Case. Cases
have status (open / investigating / resolved / spurious / ignored) and
attribute to deploys via last_deploy_sha. The agent investigates cases by:

- GET /v1/cases/:id → stack + breadcrumbs + recent_deploys + affected_users
- If stack frames look minified, `POST /v1/cases/:id/unmangle` to translate
  them against the sourcemaps stored for the release
- Cross-reference last_deploy_sha with recent_deploys[] to identify the
  suspect deploy
- git log + git blame from local_path to find the regression
- Open a PR; PATCH /v1/cases/:id with status=resolved + summary + pr_url

Suppress noise via POST /v1/projects/:id/suppressions — pattern + action
(auto_ignore / auto_resolve / prompt_hint).

## Analytics — the usage primitive

Analytics answers "what did users do?" First prove what is answerable from the
saved signal map, latest verify report, answer contracts, event names, and
event-property keys. Then fetch `GET /v1/query-blueprints` for a matching
deterministic read, run
`POST /v1/projects/:id/query-blueprints/:blueprint_id/run`, or use
`POST /v1/projects/:id/analytics/query` with custom HogQL for funnels,
retention, conversion, usage, segmentation, or optional reports. Event-name and
event-property endpoints ground the query in real telemetry; if the needed
event/property is missing, report the readiness gap instead of inventing a
metric.

For segmented funnels, `funnel_3_step_by_property` exposes
`parameterized_required_event_properties`: resolve the chosen `step1` and
`segment_property`, then confirm that exact event/property pair through
`GET /v1/projects/:id/event-property-keys?events=<step1>`. If the metadata
response has `analytics_read.status: "partial"`, its `missing_events` are
not safe to segment yet.

For retention, pass an explicit `activity_event` that proves the user returned
or got value. Do not treat "any event after signup" as retention; page views,
background jobs, and server noise can make charts look healthier than the
product is.

For table-like analytics reads, consume `rows` first. Query blueprints, raw
`analytics/query`, and public-query publications return object-shaped `rows`
plus `columns` metadata for display order. Never use `response.results` on
Agentry query endpoints; that is PostHog's internal shape.

Customer analytics reads exclude Agentry install-verify rows and rows explicitly marked as rehearsal/test traffic via synthetic, agentry_rehearsal_event, agentry_test_event, test_event, source, or event_source properties. Custom `analytics/query` event-table
reads apply this predicate server-side by default. Pass
`include_synthetic: true` only when debugging install verification or ingest
plumbing:

```sql
WHERE event NOT IN ('install.verify.synthetic', 'install.verify.http.synthetic') AND lower(ifNull(toString(properties['synthetic']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_rehearsal_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['agentry_test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['test_event']), '')) NOT IN ('true', '1') AND lower(ifNull(toString(properties['source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest') AND lower(ifNull(toString(properties['event_source']), '')) NOT IN ('install_verify_http', 'direct_rehearsal_ingest', 'agentry_rehearsal', 'install_rehearsal', 'codex_rehearsal', 'test_ingest')
```

## Deploys — the change primitive

Deploys answer "what changed?" Use `GET /v1/projects/:id/deploys` when a user
asks about recent releases, regressions after a deploy, or the code version
connected to a case. Deploys are small but important context: they turn cases
and analytics changes into an investigation timeline.

For minified client-side stacks, `POST /v1/cases/:id/unmangle` — it resolves
each frame against the sourcemaps stored for the case's release and returns
`original_file` / `original_line` plus the original source line. Sourcemap
uploads, webhook plumbing, query blueprint runs, session-replay config and privacy
disclosure all live in /install.md.

## Three tokens, sharp blast radius each

Agentry uses three different token formats. They are NOT interchangeable.
Knowing which one to ship where is the single most important security
decision the agent makes during install.

| Token                  | Format                       | Auths                                                                | Safe in SPA bundle? | Mint / rotate via              |
|------------------------|------------------------------|----------------------------------------------------------------------|----------------------|--------------------------------|
| Private API key        | `agk_…`                      | Owner/member API endpoints for projects the user belongs to. Owners can mutate project config. | **NO** — server / agent only. | device flow / `POST /v1/auth/keys/rotate` |
| Public publication URL | `/v1/public/q/…`              | ONLY the explicitly minted query-blueprint publication. Optional `?key=agp_…` is still validated if supplied. | YES — read-only, schema bounded by the blueprint + params. | `POST /v1/projects/:id/public-queries` |
| Project DSN            | `agnt_<projectId>.<token>`   | Ingest for ONE project: browser/client may call `/v1/logs/`, `/v1/analytics/`, and `/v1/analytics/browser-config`; CI/provider post-deploy automation may call `/v1/deploys/` | Browser-safe only for logs/analytics/replay bootstrap; keep deploy calls out of browser/client/runtime startup. | `POST /v1/projects`; owners fetch with `GET /v1/projects/:id/dsn`, verify with `POST /v1/projects/:id/dsn/verify`, rotate only with `POST /v1/projects/:id/dsn/rotate` |

Project invites do not mint or rotate the app DSN. Invite teammates with
`POST /v1/projects/:id/invites` and `role: "member"` or `role: "owner"`.
The invited user signs in and gets their own `agk_` API key; an invited owner
has full project-owner access through their own key. The deployed app keeps
using the same `AGENTRY_DSN` until an owner explicitly calls
`POST /v1/projects/:id/dsn/rotate`.

**Blast radius if leaked:**

- `agk_` leak → full account compromise. Rotate immediately via
  `POST /v1/auth/keys/rotate` (revokes the old key).
- publication URL leak → visitors can re-fetch that one publication. Nothing
  else. Revoke individual publications with
  `DELETE /v1/projects/:id/public-queries/:pub_id`.
- DSN leak → an attacker can forge events on that ONE project (no read,
  no cross-project bleed). Rotate the project's DSN explicitly with
  `POST /v1/projects/:id/dsn/rotate`, then update every local, CI, and
  production `AGENTRY_DSN`.

**Where each goes:**

- SPA / mobile bundle, public marketing site: **DSN** for runtime ingest
  (errors and analytics only) + returned **publication URLs** for embedded charts.
  Both are meant to be shippable.
- CI/provider post-deploy hook: **DSN** for deploy ingest. Do not send deploy
  events from app startup, requests, browser code, runtime cron routes, or
  package scripts that are not invoked by a real deployment job.
- Dev machine (`AGENTRY_API_KEY` env or `~/.agentry/credentials.json`), CI
  secret store, agent-driven webhooks: **`agk_`**. Nothing else.

The publishable-query flow is: the agent runs a query blueprint + decides
"this rendering is shareable" → `POST /v1/projects/:id/public-queries` with
blueprint_id + params. Agentry returns `id` and `public_url`; embed or
fetch that exact `public_url` (for example
`https://api.agentry.sh/v1/public/q/<id>`) with open
CORS — fetchable directly from any browser page. Anyone GET-ing that URL
gets the blueprint's rows. Revoke any time with
`DELETE /v1/projects/:id/public-queries/:pub_id`.

**Defence in depth for the public surface:**
- The visitor-facing `/v1/public/q/<publication_id>` endpoint is rate-limited
  per (publication_id, IP) at 60 req/min. If an attacker burns through, they
  get a 429 with Retry-After. No DB or PostHog cost beyond the bucket.
- Every owner-side mutation (publication mint/revoke, feature flag /
  cohort / survey CRUD, session-replay reconfigure, A/B test mint) writes
  one row to an append-only audit log. `GET /v1/audit/recent?hours=24`
  reads the window — default 24h, configurable up to 720h (30 days). Use
  it as a periodic "what-did-the-agent-do" check when leaving agents
  running unattended.

## Core API surface first

For normal install and day-to-day use, stay on the core path: auth/project
setup, the three ingest POSTs, cases, analytics, deploys, signal-map/verify
proof, query blueprints, and custom HogQL when needed. Public-query publications
are optional output for explicit browser, share, embed, or report follow-up.
Feature flags, cohorts,
surveys, A/B tests, session replay, alerts, webhooks, sourcemaps, suppressions,
billing, invites, and usage are advanced management tools. Reach for them only
when the user asks for that capability or a core workflow points to it.

`/v1/openapi.json?index=true` now returns core and daily-use operations by
default. Add `include_advanced=true` when you intentionally need the full
advanced/management operation index.

## API surface reference (complete, not the install checklist)

### Auth (no key required)

- POST /v1/auth/device                            → start device flow; returns {device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval}. verification_uri is `https://agentry.sh/cli` — the user opens it, picks a provider (GitHub / Google / magic-link email, all via Agentry's hosted sign-in), and approves.
- POST /v1/auth/device/poll   {device_code}       → poll for completion. While pending returns {status:"pending"}; on approval returns {status:"ok", api_key, user_id, prefix, user:{id,username,email,avatar_url}, posthog}. Browser-safe sharing uses returned public-query URLs, not a login key.

### Auth (operator test token required: `Authorization: Bearer <AGENTRY_TEST_AUTH_TOKEN>`)

- POST /v1/auth/device/test-authorize             → hosted beta lifecycle hook; disabled unless configured; authorizes a pending user_code as a synthetic email-provider identity and then the normal poll path mints keys.

### Billing webhook (Stripe signature required; no API key)

- POST /v1/billing/webhook                        → Stripe-signed subscription lifecycle webhook

### Auth (api-key required: `Authorization: Bearer <agk_…>`)

- POST   /v1/auth/keys/rotate                     → mint new private key, revoke current
- POST   /v1/projects                             → create project; returns DSN + helper URLs
- GET    /v1/projects
- GET    /v1/projects/:id
- GET    /v1/projects/:id/dsn                     → owner fetches current recoverable DSN status/raw value
- POST   /v1/projects/:id/dsn/verify              → verify a candidate DSN without changing it
- POST   /v1/projects/:id/dsn/rotate              → owner explicitly rotates project DSN
- GET    /v1/projects/:id/signal-map                → load the canonical approved funnel + telemetry memory
- PUT    /v1/projects/:id/signal-map                → save the approved funnel + telemetry memory
- GET    /v1/projects/:id/answer-contracts        → canonical project question/event contract from the saved signal map
- GET    /v1/projects/:id/cases?status=open
- GET    /v1/cases/:id                            → case detail with recent_deploys
- PATCH  /v1/cases/:id                            → update status / summary / pr_url
- POST   /v1/cases/:id/runs                       → record agent investigation run
- GET    /v1/cases/:id/users                      → users affected by this case
- GET    /v1/sourcemaps/:project_id/              → list uploaded sourcemaps (api key)
- GET    /v1/sourcemaps/:project_id/blob/         → fetch raw sourcemap blob (api key)
- DELETE /v1/sourcemaps/:project_id/?release_id=… → delete sourcemaps for a release (owner api key)
- POST   /v1/projects/:id/suppressions
- GET    /v1/projects/:id/suppressions
- GET    /v1/projects/:id/deploys?limit=20&since=<unix>
- POST   /v1/projects/:id/analytics/query         → HogQL (object-shaped rows; server-only clients must allow ≥45s)
- GET    /v1/projects/:id/event-names             → observed analytics events, operational event names, and signal-map coverage grounding
- GET    /v1/projects/:id/event-property-keys?events=a,b → observed property keys; pass events or the response is intentionally empty; status may be partial with missing_events
- POST   /v1/projects/:id/query-blueprints/:blueprint_id/run → run deterministic query template with params
- GET    /v1/projects/:id/next-steps              → state-aware suggested next workflows
- GET    /v1/projects/:id/health                  → telemetry/project heartbeat
- POST   /v1/projects/:id/webhooks                → register webhook
- GET    /v1/projects/:id/webhooks
- DELETE /v1/projects/:id/webhooks/:id
- POST   /v1/projects/:id/public-queries          → mint a publication (returns id + public_url)
- GET    /v1/projects/:id/public-queries          → list publications
- DELETE /v1/projects/:id/public-queries/:pub_id  → revoke publication
- POST   /v1/projects/:id/posthog/session-replay/configure  → set replay strategy
- GET    /v1/projects/:id/posthog/session-replay/status     → current config + web UI URL
- GET    /v1/projects/:id/feature-flags                     → list flags
- POST   /v1/projects/:id/feature-flags                     → create flag
- GET    /v1/projects/:id/feature-flags/:flag_id            → get flag
- PATCH  /v1/projects/:id/feature-flags/:flag_id            → update flag
- DELETE /v1/projects/:id/feature-flags/:flag_id            → soft-delete flag
- POST   /v1/projects/:id/feature-flags/evaluate            → evaluate flag(s) for a distinct_id
- GET    /v1/projects/:id/cohorts                           → list cohorts
- POST   /v1/projects/:id/cohorts                           → create cohort
- GET    /v1/projects/:id/cohorts/:cohort_id                → get cohort
- DELETE /v1/projects/:id/cohorts/:cohort_id                → soft-delete cohort
- GET    /v1/projects/:id/surveys                           → list surveys
- POST   /v1/projects/:id/surveys                           → create survey
- GET    /v1/projects/:id/surveys/:survey_id                → get survey
- GET    /v1/projects/:id/surveys/:survey_id/responses      → roll-up + recent free-text
- DELETE /v1/projects/:id/surveys/:survey_id                → delete survey
- POST   /v1/projects/:id/ab-tests                          → create A/B test (flag + conversion query)
- GET    /v1/projects/:id/session-replays                   → list recordings (filter by distinct_id/date)
- GET    /v1/projects/:id/session-replays/:replay_id        → recording metadata + player URL
- GET    /v1/projects/:id/session-replays/:replay_id/snapshots  → rrweb DOM events
- GET    /v1/projects/:id/users?days=30&limit=50                → top affected users
- GET    /v1/projects/:id/users/:distinct_id/summary        → composed user dossier
- GET    /v1/usage                                           → current account usage
- GET    /v1/usage/history?days=30                           → usage snapshots
- GET    /v1/billing/status                                  → current plan, quota, and upgrade action
- POST   /v1/billing/checkout                                → create Stripe Checkout session for {plan:"pro"|"scale"}
- POST   /v1/billing/portal                                  → create Stripe customer portal session
- GET    /v1/audit/recent?hours=24                          → audit log of agent mutations

### Public query read URL (GET no API key; mint/list/revoke require AGENTRY_API_KEY)

- GET /v1/public/q/:publication_id               → execute bound blueprint, return rows

### Analytics query timeout contract

`POST /v1/projects/:id/analytics/query` is backed by PostHog/HogQL. Real
queries can take 10-30 seconds, and Agentry allows up to 45 seconds upstream.
If you build a server-only admin route, cron, CI job, internal report, or internal dashboard that
calls this endpoint, set the client-side request timeout to **at least 45
seconds**. Do not use short 1-15 second `AbortSignal.timeout(...)` wrappers or
quick database fallbacks around raw analytics tabs; they abort valid Agentry
responses and create false "partial data" failures. Use public-query
publications, caching, or a smaller HogQL query when report/dashboard latency matters.
Browser-facing reports or dashboards should prefer `POST /v1/projects/:id/public-queries`
and the returned publication URL instead of direct owner-key HogQL calls.

### Optional reports and interfaces

Reports, dashboards, public-query publications, and protected product views are
post-setup output, not install proof. Build them only after a green verify report
and only when the user asks for a persistent or shareable surface.

Ground any report in the saved signal map, latest verify report, answer
contracts, event names, property keys, cases, deploys, and first query rows. If a
question is not answerable yet, label it `Needs more data` or
`Needs instrumentation` and name the exact event/property/product flow that is
missing instead of fabricating a metric.

For implementation details, use `/v1/docs/query`,
`/v1/query-blueprints`, and the filtered OpenAPI schema. Keep
`AGENTRY_API_KEY` server-only; use public-query URLs only for explicit
browser/share/embed output after the backing rows are proven readable.

### DSN-authenticated ingest

- POST /v1/logs/                → log/error ingest
- POST /v1/analytics/           → analytics event ingest
- GET /v1/analytics/browser-config → browser analytics/replay bootstrap using the same AGENTRY_DSN
- POST /v1/deploys/             → deploy ingest from CI/provider post-deploy automation
- POST /v1/sourcemaps/:project_id/   → upload .map blob from CI/build; list/fetch/delete require api-key auth

### Discovery (no auth)

- GET /                                          → service metadata
- GET /install.md                                 → install bootstrap markdown
- GET /agentry.md                                 → lean post-setup reference
- GET /v1/openapi.json                           → canonical full endpoint schema (OpenAPI 3.1)
- GET /v1/openapi.json?flow=install              → filtered install flow schema docs
- GET /v1/openapi.json?tag=Install&include_components=false → filtered operations
- GET /v1/capabilities                           → endpoint/skill/capability manifest for agents
- GET /v1/setup/manifest                         → env/setup manifest; default runtime is AGENTRY_DSN only
- GET /v1/install/contract?surface=client|server|direct-http&framework=…  → authoritative structured install contract
- GET /v1/query-blueprints                       → deterministic analytics/cases/deploy query templates
- GET /v1/query-blueprints/:blueprint_id          → query blueprint detail, params, render hint, and run path
- GET /v1/docs/query                             → HogQL primer
- GET /v1/docs/automation                        → webhook handler templates
- GET /v1/privacy/disclosure?variant=…           → policy clauses for privacy pages

CORS is enabled on DSN-authenticated browser/runtime endpoints (Access-Control-
Allow-Origin: *): logs, analytics, analytics/browser-config, and public-query
reads. Other endpoints reject browser origins — they're server/agent-side
(api-key auth).

## Tooling

There is no separate tool layer to learn. Start with the core/daily lanes from
`/v1/openapi.json?index=true`; add `include_advanced=true` only for explicit
management asks. Construct the HTTP call from the schema.

## Errors

Every error response has the same envelope:

```json
{"error": {"code": "...", "message": "...", "next_action": "...", "request_id": "...", "retryable": false}}
```

`invalid_payload` also includes schema recovery metadata in
`error.details`: `schema_path`, `schema_method`, `schema_ref`,
`request_body_schema`, `request_example`, `missing_fields`, and
`field_errors`. The correct recovery loop is: read those details (they include
the schema + an example), fix the body, retry once. Fetch a filtered
`/v1/openapi.json?path=...&method=...` if you need more.

Codes include: invalid_payload, unauthorized, invalid_api_key, invalid_dsn,
not_found, forbidden, rate_limited, quota_exceeded, payload_too_large,
posthog_capture_failed, analytics_not_configured, internal.

`quota_exceeded` is returned as HTTP 402 on advanced raw data reads such as
sourcemap blobs when the account is over its monthly event cap. Sourcemap
metadata listing stays readable with `billing_notice` so agents can verify
uploads and cleanup. Core
analytics/reporting row-query reads stay available with an over-quota
`billing_notice` so beta agents are not trapped behind an unrecoverable
billing wall or slowed by implicit Stripe Checkout creation.
Project health, usage/billing, event schema metadata, bounded case recovery
operations, and deploy reads stay available either way so agents can explain
the degraded state, record the investigation, and resolve cases without
inventing data. The response includes
`billing_notice.message_for_human` and `billing_notice.client_payload`.
Create an account-specific Stripe Checkout link only by calling
`POST /v1/billing/checkout`.
DSN-authenticated event ingest remains open while over quota so new events are
stored and available after the user upgrades.

The `next_action` and `retryable` fields are what the agent should do next —
read them; don't fall back to your own retry guess. Preserve
`x-agentry-request-id` / `idempotency-key` when retrying the same mutation.

## Where to read the source

- HTTP API: code lives in agentry-public on GitHub. Audit every transformation
  — including sourcemap unmangling — by reading apps/api/src/.

No magic. Everything that runs is text you can grep.
