Canonical setup for AI agents
Connect an agent to FrankKi safely
Start with a scoped production token and dryRun: it validates the whole path, prints nothing and charges nothing. Sandbox mode is a per-account grant that is off by default and currently unavailable. Validate the address, inspect the rendered letter, quote the exact cost, and require a human decision before physical mail can leave.
First connection check: production token, dryRun, nothing mailed
Sign up at https://frankki.app/dashboard/registrieren, then create a token at https://frankki.app/dashboard/tokens with only the scopes your integration needs. The raw token is shown once. Connect it to https://mcp.frankki.app and send it as Authorization: Bearer <FRANKKI_API_KEY>. This is a production token: treat every call as production unless you set dryRun.
Call mcp_health first and read what it answers instead of assuming it. It reports capabilities.sandbox and a setup step keyed sandbox. On a production probe run on 2026-08-13 (requestId req_SF83SO42MGXYYERT) that step returned status "unavailable" with details {"allowed": false, "tokenIsTest": false}: sandbox mode is a per-account grant, it is off by default, and it is currently unavailable. Do not build your first connection check on it. The documented substitute is dryRun.
Walk the whole path without dispatching anything: address_validate, letter_create_draft, letter_preview on the returned letterId, shipping_quote for that preview, then order_send with dryRun set to true. Carry the quote's priceVersion and quotedUnitPriceCents into the send, and set maxCostEuros to the ceiling a human agreed to. Read every price from shipping_quote: prices are server-authoritative and this page states none. A dryRun send runs the same validation, the same sender-profile and data-processing gates and the same pricing as a live send, and returns the same error codes. Nothing is printed, nothing is mailed, and nothing is charged.
Two guards confirmed against production on 2026-08-13 apply the moment you drop dryRun. order_send aborts with PRICE_CHANGED when the priceVersion no longer matches the current price, and aborts above the maxCostEuros ceiling rather than charging past it. The same probe reported the approval step as mode "review" with a ten-minute window: every send waits for a human decision first, and with no decision it expires after ten minutes and nothing is charged.
Authentication: interactive or headless
Interactive clients use OAuth 2.1 with PKCE (S256) and dynamic client registration (RFC 7591) at the remote MCP endpoint https://mcp.frankki.app. That surface is live in production, verified 2026-08-13 against https://mcp.frankki.app/.well-known/oauth-authorization-server, which is the URL to curl if you want to check it before you build against it. If your client cannot open a browser for authorization, use an explicitly scoped API key instead of claiming OAuth succeeded.
Headless agents use a bearer API key issued at https://frankki.app/dashboard/tokens. Store it in a secret manager and send Authorization: Bearer <FRANKKI_API_KEY>. Grant only required scopes and set a conservative daily spending cap. Never place a key in a prompt, repository, or log.
Safe send workflow
The required order is address_validate, letter_create_draft, letter_preview, shipping_quote, dry-run order_send, explicit human approval, live order_send, approval_decide when queued, then order_status. Use approvalMode:"review" on a live send when human review is required; a production probe on 2026-08-13 reported "review" with a ten-minute expiry as the account default, after which an undecided send lapses and nothing is charged. A dryRun send exercises validation, gates and pricing but does not enqueue an approval, so the first live send is where you see the approval queue.
Set maxCostEuros on every order_send. Echo priceVersion and quotedUnitPriceCents from shipping_quote when available so a changed price fails with PRICE_CHANGED before any debit. Use deterministic clientLetterId and clientOrderId values. Replays use the same key and payload; IDEMPOTENCY_CONFLICT means the key was reused for different content.
Cancellation and irreversible states
A chat approval has a ten-minute grace window when the approval result returns dispatchAt and cancellable:true. Call order_cancel immediately when the human says stop. Cancellation is never guaranteed: order_status is authoritative, and order_cancel refuses after the print cutoff.
Once printing starts, the physical letter cannot be recalled. A successful pre-print cancellation credits the closed-loop wallet rather than refunding a payment card. Never describe a submitted, approved, or processing letter as cancelled until the tool confirms it.
Complete safe-send prompt
Give this policy to an agent as a system or workflow instruction. Replace no safety step with model judgment.
Use FrankKi conservatively.
1. Start with mcp_health and read the setup steps it returns instead of assuming them. The step keyed sandbox reports whether sandbox mode is granted for this account; it is off by default. Rehearse with dryRun:true, not with a sandbox you have not been granted. Never turn a failed capability check into a live send.
2. Call address_validate with the complete recipient address. Stop on ADDRESS_INVALID or COUNTRY_NOT_SUPPORTED. Never guess or silently repair an address.
3. Create the draft with letter_create_draft and a stable clientLetterId. Inspect its rendered pages, then call letter_preview for the final letterId.
4. Call shipping_quote with the preview's page count, color, destination, and delivery type. Treat the returned price and priceVersion as authoritative.
5. Show the human the recipient, subject, rendered preview, delivery type, and exact total. Ask for a maximum cost in euros. Do not infer approval from the original request.
6. Rehearse order_send with dryRun:true, approvalMode:"review", maxCostEuros set to the human's cap, the quote's priceVersion and quotedUnitPriceCents, and a stable clientOrderId. Resolve every returned error before continuing.
7. Ask: "Approve this exact preview for [exact price] via [delivery type], with a maximum cost of [maxCostEuros]?" Continue only after an explicit yes.
8. Call order_send again with the identical payload and clientOrderId, changing only dryRun to false. A retry must reuse both the same key and the same payload. A new intent needs a new key.
9. If an approvalId is returned, show the frozen preview, exact price, and delivery type again. Call approval_decide with decision:"approve" and user_saw_preview:true only after the human explicitly approves. Rejection is always allowed.
10. Report approvalId or orderId, status, dispatchAt, and cancellable exactly as returned. If the human says stop, call order_cancel immediately. Never promise cancellation: once printing starts, physical mail cannot be recalled.References
- Service status
- Trust and compliance
- Machine-readable errors
- Pricing
- Complete developer docs
- Tool reference
- Authentication reference
Machine-readable twin: agent-onboarding.md