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": every send waits for a human decision first, unless a human has switched on autopilot in the dashboard. A send with no decision expires after 72 hours and nothing is charged.
Account setup in about ten calls
A new account follows the golden path that mcp_health returns under setup.goldenPath. It lists only the steps still open for this account and gets shorter as they finish. The same route is the MCP resource frankki://golden-path, and the server's initialize instructions point to it.
brand_setup_from_website with apply:false reads the company website and returns one proposal: logo, colours, typeface, the sender profile with the mandatory details from the imprint, the sender address and a suggested letterhead. Show it to the person and ask one question. Their yes also confirms that they may use the logo and brand. Then call it again with apply:true, the proposal and brandingOwnershipConfirmed:true. That one call saves brand, sender profile, sender address and letterhead and returns one preview image. Details the person already has on file stay as they are.
template_clone_from_library copies one to three FrankKi library templates that fit the business, using the ids mcp_health suggests in its templates step. With release:true it releases them on an account with exactly one active user, and you tell the person that you did. On an account with several users a person releases them in the dashboard.
The person handles what only they can do, in the browser: the data processing agreement and the wallet top-up. Then send from the template with order_send, within the approval setup mcp_health reports. Autopilot is a switch the person sets in the dashboard, and the agent leaves it to them.
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; curl https://mcp.frankki.app/.well-known/oauth-authorization-server if you want to check it before you build against it. Claude (web, desktop, Claude Code) and ChatGPT can create the FrankKi account inside the sign-in window. 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" as the account default. An undecided send expires after 72 hours and nothing is charged. When a human has switched on autopilot in the dashboard, a live send from a released template to a recipient already in the address book, as a standard letter without attachments and within the autopilot limits, comes back already approved with a ten-minute window in which order_cancel still stops it. Read the approvalMode step of mcp_health to know which case applies; only the human can switch autopilot on. 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 or an autopilot approval has a ten-minute grace window when the 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