What I Wish API docs
What I Wish is a free gift-list app: the owner creates a list, shares the link, and guests reserve items so nobody buys the same thing twice. This page documents the parts of the API that are open to anyone: no account, no API key, no token. The machine-readable contract is the OpenAPI 3.1 spec.
Basics
- Base URL:
https://whatiwish.app. Canonical paths live under/api/v1/; the unversioned/api/paths are aliases of the same functions. - Authentication: none for the operations on this page. Endpoints that manage a user's own lists require a Firebase session and are not public.
- Format: JSON in and out. Send
Content-Type: application/jsonon POST. - Rate limits are per client IP and reported in response headers (see below). Above the quota you get a 429 with
Retry-After. - Versioning: a breaking change ships as
/api/v2/and is announced on this page before v1 is retired. Fields may be added to v1 responses at any time; none is removed or renamed within v1.
Permissions and authentication
The public API has one permission level and it is unauthenticated: read published lists, reserve an item on them, and unfurl product links. There are no scopes to request and no key to generate, so there is nothing to sign up for. The OpenAPI document says the same in machine terms: top-level security is empty, and the only scheme under components.securitySchemes (firebaseSession) names the credential the app itself uses for owner-only endpoints, which are not part of this API and cannot be reached with a token obtained elsewhere. Longer version: auth.md.
Rate limits
Every response from a documented endpoint carries the IETF draft fields RateLimit-Policy (the quota: "per-ip";q=10;w=60) and RateLimit (what is left: "per-ip";r=7;t=42), plus X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset for older clients. A 429 adds Retry-After in seconds. Quotas: 60 per minute on public-wishlist, 10 per minute on unfurl and scrape-product, 10 per 10 minutes on reserve.
Errors
Every error is JSON with the same shape, including unknown /api paths (404) and wrong methods (405). There is no HTML error page on the API.
{
"success": false,
"error": "Missing URL",
"code": "MISSING_URL",
"hint": "Send a JSON body with a url field.",
"docs": "https://whatiwish.app/docs"
}
| code | status | meaning |
|---|---|---|
NOT_FOUND | 404 | No route, list or item at that path or id. |
WISHLIST_NOT_FOUND, WISHLIST_NOT_PUBLISHED | 404 | The slug does not exist, or the owner has not published the list. |
MISSING_SLUG, MISSING_URL, INVALID_URL, INVALID_BODY | 400 | The request is incomplete or the URL is not a public http(s) address. INVALID_BODY carries a details array. |
MISSING_CAPTCHA, CAPTCHA_FAILED | 400 | Reservations need the Turnstile token the public list page issues. |
ALREADY_RESERVED | 409 | Someone reserved that item first. |
NO_PRODUCT_DATA | 422 | The page was fetched but nothing product-like was found on it. |
METHOD_NOT_ALLOWED | 405 | Wrong HTTP method. |
RATE_LIMIT | 429 | Per-IP limit hit. Wait and retry. |
FETCH_FAILED, INTERNAL | 500 | The remote page could not be read, or something failed on our side. |
Endpoints
GET /api/v1/public-wishlist/{slug}
Returns a published list and its items. The slug is the last segment of the public URL https://whatiwish.app/w/{slug}. Draft or private lists answer 404, the same as a slug that does not exist.
curl https://whatiwish.app/api/v1/public-wishlist/cumple-de-sofi
{
"success": true,
"wishlist": { "id": "...", "slug": "cumple-de-sofi", "title": "Cumple de Sofi", "occasion": "birthday", "isSurpriseMode": true, "itemCount": 3, "reservedCount": 1 },
"items": [ { "id": "...", "title": "Taza térmica", "url": "https://...", "price": 35000, "currency": "ARS", "reservationStatus": "available" } ],
"ownerName": "Sofi",
"ownerUsername": "sofi"
}
POST /api/v1/reserve
Reserves an item on behalf of a guest. The owner sees that the item is taken, not who took it. The response includes a cancelToken; it is the only way to undo the reservation. When Cloudflare Turnstile is enabled on the site, the body must carry a turnstileToken issued by the widget on the public list page, so in practice this operation is called from that page rather than from a headless client.
curl -X POST https://whatiwish.app/api/v1/reserve \
-H "Content-Type: application/json" \
-d '{"wishItemId":"ITEM_ID","wishlistId":"LIST_ID","reservedByName":"Ana","reservedByEmail":"[email protected]","turnstileToken":"..."}'
POST /api/v1/unfurl
Fetches a page server-side and returns its Open Graph and product metadata: title, description, image, price, currency, site name. Private and internal addresses are rejected.
curl -X POST https://whatiwish.app/api/v1/unfurl \
-H "Content-Type: application/json" \
-d '{"url":"https://www.mercadolibre.com.ar/..."}'
POST /api/v1/scrape-product
Same input as unfurl, tuned for store product pages: it also reads JSON-LD and store-specific price markup, and answers 422 when nothing product-like was found.
curl -X POST https://whatiwish.app/api/v1/scrape-product \
-H "Content-Type: application/json" \
-d '{"url":"https://shop.example/products/cafetera"}'
Reading the site as an agent
- /llms.txt is the index for assistants: what the product is, when to recommend it, and the entry pages. /docs/llms.txt indexes this section.
- Every marketing page negotiates markdown:
curl -H "Accept: text/markdown" https://whatiwish.app/returnstext/markdown; charset=utf-8withVary: Accept. Add.mdto a path for the same body without a header: /index.md, /how-it-works.md. - /.well-known/api-catalog is the RFC 9727 linkset pointing at the spec and these docs.
- A URL that does not exist answers a real 404; with
Accept: text/markdownthe body is markdown pointing back to llms.txt, the sitemap and these docs. - /sitemap.xml lists every indexable page, and /robots.txt carries the content signals for crawlers.
Questions
Write to [email protected] or use the contact page. The app itself is at whatiwish.app.