New features, changes and fixes to the jsonpad platform, most recent first. jsonpad is deployed continuously, so releases are listed by the date they reached production rather than by version number. The SDKs are versioned separately.
https://mcp.jsonpad.io/api, with one of your API tokens, to build and run a backend on your account: create lists, indexes and write rules, read and change items, plan and apply schema sync documents, find out why a write was refused, restore items from their history, and call your flows. It can only do what its token can, and it's careful: it won't overwrite changes your app made in the meantime, won't delete values a guard index hides from it, and asks before anything that can't be undone. It's also an npm package, @basementuniverse/jsonpad-mcp, for running it locally. See MCP servers.oauth, which you can delete at any time to take the access away.OAUTH_CLIENT_INVALID (24001), OAUTH_REDIRECT_URI_INVALID (24002) and OAUTH_REQUEST_INVALID (24003).https://mcp.jsonpad.io/docs to search and read these docs, look up the exact contract of any API endpoint, SDK method, command line tool command or error code, and check the write rules, flows, schema sync documents and token permissions they write for you, with the same engines the API uses, before you save them. It's free, needs no account, and can't see your data. It's also an npm package, @basementuniverse/jsonpad-docs-mcp, for running it locally. See MCP servers.https://api.jsonpad.io/flows/<path> and public flows. See run a flow.https://jsonpad.io/schema/flows-v1.json, flow-tests-v1.json and rules-tests-v1.json, next to sync-v1.json, so editors can autocomplete and check those files too.includeData, path, includeGuarded and generate are now reserved index path names, because they're query parameters on the item endpoints. An existing index with one of these names keeps its values, but items can no longer be filtered or sorted by it, so rename it to use it that way.USER_NOT_AUTHENTICATED (11002) and USER_NOT_AUTHORIZED (11003)..md on the end) showed write rules examples as numbered lines run together (1allow update, delete: ...), with operators escaped. They're now code blocks, marked jsonpad-rules.QUOTA_EXCEEDED429 Too Many Requests). They're now written ` QUOTA_EXCEEDED (10013, 429 Too Many Requests) `.UNKNOWN (it's UNKNOWN_ERROR), and filed the schema sync errors under identity errors; they have their own section now.includeGuarded, on every item endpoint's page, went to a page that doesn't exist.require steps, branch, loop over arrays, and respond. Expressions are written in the write rules language. The new Flows page in the dashboard has the graph editor, templates, a test runner that rolls everything back, stored tests that must pass before a flow is saved, and a log of each flow's last 100 runs. A flow costs what the same API calls would: a call or an event run is one request, and each item it reads or writes is one more. Each plan allows a number of flows: 2 on Free, 10 on Indie, 50 on Pro and 250 on Scale. See flows.https://api.jsonpad.io/flows/<path>, with a token that has the new run permission (or run-with-identity). Everything a run writes happens together, or not at all, so a flow can take stock only if there's enough, or deal the top card of a hidden deck. A flow can also be public, and called without a token. The JavaScript SDK has runFlow().old and new to compare. Runs that can't finish are retried, flows that start flows stop three deep, and a flow that keeps failing is turned off. See event flows.jsonpad flows check, test and run). See keeping flows in git.emit step sends a flow.emit event to webhooks you choose, once the run has committed. Webhooks for changes a flow made say so in their actor.lookup("customers", new.customerId) gives the item (its data and metadata) or null, and exists(...) checks there is one. So a rule can check that a reference points at something real, or ask another item who may write ("only the game's players can post moves"). Lookups read as the list's owner, guarded values included, so a rule can check a guess against an answer the client never sees. A write can look up at most 10 different items, and each item read counts as a request, as the same GET would. A lookup in a list that doesn't exist fails closed, and saving rules warns about such lists. Rule tests can list items for lookups to find, and the rules dry run reports what it read. Because rules that look up a list can read it, an API token can only save rules that look up lists it has update permission on. See looking up other items.$jsonpad-var:random-shuffle, random-sample and random-pick, work on an array in the item's data (or on values you list), using a cryptographic random source the client can't predict or choose: deal a deck, assign teams, pick who goes first. See variables.item.created, item.updated, item.restored, item.deleted) and the lists to watch, and each change is sent to your URL as a signed POST request. Updates include the item as it was before the change, so your server can tell what changed. Deliveries are signed (x-jsonpad-signature), retried for about a day if your server is unavailable, and listed on the webhook's page with your server's response, where you can send a test ping or redeliver any event. Each plan allows a number of webhooks (1 on Free, 5 on Indie, 20 on Pro, 50 on Scale), and deliveries don't count as requests. See the new webhooks guide.if-match header when you update, restore or delete it, and the write only happens if nobody else has changed the item since you read it. Otherwise nothing is written and the API returns 412 Precondition Failed (ITEM_PRECONDITION_FAILED), so two clients editing the same item can no longer silently overwrite each other's changes. Every item write returns the new ETag to use next time. See conditional writes.allow decides who may write (a write nothing authorises is refused with 403), and require decides what they may write (a failed check is a 400 with the message you wrote). Rules read the data before and after the write, the item's owner, the identity and token making it, and the server's clock, so they can express what token permissions and JSON schema can't: only the owner may edit this, this field can never change once set, this counter can only go up by one, this player may only move on their turn. That's what lets a browser or game client talk to JSONPad directly, with the rules enforced on our side. See the new write rules guide, the language reference and the recipes.shared update, delete;, which lets several identities write the same item instead of only their own, with the rules deciding who may. It's what a game, a shared document or a chat room needs.jsonpad rules check, test and eval check them in CI without making a request.GET /lists/:list/rules/denials.409 ITEM_CONFLICT and can fetch the item again and retry.ITEM_UNABLE_TO_CREATE / ITEM_UNABLE_TO_UPDATE ("unknown error") when a value at an indexed pointer was longer than 1024 characters, and building an index over such a value failed too. An index now stores the first 1024 characters (or 2048 bytes of UTF-8, whichever is shorter) of a long value, so filtering, sorting and searching work on the start of it. Alias values still have to be stored in full to stay unique, so an item write with an alias value over the limit is refused with a message naming the index, and an alias index can't be built, or turned on, while an item has one. See long values.POST /identities/password-reset, by id, name or email) and send it to them, e.g. as a link in an email. The page the link opens sets a new password with POST /identities/password-reset/confirm, which logs the identity out everywhere. Email verification works the same way (POST /identities/email-verification and .../confirm). JSONPad never sends email itself: the email comes from your app, with your own branding. Tokens are URL-safe, single-use, and expire after 1 hour (reset) or 1 day (verification) by default. Requesting a token needs the new reset-password or verify-email token permission. See the new password reset guide.202 { "delivery": "webhook" }, so it can be made straight from a browser, and apps without a server can send emails from a serverless function or a no-code tool. Deliveries are signed (x-jsonpad-signature), retried when your webhook is unavailable, and recorded as events. The dashboard can send a test delivery.POST /identities/logout with { "all": true } logs out everywhere, and so does the new Log out everywhere button on an identity's dashboard page. GET /identities/self includes sessionCount.GET /identities/oauth/providers), starts a sign-in (POST /identities/oauth/:provider/start) and finishes it on the page the provider returns to (POST /identities/oauth/complete), which logs in the identity linked to that account, or creates one. A logged-in identity can link and unlink accounts of its own, and its last way of signing in can't be removed. Set a provider up for an identity group in the dashboard: it walks you through creating the OAuth app, pasting the client ID and secret (checked with the provider as you save them), and a test sign-in that shows exactly what the provider sent back. See the new OAuth guide, and the Google and GitHub setup guides..p8 key, which the dashboard takes (and encrypts) in the same wizard; JSONPad signs the short-lived client secrets Apple asks for. Apple posts its result back as a form, so its sign-ins always come through JSONPad's callback URL, which means localhost return pages work even though Apple won't accept them. Apple only sends someone's name the first time they sign in, and private relay addresses count as verified. See the Apple setup guide.PUT /identities/self now needs its current password in currentPassword (unless the identity has no password). This stops someone who has got hold of an identity token from locking the real owner out.GET /lists/{list}/indexes/{index}/events/{event}), and an identity's password reset, email verification, self-update and linked account events (GET /identities/{identity}/events/{event}). Anything the list returns can now be fetched on its own.POST /sync-schema takes a document describing your lists and their indexes, keyed by path name, and creates or updates the account to match. Nothing is deleted unless you ask it to prune (see below). ?dryRun=true returns the plan (what would be created, updated or left alone, field by field) without applying it. A sync is all or nothing: if any change would be refused, nothing is written. Changing an index's pointer in a list that has items makes the index unusable until it has been rebuilt, so it needs ?allowRebuild=true. A document can name a scope (e.g. your app's name): its lists are tagged with it, and a list managed by one scope can't be changed by a document with another. Tokens need the new sync-schema permission action, plus the usual permission for each change. GET /sync-schema exports existing lists and indexes as a document (optionally only a scope's lists, lists with certain tags, or lists by path name), which is the easiest way to start using schema sync on an existing account. The JS SDK has syncSchema() and exportSchema(), and a jsonpad command line tool (npx @basementuniverse/jsonpad-sdk sync-schema) for deploy scripts and CI. Documents can point editors at the JSON schema at https://jsonpad.io/schema/sync-v1.json for autocomplete. See the new schema sync guide.POST /sync-schema?prune=true also deletes the lists and indexes a scope manages that its document no longer declares. Only resources a previous sync with the same scope declared are deleted: a list that has been renamed or has lost the scope's tag since, and anything created by hand, is left alone and reported with a warning. A list's indexes are only pruned if its definition has an indexes key. Deleting a list that has items, or a guard index, needs ?allowDestructive=true, and a prune that would delete every list in the scope is refused. Tokens need permission to delete each list and index. Deletes show up in the plan (and in dry runs) with the delete action, and are logged as list-deleted / index-deleted events with the sync's id. The JS SDK's syncSchema() and the jsonpad sync-schema command have matching prune and allowDestructive options. See pruning.POST /sync-schema/move moves lists (by id or path name) to another scope, assigns lists that no scope manages to one, or releases lists from their scope with "scope": null. Pass "fromScope" instead of "lists" to move every list a scope manages, e.g. to rename it. The scope's tag moves with each list. A moved list keeps its indexes managed; an assigned list's indexes are adopted by the next sync that declares them. Like a sync, a move is all or nothing, supports ?dryRun=true, and needs the sync-schema permission plus permission to update each list. The ownership conflict error now says how to move a list. The JS SDK has moveLists(), and the command line tool has jsonpad move-lists. See moving lists between scopes.POST /lists/{list}/indexes/{index}/rebuild starts a new build for an index whose last build failed, once the problem has been fixed. Failed builds are never retried automatically. The dashboard's background jobs menu has a rebuild button on failed index builds, and the JS SDK has rebuildIndex(). Rebuilding an index that isn't failed is refused with INDEX_BUILD_NOT_FAILED (16008), and each rebuild logs an index-build-requested event.fetch and JS SDK examples for fetching, updating and deleting that resource. The examples use its real ids and, where there's a request body, its current values. Pick one of your tokens to fill it in; the choice is shared with the documentation's token selector.jsonpad is now @basementuniverse/jsonpad-cli, and it does a lot more than schema sync: manage lists, indexes, items and identities, search, read stats and event history, restore items, export and import a list's items, act as an identity, and watch realtime events. It saves tokens as profiles on your own machine, prints tables in a terminal and JSON in scripts, and has shell completion. The schema commands work exactly as before, so to switch, run npx @basementuniverse/jsonpad-cli sync-schema in place of npx @basementuniverse/jsonpad-sdk sync-schema. The JS SDK's own command is deprecated, and will be removed in SDK 2.0.0. See the new command line tool guide.PUT /lists/{list}/indexes/{index} (or from the dashboard). Previously this was always refused with "list already has an alias index", even when the list had no alias index.alias on for an existing index is refused if more than one item already has the same value at its pointer, since alias values have to be unique. If the index is still being built, the build checks the values when it finishes..md on the end) now includes every language of each code example, rather than only the first.@basementuniverse/jsonpad-sdk rather than jsonpad.Content-Type header on requests without a body.POST /tokens/{id}/regenerate issues a new value for an existing token without deleting and re-creating the token entity. User auth mode only. Use it when a token has been exposed.DELETE /lists/{list} deletes the list itself, so it disappears (and its path name is free to use again) immediately, and its items, indexed values and indexes are deleted by a background job. Deleting a list with a lot of items is no longer at risk of timing out. The job shows up in the dashboard's jobs menu while it runs, and your stored bytes are recalculated when it finishes.guard, which strips the value at its pointer from item data in responses made with token auth. The value can still be written. If the item belongs to the calling identity, pass ?includeGuarded=true to get the guarded data back. Useful for keeping part of an item private to its owner.includeSnapshot=true to restore the previous behaviour. Event responses were large and most callers never read the snapshot.before/after payloads of token events.POST /items/{id}/restore restores a deleted item.GET /tokens/self returns the token being used to make the request, so an application can inspect its own permissions and identity without a user-mode call.displayName.connect_error carrying the same error shape as an API error, plus max and retryAfter. Realtime messages themselves are not metered.x-quota-total, x-quota-remaining, x-quota-credits, x-quota-reset, x-quota-degraded, x-rate-limit-total, x-rate-limit-remaining and retry-after.GET /subscriptions/usage returns current usage against your allowance./pricing page and a "Limits and quotas" documentation section.429 with QUOTA_EXCEEDED. Paid plans get a 10% overdraft first.5xx, that are refused for quota, or that trip the rate limiter are given back and do not count against your allowance..md to its URL — for example https://jsonpad.io/docs/getting-started.md — and the site now publishes sitemap.xml, robots.txt, llms.txt and llms-full.txt.identityId variable, so the id of the identity that owns an item can be substituted into that item's data.Content-Type: application/json5 to have a request body parsed as JSON5, or Accept: application/json5 to have the response serialised as JSON5.Content-Type: application/x-msgpack to have a request body decoded as MessagePack, or Accept: application/x-msgpack to have the response encoded as MessagePack.includeData parameter on the item write endpoints. It defaults to true; set it to false to skip item data in the response, which is worth doing when updating large items.path parameter on the items index endpoint.JSONPad went live.