---
name: adorably-claim-handlers
description: Write, test and save an Adorably claim handler, the JavaScript `handle(input)` function Adorably runs in a sandbox when a developer claims a sponsored perk, returning `{ url, cta_label? }`. Use when writing or debugging a claim handler, calling test_claim_handler or set_claim_handler on the Adorably seller MCP server, building a signed signup, trial, magic-link or promo-code link for Adorably claims, writing an `explain(input)` sponsored explanation against the sandbox's OpenRouter-compatible model API, or using `adorably.url`, `adorably.hmac`, `adorably.jwt`, `adorably.code` or `adorably.fallback`.
---

# Adorably claim handlers

A claim handler is a plain script (no modules) with a top-level `async function handle(input)`. Adorably runs it in a fresh QuickJS sandbox once per claim, with the claim, the offer and the email the developer chose to share, and opens the `url` it returns. Every failure sends the developer to the offer's plain landing URL.

## Workflow

1. Read the types first: the `adorably://claim-handler.d.ts` resource on the seller MCP server, or `references/handler-runtime.d.ts` here. It is the whole API: `HandlerInput`, `SECRETS`, the `fetch` subset, `console` and the `adorably` helpers, with their limits.
2. Pick the closest example (`adorably://claim-handler-examples`, or `references/examples.md`) and adapt it: your hosts, paths, field names and secret names. Do not start from scratch.
3. Check that every host the handler fetches or links to is on the seller's approved hosts: the landing page hosts of its offers Adorably approved, and their subdomains (`whoami` lists them). Test runs use the same hosts, so an offer on a host not yet approved (a first offer, say) gets its handler once Adorably approves it.
4. Run `test_claim_handler` with the offer id, the source, the secrets and a `test_email` (optional `claim_id`, default `clm_test`, and `fetch_mocks`). It saves nothing and returns the result with `logs` and `fetch_log`. Leaving out `source` or `secrets` runs the saved ones. There are 60 handler runs an hour per seller, shared with the test runs of `set_claim_handler`; past that both answer `rate_limited`, so read each result before running again.
5. Read `error` and `message`, then `fetch_log`, then `logs`. Fix and run again. Test the failure paths with `fetch_mocks` (`[{ "match": "https://api.example.com/v1/", "status": 500 }]`, or `delay_ms`, `network_error`, a `body` that is not JSON) instead of building failing endpoints, and make each end in `adorably.fallback(reason)`.
6. When the run returns the link you expect, save it with `set_claim_handler`: `offer_id`, `source`, `privacy_url` (the seller's privacy policy, on the offer's landing page host or another approved host), and `secrets`, `test_email` or `claim_id` if you passed them; it takes no `fetch_mocks`. It test-runs again against the real API and saves only if that passes.
7. Afterwards, `list_claims` shows each claim's handler status and `get_analytics` the failures and the last error.

## Rules that trip agents up

- Plain JavaScript only: no TypeScript syntax, no `import` or `export`, no `require`.
- No `URL`, `URLSearchParams`, `TextEncoder`, `atob`, `btoa`, `crypto`, timers, `AbortSignal`, `structuredClone` or `queueMicrotask`. Build links with `adorably.url(base, params)`; hash and sign with `adorably.sha256`, `adorably.hmac` and `adorably.jwt`; make codes with `adorably.code`; check a URL your API returns with `adorably.allowedUrl(url)` before returning it.
- `fetch`: `body` must be a string (`JSON.stringify` it); `res.headers` is a plain object (`res.headers['x-id']`, no `.get()`); `timeoutMs` gives up sooner than the per-fetch limit, and `adorably.remainingMs()` (read after a fetch) says whether another one fits.
- `input.email` is always there; `input.test` is `true` in test runs. A secret name that is not set reads as `undefined` (test runs log a warning).
- Limits per run: 2000 ms, 16 MiB, a 32 KiB source, 3 fetches of at most 1500 ms and 64 KiB each. Do not retry after `fetch timed out`.
- The returned `url` must be https on an approved host, at most 1024 characters. `cta_label` is optional plain text, at most 24 characters, not blank, no URL or domain (dotted names like `Node.js` count: write `Node`), nothing in a prohibited category (`adorably://prohibited`).
- Never put a secret in the returned URL; sign with it. Do not log secrets. Test results redact plain and URL-encoded secret values only, and anyone who can run handlers for the seller account can read the saved secrets, so redaction is not a boundary.
- Use the developer's email only for the perk and the account they asked for. Prefer a POST body over a URL for it.
- Send `input.claim_id` as an idempotency key, find-or-create by email, and treat a run with `input.test` as a test; pass a fresh `claim_id` per test run when the API caches answers by key.
- To type-check, use the `.d.ts` with only its globals: `lib: ["es2022"]`, `types: []`, `allowJs`, `checkJs`, `strict` (the guide has the tsconfig).
- `adorably.fallback(reason)` cannot be caught and ends the run; use it for every expected failure, with a reason that says what went wrong (it shows in test runs).

## Sponsored explanations

Define `explain(input)` next to `handle(input)` (or set `globalThis.explain` from a bundle) to explain, as a message from you, why your product fits the developer's stack when they press Explain. It gets `{ offer, tags, competitor, brief, test }` and returns `{ message, sources, cta_label? }` or `null`. The sandbox has an OpenRouter-compatible model API at `https://openrouter.adorably.local/api/v1` (`/chat/completions` with tools and streaming, `/models`), reachable with `fetch`, `XMLHttpRequest`, `adorably.model.complete` or a bundled OpenAI or OpenRouter client; agent harnesses run unchanged when pointed at it. No API key. Test it with `test_claim_handler` and `explain: { tags }`: it shows each model call, its cost and whether the message passes Adorably's checks. `references/guide.md` (Sponsored explanations) has the checks, the errors, the limits and how to bundle; `references/examples.md` has `explain-competitor.js` and `explain-agent-loop.js`.

## References

- `references/guide.md`: the full guide (contract, debug loop, limits, every error with its fix, security, backend verification code in Node and Python). Also `adorably://claim-handler` with live limits, and https://adorably.io/docs/claim-handlers.
- `references/handler-runtime.d.ts`: the runtime types.
- `references/examples.md`: complete, tested handlers, one per common case.
