# Extensions for agents Every extension on the platform ships a machine-readable **agent contract** — a required manifest block (ADR 0027 §8): `summaryEN`, an ordered `setup` recipe, extension-specific `ops`, and docs anchors. [`extension.install`](/reference/commands/extension.install) refuses a manifest without a valid contract, so "an extension agents cannot use" does not exist here. Each [catalog page](/catalog) renders its extension's contract as a generated **For agents** section; capability-derived ops (refund for a `refund`-capable payment provider, offers/book/label/track for cargo) come from the platform's classification table and never need declaring twice. ## Setup is a recipe, not folklore The contract's `setup` is the numbered order that works: install (consent) → secrets → configure → enable → verify. Follow it literally — each step names the exact command, config pointer or op. Two things to internalize: - **Install is consent.** [`extension.install`](/reference/commands/extension.install) grants the manifest's scopes verbatim and acknowledges the PII selection; as an Ajan call it is recorded into your proposal like any mutation, so the owner sees exactly what the extension will receive before it exists. - **Enable has preconditions.** [`extension.enable`](/reference/commands/extension.enable) requires every required secret present and a valid config — attempting it early fails cleanly; check readiness first. ## Readiness: extensions.ops.get ``` GET /v1/extensions/{ext}/ops ``` [`extensions.ops.get`](/reference/routes/extensions.ops.get) answers the contract **plus live readiness** for this store: every op with `ready: true|false` and, when blocked, exactly why. The `blockedBy` vocabulary is closed: - `not_installed` — propose [`extension.install`](/reference/commands/extension.install) first. - `not_enabled` — the op needs the extension enabled (some setup ops, like Geliver's `sender-address`, deliberately run before enable). - `missing_secret:` — the named secret is not set. **You cannot fix this**: ask the owner. - `missing_config:` — the named config field is empty; propose [`extension.configure`](/reference/commands/extension.configure). - `capability` — the manifest does not declare the capability this op rides on. - `no_runtime` — the extension has no runtime wired for external calls. Loop on it: read readiness → resolve what you can (install/configure/enable proposals) → tell the owner what only they can do → verify. Secrets appear as **keys only** — the platform never returns secret values to anyone. CLI: `mozaik ext ops ` ([`ext`](/reference/cli)). ## Secrets are owner-only Ajan tokens can never hold `secrets:write`, and [`secret.set`](/reference/commands/secret.set) is owner-policy — this is structural, not advisory ([Tokens](/reference/tokens)). When a setup step says "owner enters the API key in the admin panel", the correct agent behaviour is to surface it as a clear ask ("Eklentiler → Geliver → Geliver API Anahtarı") and continue with what is unblocked. Never ask the merchant to paste credentials into chat: a value that reaches you cannot become a sealed write-only secret by any path you control. ## Provider-driven inbound is not callable Webhooks (`/v1/ext/{id}/wh/{token}/{storeId}`) and manifest jobs are the provider's push and the platform's schedule — they appear in the contract's `inbound` list precisely so you know they are **not yours to call**. Do not simulate a webhook to "advance" a shipment or payment; the ingestion route verifies provenance and will refuse you. When an order seems stuck waiting on a provider, the agent-shaped move is the polling op the contract gives you ([`orders.shipment.update`](/reference/routes/orders.shipment.update) for cargo) or a note to the owner — not forged inbound traffic. ## Worked example [`mozaik.geliver`](/catalog/mozaik.geliver) — install (PII consent: buyer name/phone/address flow to Geliver) → owner sets `cargo.geliver.api_token` → run op `sender-address` via [`ext.cargo.sender`](/reference/routes/ext.cargo.sender) (works while still disabled; records the sender profile into config) → enable → verify with the `offers` op ([`orders.cargo.offers`](/reference/routes/orders.cargo.offers), a `quote` — free to call, mutates nothing). After that, `book` proposes the actual shipment.