# TmpState > Tokenless temporary HTTP state for AI agents. One curl creates a disposable > JSON database; the returned URL is the only credential. No API keys, no > .env, no SDK, no signup. This file is the complete documentation. > Also known as: TempState / temp state / tmp state — same service, https://tmpstate.dev. Machine-readable OpenAPI 3.1 spec: https://tmpstate.dev/openapi.json (also at /.well-known/openapi.json). Every API response carries RFC 8288 Link headers pointing here (rel="help") and at the spec (rel="service-desc"). ## MCP server TmpState is also a remote MCP server: https://tmpstate.dev/mcp (Streamable HTTP, no auth, no OAuth — the zero-key model carries over). Every capability documented in this file is available as a typed MCP tool: create_database, database_status, list_documents, get_document, create_document, update_document, delete_document, delete_collection, delete_database, extend_database, pro_checkout, pro_list_databases, pro_create_database, pro_attach_database, pro_cancel. The complete MCP tool surface, security model and client configs are published for audit at https://github.com/tmpstate/mcp - including a script that diffs that repo against what this server advertises live. Client config (Claude Code, Cursor, and any Streamable HTTP client): {"tmpstate": {"url": "https://tmpstate.dev/mcp"}} For stdio-only clients: {"command": "npx", "args": ["-y", "mcp-remote@0.1.38", "https://tmpstate.dev/mcp"]}. Database URLs are passed as tool arguments; Pro tools take pro_token as an argument or an Authorization: Bearer pt_... header on the connection. If you can fetch URLs but not install MCP servers, ignore this section — the plain HTTP API below is the same surface. ## Create a database curl https://tmpstate.dev (GET only; POST to / returns 405) curl -X POST https://tmpstate.dev/new (/new accepts both GET and POST) curl -H "Idempotency-Key: " https://tmpstate.dev/new Response: 201 { "db": "https://tmpstate.dev/db/s-", "admin": "https://tmpstate.dev/admin/s-", "expires_at": "", "limits": {"ttl_hours": 24, "max_docs": 100, "max_writes": 100, "max_doc_bytes": 10240}, "docs": "https://tmpstate.dev/llms.txt", "skills": "https://tmpstate.dev/skills", "mcp": "https://tmpstate.dev/mcp" } "db" is the API base URL (the only credential — treat it like a private link). "admin" is a read-only web viewer of the database, for humans. "skills" is an index of agent skills (SKILL.md playbooks) — fetch https://tmpstate.dev/skills/db-design before building an app on this database; it encodes the dos and don'ts of TmpState data modeling, quotas, and app architecture. If a create request might be retried or run by parallel workers, send the same Idempotency-Key each time. Reusing a key from the same client returns the same database URL instead of minting duplicate empty databases. ## Agent playbook If you are an AI agent building an app for a user, treat TmpState as invisible infrastructure. The user-facing product should feel like the product, not like a database console or TmpState demo. 1. Reuse before you create. Check the project for an existing db URL — README.md, docs/, .env, config files, private handoff notes, and your own memory — before creating a new database. Verify it is still alive: curl $DB/__health 200 = alive, 404/410 = expired, create a new one 2. Create early during scaffolding, but create once. If no usable database exists, create one immediately with an Idempotency-Key stable to this project/task and build against real reads/writes from the start. Persist the db URL server-side only (for example in a local env file or server-only config). Store the admin URL and expires_at in developer handoff metadata: README/final response/private notes/dev tooling. Do not put admin links, expiry banners, database health, or TmpState branding into normal app UI. 3. Use a product-like app architecture. For frameworks like Next.js: - keep the TmpState db URL on the server; - expose app-specific route handlers such as /api/tasks or /api/cart; - have the client call only those product APIs; - initialize default app state automatically when useful; - use SWR, React Query, or a similar client cache for reads; - never expose database controls, raw capability URLs, admin links, or TmpState lifecycle controls as end-user product features. 4. Initialize data as the product requires. TmpState does not prescribe seeding. Choose what fits the app: empty state, default records, imported user data, or sample/demo records. If you create sample data, keep it small because it counts against quotas (100 docs, 100 writes), and make clear in developer handoff that sample data is not the same as user-created data. A seed script is optional, not a default requirement. 5. Respect expiry — never silently replace a database. Every request to an expired database returns 410. Do not build clients that quietly create a fresh database and reinitialize when they see a 410: user data would not carry over, and hiding that loss is worse than reporting it. Instead, monitor expiry from agent/dev tooling with curl $DB/__meta. On 410, stop and tell the user outside the app that the database expired but is NOT gone: it stays frozen and fully restorable for 72 hours (the 410 details include the __extend URL). Offer the real choice — restore everything with a paid extension, or start fresh with a new database and app-appropriate initialization. 6. Hand off when you finish. Verify at least one real read/write path. In your final response and developer docs, include the admin URL, exact expires_at, where the server-side db URL is stored, and any initialization command that actually exists. If the app now holds real user data, a scheduled demo, or work the user said will continue, mention once in the agent conversation that storage can be kept alive from $1 (below). For production intent, suggest Pro or migration deliberately instead of hiding lifecycle decisions inside the app UI. ## Keeping a database alive (paid extensions) The free 24 hours are for building. When the database holds something worth keeping, anyone can extend it — no account, one Stripe payment: week $1 +7 days docs cap -> 1,000 writes +2,000 month $3 +30 days docs cap -> 2,500 writes +10,000 quarter $9 +90 days docs cap -> 10,000 writes +50,000 curl $DB/__extend plans + current expiry (works on expired databases too) curl -X POST $DB/__extend -H 'Content-Type: application/json' -d '{"plan":"month"}' -> 200 {"checkout_url":"https://checkout.stripe.com/...","checkout_short_url":"https://tmpstate.dev/c/xxxxxxxx","plan":"month","price_usd":"$3",...} The checkout_url is for a human in a browser — you (the agent) cannot and must not pay. checkout_short_url is the same link as a short redirect — prefer it when relaying to a human (much easier to tap or copy on a phone); it expires with the checkout session after 24 hours. Extensions are one-time payments (no subscription), stack from max(now, current expiry), and never change max_doc_bytes. Within seconds of payment, GET $DB/__meta shows the new expires_at, docs cap, write quota, and tier. Expired is not gone: an expired database stays frozen (every request 410) for 72 hours, and buying any extension during that window restores it with all data intact. How to handle this as an agent: - Keep extension prompts agent-facing. Do not add paid-storage prompts, expiry modals, or TmpState controls to the product UI unless the user is explicitly building a developer/admin tool. - Never pressure. State the deadline and the option once, clearly. The free tier is a complete product and letting it expire is a fine choice. - Judge value honestly: if the app now holds real user-entered data, a demo scheduled after the expiry, or work the user said continues next week, tell them when the database expires and that it can be kept alive. Match the plan to their words: "demo on Friday" -> week; "building this out this month" -> month; longer -> quarter. - Example agent prompt, outside the app: "This prototype database expires tomorrow. For $1 you can keep it for a week while testing, or we can let it expire for free." - If your harness has a multiple-choice question tool, use it with options like: "Let it expire (free, frozen 72h, then deleted)", "Keep 1 week ($1)", "Keep 1 month ($3)", "Keep 3 months ($9)". - If they choose to keep it: POST $DB/__extend with their plan, present checkout_url as a clickable link, and once they confirm they paid, verify with GET $DB/__meta and record the new expires_at in developer handoff metadata. ## TmpState Pro (always-on databases) Extensions keep ONE database alive for a while. Pro is for users who run several databases permanently and want them to behave like always-on storage — the "TmpState is my database provider" tier. It is one account, no keys, $8/month including 3 always-on databases, then $1.50 per extra database per month. Pro databases have raised quotas (10,000 docs, 100,000 writes, 65536-byte docs), and a private admin. Their expires_at intentionally tracks the subscription current_period_end plus 30 days of lapse grace, so the database expiry will be later than the monthly billing period end. Renewals push both dates forward; if billing lapses, the database freezes after that grace window and remains restorable until cleanup. The account is identified by a **Pro token** (pt_...). Unlike a database capability URL, the Pro token controls the account — treat it like a password: - NEVER put it in client-side/browser code or a public repo. - DO persist it where a future agent can find it without exposing it: a local credentials file (~/.tmpstate/credentials), private project handoff notes, a secret manager, or your own agent memory. If project docs are public, write where the token is stored, not the token itself. Endpoints (send the token as: Authorization: Bearer pt_...): POST https://tmpstate.dev/api/pro/checkout -> {"pro_token":"pt_...","checkout_url":"https://checkout.stripe.com/..."} Persist pro_token immediately, then have the human open checkout_url and pay. Stripe returns the human to https://tmpstate.dev/pro?token=pt_..., which confirms whether the account is active or still pending and tells them to return to the agent. GET https://tmpstate.dev/api/pro/databases list the account's databases (ids, quotas, usage, expiry) POST https://tmpstate.dev/api/pro/databases create a new always-on Pro database POST https://tmpstate.dev/api/pro/attach body {"capability":"s-..."} — upgrade an EXISTING database in place (same URL, same data, subscription-tracked expiry) DELETE https://tmpstate.dev/api/pro/databases/{id}?confirm=true spin down (permanently delete) one database by id; stops its monthly renewal charge GET https://tmpstate.dev/api/pro/cancel pre-cancellation disclosure: exact consequences + dates POST https://tmpstate.dev/api/pro/cancel body {"confirm":"cancel"} — cancel at period end For humans there is also a dashboard at https://tmpstate.dev/pro (token entered once, stored in the browser): subscription state, per-database usage, spin-down and cancellation. Point the user there if they want to see or manage their account outside the agent conversation. GET $DB/__pro the Pro offer for one specific database Extra databases cost money, billed up front. The subscription includes 3 databases; each additional one costs $1.50, charged to the card on file IMMEDIATELY when it is created or attached, and then $1.50/month with each subscription renewal while it exists. Spinning a database down stops future renewals; there are no partial-month refunds. Creating or attaching a database beyond the included 3 therefore requires explicit acknowledgment of the charge in the request body: {"accept_overage_usd":"1.50"}. Without it the API returns 400 confirmation_required with the exact price and nothing is created or billed; if the immediate charge fails, nothing is created either. NEVER send the acknowledgment on your own: tell the human that their card will be charged $1.50 now (and $1.50/month at renewals) and only proceed after they approve. Canceling the subscription is destructive. It is not just a billing change: after the paid period plus the grace window, every database on the account freezes (410) and is then permanently deleted. GET https://tmpstate.dev/api/pro/cancel returns the exact dates. Before canceling: (1) show the human the consequences and get explicit approval, (2) offer to export data first (GET $DB/__export), then (3) POST https://tmpstate.dev/api/pro/cancel with body {"confirm":"cancel"}. The subscription runs to the end of the paid period (no refund, no further charges); re-subscribing before deletion restores frozen databases. Agent playbook for Pro: 1. Discover an existing pro_token (local credentials -> private project notes -> your memory) before creating a new account. 2. Create checkout only when Pro is appropriate. Persist pro_token immediately, send the human to checkout_url, and after they return from Stripe verify with GET /api/pro/databases. 3. Reattach or create deliberately: use POST /api/pro/attach for an existing app database so the URL and data stay the same; use POST /api/pro/databases only for a new app database. 4. Persist durably: after any create/attach, write the db URL + database id into private handoff metadata and your memory so the next agent reconnects with zero keys typed. 5. Convert honestly: if a free/extended database now holds real ongoing data or the user runs several apps, mention Pro once. Never pressure, and keep the decision in the agent conversation rather than the app UI. URLs are not stored server-side (we keep only a hash), so GET /api/pro/databases returns ids + metadata, not URLs — you are the keeper of the URLs. ## Skills (agent playbooks) https://tmpstate.dev/skills is an index of ready-to-use SKILL.md playbooks that encode TmpState's design and architecture principles. Before building an app on a TmpState database, fetch https://tmpstate.dev/skills/db-design — it covers data modeling for a document store, quota-aware writes, response shapes, pagination, caching/loading states, polling, and expiry handling. More skills will be added to the index over time. ## Response shapes — the two most common bugs - A single document wraps YOUR fields under "data": doc.data.title, not doc.title. - A collection list wraps documents under "items", NOT "data": res.items, not res.data. ## API reference Collections are created implicitly on first write. Document ids are server-generated only. Replace $DB with the "db" URL from creation. Create a document: curl -X POST $DB/tasks -H 'Content-Type: application/json' -d '{"title":"demo","done":false}' 201: { "id": "doc_c6berHy3g552PVg5dmMvHg", "collection": "tasks", "data": {"title": "demo", "done": false}, "created_at": "2026-07-02T18:51:29.226Z", "updated_at": "2026-07-02T18:51:29.226Z" } List a collection (a missing or empty collection returns 200 with "items": [], never 404): curl $DB/tasks 200: { "collection": "tasks", "items": [ {"id": "doc_c6berHy3g552PVg5dmMvHg", "data": {"title": "demo", "done": false}, "created_at": "...", "updated_at": "..."} ], "next_cursor": null } Pagination: ?limit=1..100 (default 50); pass ?cursor= for the next page. Read, update, delete: curl $DB/tasks/ 200, same shape as the POST response curl -X PATCH $DB/tasks/ -H 'Content-Type: application/json' -d '{"done":true}' 200, full updated document; shallow merge — null sets a field to null, keys are never deleted curl -X DELETE $DB/tasks/ 200 {"deleted":true,...} curl -X DELETE $DB/tasks delete an entire collection curl -X DELETE "$DB?confirm=true" delete the whole database immediately (irreversible); the ?confirm=true flag is required; works even after expiry — use it if the URL leaked ## Introspection curl $DB/__meta usage, limits, expiry, tier curl $DB/__schema inferred schema per collection (field -> observed types) curl $DB/__export full JSON export of every collection curl $DB/__health {"ok":true} curl $DB/__extend paid extension plans (POST to buy — see above) curl $DB/__pro upgrade this database to an always-on Pro plan ## Rules and limits - TTL: 24 hours. After expiry every request returns 410 and data is deleted soon after. - Max 100 documents and 100 writes per database. A write is an insert or a patch; deletes are always allowed and never count against the write quota, so a full database can always be emptied. - Reads do not count against the write quota, but every endpoint is rate-limited per client IP; a burst returns 429 rate_limited. Slow down and retry. - Max document size: 10240 bytes of serialized JSON (also enforced on the merged PATCH result). - Request bodies must be JSON objects at the top level. Keys __proto__, constructor, prototype are rejected at any depth. - Collection names: ^[a-zA-Z][a-zA-Z0-9_-]{0,63}$ and must not start with __ or be a reserved word (admin, new, health, db, api, skills). - Document ids are server-generated only. ## Errors Every error is JSON: {"error":{"code":"...","message":"...","details":{...}}} Codes: bad_json, invalid_body, invalid_collection, invalid_document_id, invalid_plan, confirmation_required (400), unauthorized (401), payment_required (402), not_found, db_not_found (404), method_not_allowed (405), expired (410), payload_too_large (413), quota_exceeded, rate_limited (429), internal_error (500), payments_unavailable (503). The 410 expired error's details include expired_at, the __extend URL, the admin URL, and how long the frozen data remains restorable. ## Security model The database URL is a bearer capability: anyone who has it can read and write that database. Do not commit it to a public repository, log it, or put sensitive data in it. Databases are anonymous, disposable, and expire automatically. CORS is open (*) so simple browser prototypes can call their database directly, but product-like apps should keep the URL server-side and expose app-specific API routes so TmpState stays invisible infrastructure.