Wispist
Persistent JSON documents and live updates for simple browser sites — no connection step, no application server.
Single-page view · as markdown
What is Wispist
Wispist / Getting started
Wispist is a lightweight data engine for simple websites. It gives ordinary HTML and JavaScript a persistent document store, safe concurrent mutations, and live updates while keeping the site itself static.
On a Wispdeck-hosted site, Wispist is already there:
const = ;
const = await ;
await ;
There is no connection call, project identifier, endpoint, API key, or secret in that code. Wispdeck identifies the site and its active release from the request, then supplies an opaque namespace and policy to the embedded Wispist Engine. The same-origin browser client cannot choose another site's store.
Static code, persistent data #
A Wispist site is still a static bundle:
site.zip
├── index.html
├── app.js
├── styles.css
└── wispist.json
wispist.json declares which collections the release may use and who may perform each operation. Wispdeck validates that declaration when the bundle is uploaded, and inserts a small parser-blocking client before site-authored elements in every served HTML head, so globalThis.wispist exists before later site scripts run.
The site's documents live in SQLite, outside the immutable release. Republishing code therefore keeps production data. An idle site has no dedicated process, timer, poller, goroutine, or database connection; work happens only while a viewer or mutation is active.
The data model #
Wispist v1 has three main concepts:
- A namespace is the host-selected isolation boundary. Wispdeck uses separate
liveanddraftnamespaces for each site. - A collection is a declared, ordered set of documents, such as
before-you-go. - A document contains an opaque ID, an opaque revision, server timestamps, and a caller-owned JSON object in
data.
Every replacement and deletion is conditional on an observed revision. If two browsers edit the same document concurrently, one succeeds and the other receives a revision conflict rather than silently overwriting work it did not see.
Subscriptions use Server-Sent Events. The client first lists a consistent collection view, then consumes retained changes after that list cursor. It reconnects after ordinary network interruptions and relists if the retained history is no longer sufficient.
Policy is public, enforcement is not #
The release declaration contains policy, not credentials. The concise shared profile deliberately allows every visitor to list, read, create, update, delete, and subscribe:
That is suitable for a genuinely public collaborative checklist. It is not user authorisation. Exact-origin checks, quotas, and request limits reduce cross-site and automated abuse, but any visitor can still change a shared collection.
Expanded declarations can set every operation to anyone, authenticated, or nobody. Wispdeck's first integration supplies anonymous principals to hosted site code, so authenticated is reserved for future identity integration rather than a way to reuse the dashboard cookie.
Draft and live state #
Wispdeck previews use a fresh private origin. Draft code reads and writes the site's draft namespace. The Current selector on that preview origin reads the live namespace with an unconditional server-side read-only override.
Publishing changes which release supplies public code and policy. It does not promote draft documents, clear live documents, or create a new production store. This prevents test data from silently replacing real data.
What v1 deliberately leaves out #
Wispist v1 is document CRUD plus live collection updates. It does not expose SQL, run user functions, execute scheduled jobs, perform joins, provide offline conflict resolution, accept file uploads, or offer multi-document transactions. A data directory supports one active process.
The small surface is intentional: Wispist is for the state that turns a static page into a useful little shared application, not a general application server.
Where to go next #
To build the checklist from the snippet above end to end, follow Build a shared checklist.
For the data model in full — namespaces, envelopes, revisions, conditional mutation — read Documents and collections.
For every field the declaration file accepts, read the wispist.json reference.
Build a shared checklist
Wispist / Getting started
This page builds the smallest useful Wispist site: a checklist that persists and updates in every open browser. The site has no build step and no connection configuration.
Declare the collection #
Create wispist.json at the root of the site bundle:
The shared profile lets every viewer read and change the collection. Use it only when that is the intended product behaviour. The two limits lower Wispist's installation defaults for this collection.
Write the page #
Create index.html:
<!doctype html>
Before you go
Before you go
New item
Add
Loading…
Do not add the Wispist client script yourself on Wispdeck. Wispdeck inserts it before the page's own script element, so wispist exists when app.js runs.
Add and render documents #
Create app.js:
const = ;
const = ;
const = ;
const = ;
let = new;
;
add creates a document with a generated ID and returns the complete document envelope. The client supplies the required idempotency key automatically.
Apply safe updates #
Add a delegated change listener:
;
update accepts the observed document, not only its ID. It shallow-merges the new fields locally and sends a replacement conditional on item.revision. A stale revision becomes a typed revision conflict.
Subscribe #
Start the live collection at the end of app.js:
const = ;
subscribe returns immediately. It lists every page, opens a change stream from that list cursor, maintains a document map, and calls render with the initial state and every later state.
Call unsubscribe() when a long-lived page no longer needs the collection. Closing a page closes its EventSource automatically.
Upload and test #
Put the three files at the ZIP root:
checklist.zip
├── index.html
├── app.js
└── wispist.json
Upload the archive as a Wispdeck site draft. In preview, the checklist uses draft data. Publish the release, open the public site in two independent browser windows, and add or check an item in one. The other should update without a refresh.
When a later draft exists, changes made in Draft remain separate. Select Current to see live data in read-only mode. Publishing that draft changes the code and declaration while leaving the public checklist intact.
Where to go next #
- Documents and collections — the envelope, revision, and conditional-mutation model behind
update. - Live updates — what
subscribedoes underneath: snapshot, cursor, Server-Sent Events, reconnect, reset. - wispist.json reference — every declaration field, profile, and limit.
Documents and collections
Wispist / Using Wispist
Wispist stores JSON objects as revisioned documents inside declared collections. The host supplies the namespace; site code selects only a collection and, where needed, a document ID.
Namespaces are the isolation boundary #
A namespace is opaque to Wispist. It cannot be selected through the browser API and is never inferred from a collection name. Wispdeck binds every request to a stable site store and either its live or draft namespace.
The same collection name in two sites, or in one site's live and draft views, addresses different data. Republishing does not change the site's stable store key.
Collections must be declared #
A collection exists for browser access only when the active release declares it in wispist.json. Removing the declaration hides stored documents but does not delete them. Re-adding it makes them accessible again.
Names are 1 to 48 ASCII bytes, begin with a lowercase letter, and continue with lowercase letters, digits, hyphens, or underscores:
[a-z][a-z0-9_-]{0,47}
Collection order is document creation order, with ID as the deterministic tie-breaker.
A document has server and caller fields #
Wispist owns id, revision, createdAt, and updatedAt. The caller owns data, whose root must be a JSON object. Nested arrays, objects, strings, numbers, booleans, and null are allowed.
JSON is strict. Invalid UTF-8, duplicate member names, more than 32 nesting levels, keys longer than 256 UTF-8 bytes, and oversized representations are rejected. Wispist stores compact JSON but does not give object-member order any meaning.
IDs may be caller-selected or generated. They contain 1 to 64 ASCII letters, digits, underscores, or hyphens and are case-sensitive. . and .. are not valid IDs.
Revisions prevent silent overwrite #
A revision identifies one incarnation of one document. Every successful create or replacement produces a new opaque value, and deleting then recreating an ID does not reuse an old revision.
The easiest safe operations are the browser client's document-envelope methods:
const = await ;
const = await ;
await ;
update and delete carry the observed revision. If another caller changed the document first, Wispist returns revision_conflict. It never falls back to last-write-wins.
Creating a deterministic ID uses create-only semantics:
await ;
That operation fails with a revision conflict if passport already exists.
One document per transaction #
Each create, replacement, or deletion commits the document state and one ordered change record in the same SQLite transaction. A subscriber can therefore never observe a change whose document mutation failed to commit.
V1 does not provide multi-document transactions. Model one atomic unit as one document when several fields must change together.
Where to go next #
For the client methods that carry these semantics — get, create, replace, update, delete — read the Browser API reference.
For what happens when a conditional mutation loses a race, read Revision conflict.
For how draft and live namespaces relate across releases, read Drafts and publishing.
Live updates
Wispist / Using Wispist
collection.subscribe(callback) keeps one collection view current without polling. It combines an initial paginated list with a same-origin Server-Sent Events stream.
Snapshot, then changes #
Opening a subscription does four things:
- Lists every page of the collection.
- Records the change cursor captured at the first page.
- Opens
/_wispist/v1/changesafter that cursor. - Applies create, update, and delete events to an in-memory map.
Pagination is not a frozen SQLite snapshot across requests. The cursor is what closes the gap: mutations that race the list appear in the retained change stream after the first page's watermark.
The callback receives the complete ordered document array and an event:
const = ;
Call stop() to close the stream and cancel reconnect work.
Delivery and reconnection #
Wispist commits a mutation before publishing it to in-memory listeners. On connection, the server registers the listener before reading retained changes, sends the backlog, removes duplicate queued events by cursor, and then continues live.
Delivery is at least once. Cursors are opaque and the client de-duplicates them. Ordinary disconnects reconnect with bounded exponential backoff and jitter.
Change history is finite. If a cursor is older than retained history, or a subscriber falls behind its bounded event queue, the server sends a reset event and closes. The browser client relists automatically rather than risking silent divergence.
Active compute only #
An open EventSource is active work. Wispist caps streams per site and client address and sends periodic comments to keep healthy intermediaries from treating an idle connection as abandoned.
There is no listener, poller, or goroutine for a site with no active subscription. Reading an untouched collection or opening its first stream does not create a SQLite file; the first mutation does.
Where to go next #
For the wire-level change stream — cursors, event shapes, heartbeats — read the HTTP API reference.
For the stream caps and retention bounds behind reset, read Limits and abuse controls.
Drafts and publishing
Wispist / Using Wispist
Wispist data belongs to a site, not to one release. Releases select code and access policy; they do not each get a new production database.
Wispdeck binds each request as follows:
| View | Declaration | Namespace | Mutations |
|---|---|---|---|
| Public published site | Published release | Live | Per collection policy |
| Public site with no release | None | None | Denied |
| Private preview, Draft | Draft release | Draft | Per collection policy |
| Private preview, Current | Published release | Live | Always denied |
The Draft namespace remains stable across successive draft uploads for the same site, so ordinary iteration does not discard test data.
Publishing changes code, not data #
Publishing or rolling back atomically changes the release pointer used by the public origin. Existing live documents remain where they are.
In particular:
- Publishing the first release does not copy draft documents into live.
- Republishing preserves live documents.
- Rolling back code preserves live documents.
- Removing a collection declaration hides its documents without deleting them.
- Re-adding the declaration exposes those documents again.
Promotion is deliberately not implicit. Draft data can be incomplete, destructive, private, or nonsensical. A future copy or promotion feature must be an explicit management action.
Current is a safe comparison view #
When both a public release and a new draft exist, preview offers Current and Draft. Current loads public code and live data on the private preview origin, but Wispdeck sets an unconditional read-only binding. A collection declaration cannot relax it.
wispist.mode reports live-preview and wispist.readOnly is true, allowing the page to hide editing controls. Those values are only presentation hints; the server rejects mutations even if site code ignores them.
Where to go next #
For what a rejected Current-preview mutation looks like to code, read Forbidden.
For the declaration each release carries, read the wispist.json reference.
wispist.json reference
Wispist / Reference
wispist.json is an optional file at the site archive root. It declares the collections exposed to browser code by that release.
The parser is strict:
- The file must be UTF-8 JSON no larger than 64 KiB.
- Duplicate object members and unknown fields are errors.
versionmust be1.collectionsis required and may contain at most 32 entries.- Collection limits may lower, but never raise, host defaults.
- Missing
wispist.jsonmeans zero declared collections.
An invalid declaration rejects the complete upload. It never falls back to a permissive policy.
Top-level object #
| Member | Type | Required | Meaning |
|---|---|---|---|
version | integer | yes | Protocol declaration version; exactly 1. |
collections | object | yes | Collection name to collection declaration. |
Collection names #
Names match:
[a-z][a-z0-9_-]{0,47}
They are ASCII, case-sensitive, and compared byte-for-byte.
Collection declaration #
| Member | Type | Required | Meaning |
|---|---|---|---|
access | string or object | yes | A built-in profile or all six operation policies. |
limits | object | no | Lower document count and byte limits. |
Shared profile #
shared expands to anyone for list, read, create, update, delete, and subscribe. Every visitor can read and change the collection.
Expanded access #
Every operation must appear exactly once:
| Operation | Controls |
|---|---|
list | Listing collection documents. |
read | Reading one document and receiving its body in a subscription. |
create | Generated-ID POST and selected-ID create. |
update | Replacing an existing document. |
delete | Deleting an existing document. |
subscribe | Opening a change stream for the collection. |
Each value is one of:
| Value | Meaning |
|---|---|
anyone | Permit every principal. |
authenticated | Require an authenticated principal asserted by the host. |
nobody | Deny the operation. |
Wispdeck initially supplies anonymous principals to ordinary hosted-site and preview JavaScript. Preview authorisation grants access to the preview, not authenticated Wispist authority.
read and list are intentionally independent, as are read and subscribe.
Collection limits #
| Member | Type | Range | Default |
|---|---|---|---|
maxDocuments | integer | 1 to host maximum | 1,000 |
maxDocumentBytes | integer | 2 to host maximum | 32 KiB |
These limits apply to that collection. The namespace also has a total live or draft byte limit.
See also #
- Limits and abuse controls — the installation defaults these declarations may lower.
- Documents and collections — what a declared collection contains.
- Forbidden — what a denied operation returns.
Browser API reference
Wispist / Reference
Wispdeck defines one non-enumerable, non-writable global before site-authored elements run:
wispist.version
wispist.mode
wispist.readOnly
wispist.collection(name)
There is no connect method. The client makes no request during evaluation.
Environment fields #
| Field | Type | Meaning |
|---|---|---|
version | number | Client protocol major; 1. |
mode | string | live, draft, or live-preview. |
readOnly | boolean | Whether the host has forced mutations off. |
mode and readOnly are synchronous presentation metadata. The server enforces the real binding independently.
Collection handle #
const = ;
collection(name) validates the collection-name grammar locally and returns a frozen handle. The server still requires the active release to declare it.
list(options?) #
Returns a promise for every document in creation order. options.limit selects the page size from 1 to 250; the client follows pagination automatically.
const = await ;
get(id) #
Returns one complete document envelope or rejects with not_found.
const = await ;
add(data) #
Creates a generated-ID document and returns it. data must be a non-null object and not an array. The client generates the required idempotency key.
const = await ;
create(id, data) #
Creates exactly the selected ID and returns it. It uses create-only semantics and rejects with revision_conflict if that ID exists.
replace(document, data) #
Conditionally replaces the observed document with data and returns the new envelope. A concurrent change rejects with revision_conflict.
update(document, changes) #
Shallow-merges changes into document.data locally, then performs replace.
const = await ;
An undefined change is omitted by JSON serialisation but does not delete the old property from the local merge. To remove properties or make nested changes, construct the complete new object and call replace.
delete(document) #
Conditionally deletes the observed document. The promise resolves with no value.
Subscribe #
const = ;
The callback runs once with event.type === "initial" and after each applied change. Documents remain in creation order, with ID as the deterministic tie-breaker.
The client reconnects after recoverable EventSource failures with bounded exponential backoff and jitter. A server reset causes a fresh list. onError receives initial or terminal errors; ordinary reconnects do not create unhandled promise rejections.
subscribe returns the unsubscribe function immediately.
WispistError #
Rejected operations use an Error subclass with:
| Field | Meaning |
|---|---|
name | Always WispistError. |
type | RFC 9457 problem type URI, when an HTTP problem was returned. |
code | Ergonomic code mapped from a known problem type. |
title | Stable problem title. |
detail | Safe occurrence-specific explanation. |
status | HTTP status, when available. |
instance | Problem occurrence URI, when available. |
requestId | X-Request-ID value, when available. |
problem | Complete decoded Problem Details object. |
The wire protocol does not contain a separate code. type is the stable machine identifier; code is client convenience. Network failures use network_error without a type or HTTP status. An invalid non-problem response uses invalid_response.
Known codes are listed in Problem Details.
Browser dependencies and side effects #
The client uses fetch, EventSource, and Web Crypto. It has no package dependency, local-storage use, service worker, analytics, or telemetry. Requests are same-origin, use same-origin credentials, and disable browser caching.
See also #
- HTTP API reference — the routes these methods call.
- Problem Details — every
codeaWispistErrorcan carry. - Live updates — how
subscribebehaves across disconnects and resets.
HTTP API reference
Wispist / Reference
Wispist v1 is served from the hosted site's own origin under /_wispist/:
GET /_wispist/client/v1.js
GET /_wispist/v1
GET /_wispist/v1/collections/{collection}/documents
POST /_wispist/v1/collections/{collection}/documents
GET /_wispist/v1/collections/{collection}/documents/{document}
PUT /_wispist/v1/collections/{collection}/documents/{document}
DELETE /_wispist/v1/collections/{collection}/documents/{document}
GET /_wispist/v1/changes
Use the browser client unless direct HTTP control is necessary.
General rules #
- API responses use
Cache-Control: no-storeandX-Content-Type-Options: nosniff. - JSON responses use
application/json; charset=utf-8. - JSON request bodies require
Content-Type: application/json. - Every response includes
X-Request-ID. A caller-provided UUID may be retained. - Errors use RFC 9457
application/problem+json. - Unsupported methods on known resources return
405withAllow. - Paths and identifiers must be decoded, canonical, and free of encoded separators or dot segments.
- No CORS permission is emitted.
Unsafe requests require one exact same-origin Origin header. If Sec-Fetch-Site is present, it must be same-origin.
Service description #
List documents #
?limit defaults to 100 and may be 1 to 250. after is an opaque pagination cursor.
The changes cursor is captured by the first page and repeated by later pages. List all pages, then consume changes after it to reconcile concurrent mutations.
Create a generated ID #
The key is required and contains 16 to 128 visible ASCII characters. The same namespace, key, and request fingerprint returns the original 201 Created response for at least 24 hours. Reusing the key for a different request returns idempotency conflict.
The response includes Location, the document ETag, and the complete envelope.
Read #
The ETag is the quoted opaque revision. If-None-Match supports wildcard, lists, and weak comparison for GET/HEAD and may return 304 Not Modified.
Create or replace a selected ID #
Create only:
Replace:
Exactly one precondition is required. Create returns 201; replacement returns 200. Both return a new document and ETag.
Delete #
Success is 204 No Content. The deletion appends a change containing ID and deleted revision but no document body.
Change stream #
?collections is repeatable with 1 to 8 distinct declared values. Every collection must permit subscribe and read.
id: opaque-change-cursor
event: change
data: {"collection":"before-you-go","operation":"update","document":{...}}
A deletion supplies id and revision instead of document. A reset event means the client must list again. If after is absent, the stream begins after the current high-water mark and does not replay the collection.
The server sends heartbeat comments while otherwise idle. Delivery is at least once; de-duplicate by cursor.
See also #
- Browser API reference — the supported client over these routes.
- Problem Details — the error shape every route uses.
- Limits and abuse controls — body, pagination, stream, and rate bounds.
Limits and abuse controls
Wispist / Reference
Wispist applies hard resource limits and bounded rate buckets before expensive work. A release may lower collection limits but cannot raise installation policy.
Resource defaults #
| Resource | Default |
|---|---|
| Collections per release | 32 |
| Documents per collection | 1,000 |
| Document JSON | 32 KiB |
| Live document data per site | 10 MiB |
| Draft document data per site | 5 MiB |
| List page | 100; maximum 250 |
| Collections per SSE stream | 8 |
| SSE streams per site and client | 6 |
| SSE streams per site | 100 |
| Pending events per stream | 128 |
| Request envelope allowance | document limit plus 4 KiB |
| Live idempotency records per namespace | 10,000 |
| Retained changes per namespace | 10,000 |
| Change age | 7 days |
| Idempotency retention | at least 24 hours |
The document-data quota counts compact document JSON, not release files. Change and idempotency tables are separately bounded.
Initial Wispdeck rate buckets #
| Bucket | Sustained | Burst |
|---|---|---|
| Reads per site and client | 600/minute | 100 |
| Mutations per site and client | 60/minute | 20 |
| Mutations across one site | 300/minute | 60 |
| Generated-ID creates across one site | 10,000/day | 100 |
| All Wispist requests | 6,000/minute | 1,000 |
Client keys come from Wispdeck's trusted-proxy-aware address resolution. Untrusted forwarding headers are ignored.
Buckets use bounded memory and expire after inactivity. Concurrent streams use counters rather than token rates. A rejected request returns rate limited with Retry-After.
Rate limits are availability controls, not authorisation. A public shared collection remains publicly writable below the rate allowance.
See also #
- wispist.json reference — how a release lowers collection limits.
- Quota exceeded, Request too large, Rate limited — the problems these bounds produce.
Problem Details
Wispist / Problems
Wispist API errors use RFC 9457 Problem Details and application/problem+json:
The type URI is the primary machine identifier. It is permanent even if the implementation or documentation later moves. title is stable for that type; detail describes this occurrence. status matches the HTTP status.
instance is a urn:uuid: URI built from the same UUID returned in X-Request-ID. Give that request ID to an operator when investigating a server-side failure.
Validation problems may add an errors array of JSON Pointer and detail objects. Internal SQL, paths, stack traces, document bodies, tokens, and policy predicates are never returned.
The browser client maps known type URIs to convenient error.code values:
| HTTP | Type | Browser code |
|---|---|---|
| 400 | Invalid request | invalid_request |
| 400 | Invalid JSON | invalid_json |
| 401 | Authentication required | authentication_required |
| 403 | Forbidden | forbidden |
| 404 | Not found | not_found |
| 405 | Method not allowed | method_not_allowed |
| 409 | Idempotency conflict | idempotency_conflict |
| 409 | Quota exceeded | quota_exceeded |
| 412 | Revision conflict | revision_conflict |
| 413 | Request too large | request_too_large |
| 415 | Unsupported media type | unsupported_media_type |
| 428 | Precondition required | precondition_required |
| 429 | Rate limited | rate_limited |
| 503 | Temporarily unavailable | temporarily_unavailable |
HTTP headers retain their normal authority. Retry-After, ETag, Allow, and WWW-Authenticate are not replaced by Problem Details members.
Invalid request
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/invalid-request/
Status: 400 Bad Request. Browser code: invalid_request.
Wispist returns this problem when the request cannot be interpreted under the v1 resource contract. Examples include an unknown or repeated query parameter, invalid collection or document ID, non-canonical path, malformed cursor, unknown request-envelope member, or contradictory conditional headers.
This differs from Invalid JSON: the JSON may be syntactically valid while its protocol envelope or request metadata is not.
Correct the request rather than retrying it unchanged. Use the browser client where possible; it constructs canonical paths, envelopes, pagination, and mutation preconditions.
Invalid JSON
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/invalid-json/
Status: 400 Bad Request. Browser code: invalid_json.
The submitted document representation is not permitted JSON. Wispist rejects malformed or invalid UTF-8 input, a data root that is not an object, duplicate object member names, more than 32 nesting levels, keys longer than 256 UTF-8 bytes, and trailing JSON values.
Generate the body with a normal JSON serialiser and pass a non-null object to the browser client. Duplicate members must be fixed at the producer; Wispist does not choose which ambiguous value wins.
An oversized representation uses Request too large instead.
Authentication required
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/authentication-required/
Status: 401 Unauthorized. Browser code: authentication_required.
The collection operation is declared authenticated, but the host did not bind an authenticated principal. The response includes WWW-Authenticate.
Wispdeck's initial hosted-site integration supplies anonymous principals to public and preview JavaScript. A dashboard session or private preview cookie does not automatically become Wispist identity. Until a host identity integration is configured, change the operation to anyone only if public access is genuinely intended, or leave it inaccessible.
An authenticated principal that still lacks permission receives Forbidden.
Forbidden
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/forbidden/
Status: 403 Forbidden. Browser code: forbidden.
The bound principal may not perform the operation. Common causes are:
- The collection operation is
nobody. - A mutation did not carry the exact bound site
Origin, or Fetch Metadata identified it as cross-site. - The request is using Wispdeck's Current preview, whose live-data binding is unconditionally read-only.
- A configured authoriser denied the current or proposed document.
Do not retry unchanged. Check wispist.readOnly to adjust Current-preview UI, and compare the active release's wispist.json with the operation being attempted. Client metadata is only a UI hint; the server remains authoritative.
Not found
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/not-found/
Status: 404 Not Found. Browser code: not_found.
The requested resource is unavailable. A document may not exist, the active release may not declare the collection, or the path may not identify a v1 resource.
Wispist does not reveal hidden stored documents merely because an older release once declared them. Removing a declaration makes the collection unavailable without deleting its data.
For get, treat this as an absent document where that is valid application state. For an entire collection, inspect the declaration attached to the release serving this request.
Method not allowed
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/method-not-allowed/
Status: 405 Method Not Allowed. Browser code: method_not_allowed.
The path names a known Wispist resource, but its method is not supported. The Allow response header lists valid methods and is authoritative.
For example, a collection document resource supports GET, HEAD, PUT, and DELETE, while the change stream supports only GET. Use the route table in the HTTP API reference or the browser client.
Idempotency conflict
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/idempotency-conflict/
Status: 409 Conflict. Browser code: idempotency_conflict.
The Idempotency-Key was already used in this namespace for a different generated-ID POST. Wispist retains each successful key and response for at least 24 hours so a lost response can be retried safely.
If this is a retry, send exactly the same method, path, and document data. If it is a new logical create, generate a new strong random key. The built-in collection.add method does this automatically.
This problem concerns POST request identity. Concurrent document revisions use Revision conflict.
Quota exceeded
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/quota-exceeded/
Status: 409 Conflict. Browser code: quota_exceeded.
The mutation is valid in isolation but would exceed a storage bound. The transactional check may cover collection document count, total live or draft document bytes, or the maximum number of unexpired idempotency records.
Delete data that is no longer needed, reduce the proposed document, or ask the operator to review installation policy. A release declaration may lower limits but cannot raise them.
A single request or document that exceeds its byte limit uses Request too large.
Revision conflict
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/revision-conflict/
Status: 412 Precondition Failed. Browser code: revision_conflict.
The mutation's HTTP precondition did not match current state:
If-None-Match: *was used to create an ID that already exists.If-Matchcarried an older revision for replacement or deletion.
Fetch or wait for the latest document, reconcile the user's intent, then submit a new conditional mutation using that revision. Never loop blindly: another caller may have made a meaningful conflicting change.
The browser client's replace, update, and delete methods accept an observed document envelope so safe concurrency is the normal path.
Request too large
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/request-too-large/
Status: 413 Content Too Large. Browser code: request_too_large.
The request body or data representation exceeds the active collection limit. Wispist limits the body before decoding and allows only a small fixed amount beyond the document limit for the JSON envelope.
Reduce the document or split independent records into separate documents. A release may request a higher collection limit only up to the operator's host maximum.
Total namespace storage exhaustion uses Quota exceeded.
Unsupported media type
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/unsupported-media-type/
Status: 415 Unsupported Media Type. Browser code: unsupported_media_type.
Document create and replacement bodies require:
Media-type parameters such as charset=utf-8 are accepted, but another base type or a missing header is not. Set the header and send the documented {"data": {...}} envelope.
JSON that has the correct media type but invalid content uses Invalid JSON or Invalid request.
Precondition required
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/precondition-required/
Status: 428 Precondition Required. Browser code: precondition_required.
Wispist does not offer unconditional last-write-wins mutation. A selected-ID create requires If-None-Match: *. Replacing or deleting an existing document requires If-Match with the quoted observed revision.
Read the current document and use replace, update, or delete with that complete envelope. If the supplied revision is no longer current, the next response is Revision conflict.
Rate limited
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/rate-limited/
Status: 429 Too Many Requests. Browser code: rate_limited.
A read, mutation, generated-ID create, installation-wide request, or concurrent change stream exceeded its bounded allowance. Retry-After gives the minimum server-selected delay in seconds.
Wait for that delay and retry with backoff. Avoid tight loops, duplicate subscriptions, and unnecessary full-list refreshes. The built-in subscription client already reconnects with bounded exponential backoff.
Rate limiting is an availability control. It does not change collection policy or make public writes private.
Temporarily unavailable
Wispist / Problems
Canonical type:
https://learn.peios.org/wispist/problems/temporarily-unavailable/
Status: 503 Service Unavailable. Browser code: temporarily_unavailable.
Wispist could not safely complete the operation. Expected transient causes include a busy SQLite store or temporary saturation of the bounded open-store cache. The response may include Retry-After.
Retry idempotent reads after a short backoff. Retry generated-ID POST only with the same idempotency key and identical request. Conditional replacements and deletions may be retried with the same observed revision; a committed competing change then becomes Revision conflict.
If the problem persists, give the operator the X-Request-ID. Wispist does not expose internal error text in the response.