# Typed routes Everything that is not a command is a catalog route (`ROUTE_CATALOG`, ADR 0027 §5): reads, provider-backed ops, draft workspace writes, owner-only actions. The catalog IS the exceptions to the command bus — the API router serves exactly these entries plus the excluded platform prefixes below, and **every non-owner entry is an MCP tool** under its dotted id. `GET /v1/routes` serves this list with JSON Schemas (the twin of `GET /v1/commands`). ## Agent policy vocabulary | policy | meaning | |---|---| | `read` | GET, no store effects — every token kind calls it directly. | | `quote` | a provider call with no store-state change (offers, labels, validators) — every token kind calls it directly. | | `workspace` | writes confined to the caller's own draft workspace or staging — every token kind executes it directly. | | `propose` | a live mutation — an Ajan token's call is recorded into its open proposal (202 `proposed: true`); a Geliştirici token executes it directly. | | `owner` | owner sessions only — never callable by any token and never an MCP tool. | ## Catalog | route | method + path | policy | summary | |---|---|---|---| | [`meta.get`](/reference/routes/meta.get) | `GET /v1/meta` | `read` | Platform build identity and compatibility fingerprint (buildId, registryHash, minCli). | | [`commands.list`](/reference/routes/commands.list) | `GET /v1/commands` | `read` | List commands with JSON Schemas; anonymous callers see the frozen core set, authenticated callers the scope-filtered registry. | | [`commands.run`](/reference/routes/commands.run) | `POST /v1/commands` | `propose` | Run one command envelope {command, storeId, payload, idempotencyKey\|dryRun}. | | [`routes.list`](/reference/routes/routes.list) | `GET /v1/routes` | `read` | List typed routes with JSON Schemas (the twin of commands.list). | | [`sections.meta.get`](/reference/routes/sections.meta.get) | `GET /v1/sections/meta` | `read` | Section catalog with props/blocks JSON Schemas and placement rules. | | [`store.health.get`](/reference/routes/store.health.get) | `GET /v1/store/health` | `read` | Store readiness checklist (draft/live, identity, flags). | | [`store.identity.get`](/reference/routes/store.identity.get) | `GET /v1/store/identity` | `read` | Legal identity block (owner-only). | | [`store.checkoutSettings.get`](/reference/routes/store.checkoutSettings.get) | `GET /v1/store/checkout-settings` | `read` | Checkout settings (owner-only). | | [`extensions.list`](/reference/routes/extensions.list) | `GET /v1/extensions` | `read` | Installed + available extensions with config, secrets state and manifest UI hints (owner-only). | | [`extensions.ops.get`](/reference/routes/extensions.ops.get) | `GET /v1/extensions/{ext}/ops` | `read` | The extension's agent contract with LIVE readiness: every op, what it needs (secrets/config/enabled), and exactly what blocks it right now. Secrets appear as keys only. | | [`metafields.definitions.list`](/reference/routes/metafields.definitions.list) | `GET /v1/metafields/definitions` | `read` | Metafield definitions (owner-only). | | [`catalog.products.list`](/reference/routes/catalog.products.list) | `GET /v1/catalog/products` | `read` | List products (keyset cursor, filters). | | [`catalog.products.get`](/reference/routes/catalog.products.get) | `GET /v1/catalog/products/{prd}` | `read` | Get one product with variants, media and stock. | | [`catalog.stockMovements.list`](/reference/routes/catalog.stockMovements.list) | `GET /v1/catalog/stock-movements` | `read` | Stock ledger rows for a product or variant. | | [`catalog.collections.list`](/reference/routes/catalog.collections.list) | `GET /v1/catalog/collections` | `read` | List collections. | | [`catalog.collections.get`](/reference/routes/catalog.collections.get) | `GET /v1/catalog/collections/{col}` | `read` | Get one collection with members. | | [`catalog.categories.list`](/reference/routes/catalog.categories.list) | `GET /v1/catalog/categories` | `read` | List categories. | | [`media.list`](/reference/routes/media.list) | `GET /v1/media` | `read` | List media objects. | | [`media.upload`](/reference/routes/media.upload) | `POST /v1/media/upload` | `workspace` | Stage a binary upload (raw body); commit with catalog.media.commit. | | [`orders.list`](/reference/routes/orders.list) | `GET /v1/orders` | `read` | List orders. | | [`orders.get`](/reference/routes/orders.get) | `GET /v1/orders/{ord}` | `read` | Get one order with lines, payment, shipments, returns, timeline. | | [`orders.belge.get`](/reference/routes/orders.belge.get) | `GET /v1/orders/{ord}/belge/{belge}` | `read` | Fetch a legal document snapshot of an order. | | [`orders.note.add`](/reference/routes/orders.note.add) | `POST /v1/orders/{ord}/not` | `propose` | Add a merchant note to the order timeline. | | [`orders.cod.collect`](/reference/routes/orders.cod.collect) | `POST /v1/orders/{ord}/tahsilat` | `propose` | Mark a cash-on-delivery order as collected. | | [`orders.refund`](/reference/routes/orders.refund) | `POST /v1/orders/{ord}/refund` | `propose` | Refund part or all of a captured payment through the original PSP extension. | | [`orders.cargo.offers`](/reference/routes/orders.cargo.offers) | `POST /v1/orders/{ord}/cargo/offers` | `quote` | Create a provider quote for the order and return carrier offers (no store mutation). | | [`orders.cargo.book`](/reference/routes/orders.cargo.book) | `POST /v1/orders/{ord}/cargo/book` | `propose` | Accept a carrier offer, create the label and ship the order. | | [`orders.cargo.ship`](/reference/routes/orders.cargo.ship) | `POST /v1/orders/{ord}/cargo/ship` | `propose` | Ship with the merchant's own carrier (manual tracking). | | [`orders.shipment.track`](/reference/routes/orders.shipment.track) | `POST /v1/orders/{ord}/cargo/{shp}/track` | `propose` | Record a shipment status transition by hand. | | [`orders.shipment.update`](/reference/routes/orders.shipment.update) | `POST /v1/orders/{ord}/cargo/{shp}/update` | `propose` | Poll the provider and apply the current tracking status. | | [`orders.shipment.cancel`](/reference/routes/orders.shipment.cancel) | `POST /v1/orders/{ord}/cargo/{shp}/cancel` | `propose` | Cancel a shipment before pickup. | | [`orders.shipment.label`](/reference/routes/orders.shipment.label) | `POST /v1/orders/{ord}/cargo/{shp}/label` | `quote` | Fetch the shipping label URL from the provider. | | [`cargo.carriers.list`](/reference/routes/cargo.carriers.list) | `GET /v1/cargo/carriers` | `read` | Platform carrier reference table (codes, labels, tracking availability). | | [`returns.list`](/reference/routes/returns.list) | `GET /v1/returns` | `read` | List return requests. | | [`returns.get`](/reference/routes/returns.get) | `GET /v1/returns/{ret}` | `read` | Get one return request. | | [`returns.approve`](/reference/routes/returns.approve) | `POST /v1/returns/{ret}/onayla` | `propose` | Approve a return request with shipping instructions; mails the shopper. | | [`returns.reject`](/reference/routes/returns.reject) | `POST /v1/returns/{ret}/reddet` | `propose` | Reject a return request with a reason; mails the shopper. | | [`ext.cargo.sender`](/reference/routes/ext.cargo.sender) | `POST /v1/ext/{ext}/cargo/sender` | `propose` | Create the sender address at the cargo provider and store its id in the extension config. | | [`design.draft.get`](/reference/routes/design.draft.get) | `GET /v1/design/draft` | `read` | Read the draft bundle (rev, hash, body, preview link). | | [`design.draft.put`](/reference/routes/design.draft.put) | `PUT /v1/design/draft` | `workspace` | Replace the draft bundle under optimistic concurrency (expectedRev). | | [`design.draft.delete`](/reference/routes/design.draft.delete) | `DELETE /v1/design/draft` | `workspace` | Discard the draft (expectedRev). | | [`design.draftFromTemplate`](/reference/routes/design.draftFromTemplate) | `POST /v1/design/draft-from-template` | `workspace` | Instantiate a starter template as the draft. | | [`design.templates.list`](/reference/routes/design.templates.list) | `GET /v1/design/templates` | `read` | Starter template cards. | | [`design.versions.list`](/reference/routes/design.versions.list) | `GET /v1/design/versions` | `read` | Published version ledger (seq, hash, label, live marker). | | [`design.version.get`](/reference/routes/design.version.get) | `GET /v1/design/version/{seq}` | `read` | Full body of a published version. | | [`design.previewUrl.get`](/reference/routes/design.previewUrl.get) | `GET /v1/design/preview-url` | `read` | Signed preview link: ?hash= freezes a snapshot, ?workspace= follows that workspace's current draft (the dev-loop link). | | [`proposals.list`](/reference/routes/proposals.list) | `GET /v1/proposals` | `read` | List proposals; agent tokens see only their own. | | [`proposals.get`](/reference/routes/proposals.get) | `GET /v1/proposals/{prp}` | `read` | One proposal with its items, recorded previews and (for design items) a preview link. | | [`proposals.apply`](/reference/routes/proposals.apply) | `POST /v1/proposals/{prp}/onayla` | `owner` | Approve a proposal and apply its items in order, re-checking each recorded preview (store owner only). | | [`proposals.resume`](/reference/routes/proposals.resume) | `POST /v1/proposals/{prp}/devam` | `owner` | Resume a paused proposal after skipping or accepting the new preview of a stale item (store owner only). | | [`workspaces.list`](/reference/routes/workspaces.list) | `GET /v1/workspaces` | `read` | List draft workspaces: the builder's main draft plus every agent/developer workspace with its draft rev and sandbox state. | | [`workspaces.create`](/reference/routes/workspaces.create) | `POST /v1/workspaces` | `owner` | Create a workspace; for kind agent it also mints the bound Ajan token (returned once). | | [`workspaces.adopt`](/reference/routes/workspaces.adopt) | `POST /v1/workspaces/{ws}/adopt` | `workspace` | Copy a workspace draft into the builder's main draft under optimistic concurrency (never a merge). | | [`workspaces.exec`](/reference/routes/workspaces.exec) | `POST /v1/workspaces/{ws}/exec` | `workspace` | Run one command in the workspace's hosted sandbox (the pinned CLI is preinstalled; cwd defaults to /workspace/store). Output capped at 256 KB. | | [`workspaces.files.list`](/reference/routes/workspaces.files.list) | `GET /v1/workspaces/{ws}/files` | `quote` | List a directory inside the hosted sandbox (confined to /workspace). | | [`workspaces.files.read`](/reference/routes/workspaces.files.read) | `GET /v1/workspaces/{ws}/file` | `quote` | Read a file from the hosted sandbox (≤1 MiB; base64 for binary). | | [`workspaces.files.write`](/reference/routes/workspaces.files.write) | `PUT /v1/workspaces/{ws}/file` | `workspace` | Write a file into the hosted sandbox (≤1 MiB; scratch only — store state flows through mozaik push/proposals). | | [`workspaces.backup`](/reference/routes/workspaces.backup) | `POST /v1/workspaces/{ws}/backup` | `workspace` | Snapshot the sandbox's /workspace/store to R2 (7-day TTL); restored automatically on the next cold start. | | [`workspaces.preview`](/reference/routes/workspaces.preview) | `GET /v1/workspaces/{ws}/preview-url` | `read` | Stable signed preview URL of this workspace's draft on the store hostname (never touches the container). | | [`workspaces.activity.list`](/reference/routes/workspaces.activity.list) | `GET /v1/workspaces/{ws}/activity` | `read` | The workspace's append-only activity feed (exec/read/write/backup/refresh with exit codes and durations). | | [`workspaces.refresh`](/reference/routes/workspaces.refresh) | `POST /v1/workspaces/{ws}/refresh` | `owner` | Rebuild the hosted sandbox on the current platform image: backup, destroy, revoke its token; the next use recreates it fresh. | | [`scopes.list`](/reference/routes/scopes.list) | `GET /v1/scopes` | `read` | Scope registry: TR labels, sensitivity and who may hold each scope (any token / developer tokens / sessions only). | | [`tokens.mint`](/reference/routes/tokens.mint) | `POST /v1/tokens` | `owner` | Mint an Ajan (propose-only) or Geliştirici (direct) token; the plaintext is returned exactly once. | | [`tokens.list`](/reference/routes/tokens.list) | `GET /v1/tokens` | `owner` | List this store's tokens (never the secrets): kind, scopes, expiry, last use. | | [`tokens.whoami`](/reference/routes/tokens.whoami) | `GET /v1/tokens/me` | `read` | Describe the calling credential: store, kind, scopes, expiry, workspace — what an agent should call first. | | [`audit.list`](/reference/routes/audit.list) | `GET /v1/audit` | `read` | Recent audit rows for the store (optionally by entity). | | [`validate.bundle`](/reference/routes/validate.bundle) | `POST /v1/validate/bundle` | `quote` | Validate a store bundle (schema + referential integrity) without saving anything. Media readiness is checked at draft save, which is the enforcement point. | | [`validate.command`](/reference/routes/validate.command) | `POST /v1/validate/command` | `quote` | Validate a command payload against its registered schema without running it (no preview, no mutation). | | [`validate.manifest`](/reference/routes/validate.manifest) | `POST /v1/validate/manifest` | `quote` | Validate an extension manifest against zExtensionManifest, including the agent-contract rules. | ## Excluded prefixes Deliberately NOT store-management capabilities — never in the catalog, never agent tools. A `/v1/…` literal in the router that is neither a catalog path nor one of these fails CI (G-ROUTES). | prefix | why | |---|---| | `/v1/health` | liveness probe | | `/v1/auth/` | merchant login/session — platform side (ADR 0015), not store management | | `/v1/account/` | apex account panel — session-only by ADR 0015; store creation is the /kur wizard | | `/v1/id/` | Mozaik ID shopper identity (ADR 0025) — shopper-side | | `/v1/ext/{ext}/wh/` | provider-driven webhook ingestion (ADR 0026 §5) — agents never call it | | `/v1/psp/` | legacy iyzico webhook alias — pending the user's dashboard re-registration | | `/v1/cargo/geliver/webhook` | legacy Geliver webhook alias — pending deletion with the above | | `checkout.internal` | storefront→api service-binding enclave (ADR 0021) |