Skip to content
Viresso. Documentation Search Open Viresso

Objects API

Use the Objects API for JSON entries with or without a contract. It reads the entry's JSON directly, including native entries that have no content type. JSON-backed entries converted from the older content model work through the same endpoints. Existing Entries integrations keep their current URLs and payloads.

Files use the Files API. Stored form submissions stay separate from objects and use the form's submissions workflow. The Objects endpoints do not list or read file references or legacy linked submissions.

Availability and permissions

All routes are under /api/v1/{workspace}/objects and require a workspace API key. The global Objects setting and workspace activation must permit Objects; disabled workspaces return 404. Production requires OBJECTS_ENABLED=true and an activated workspace. These endpoints are not limited to local development and do not activate or migrate a workspace themselves.

Operation Method and path API ability Creator permission
List published entries GET /objects entries:read list-entries
Read a published entry GET /objects/{objectRef} entries:read view-entry
Query drafts and published entries POST /objects/query entries:query list-entries
Create an entry POST /objects entries:write create-entry
Update an entry PATCH /objects/{objectRef} entries:write update-entry
Delete an entry DELETE /objects/{objectRef} entries:delete delete-entry

{objectRef} is the numeric object id returned by this API. It is different from the entry's ULID (entry_id) and its bucket-relative address (ref). Use entry_id in relationship values. A reference rename or bucket move preserves both IDs.

Published reads always exclude drafts, even for wildcard keys. Use the separately scoped query endpoint to inspect drafts. Keys are still restricted to their workspace and their creator's permissions. These requests share the existing entries quota: by default 500 units per minute per API key, with one unit per request. See API access.

Create an entry

POST /api/v1/acme/objects
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
  "ref": "products/inflatable-pillow",
  "status": "published",
  "data": {
    "name": "Inflatable pillow",
    "price": 24.95,
    "available": true,
    "dimensions": { "width": 40 },
    "tags": ["camping"]
  }
}

The unwrapped 201 response includes:

{
  "id": 42,
  "entry_id": "01HY8M7M7D4RT5N6P2Q3R4S5T6",
  "bucket_id": 3,
  "bucket": "general",
  "ref": "products/inflatable-pillow",
  "kind": "entry",
  "contract": null,
  "data": {
    "name": "Inflatable pillow",
    "price": 24.95,
    "available": true,
    "dimensions": { "width": 40 },
    "tags": ["camping"]
  },
  "locale": "en-US",
  "locales": ["en-US"],
  "status": "published",
  "lock_version": 0,
  "created_at": "2026-10-15T12:00:00.000000Z",
  "updated_at": "2026-10-15T12:00:00.000000Z",
  "validation": {
    "mode": null,
    "issues": [],
    "checked_at": "2026-10-15T12:00:00.000000Z",
    "pending": false,
    "failed": false
  }
}

data must be a JSON object. Numbers, booleans, nulls, arrays and empty objects retain their JSON types. No contract or content type is required. Omitted bucket_id uses General; omitted status uses draft. References are unique across entries and files within their bucket.

Instead of ref, supply path to generate a reference from a folder path and the new entry's server-generated ULID. Use an empty or null path for the bucket root. Do not supply both ref and path, or your own id or entry_id. The maximum write request size is 1 MiB.

Optional contract selects validation: omitted/null means Automatic on creation, false means No contract, and a string selects that exact workspace contract name. See Contracts.

List published entries

GET /api/v1/acme/objects?bucket=general&prefix=products&per_page=50
Authorization: Bearer YOUR_API_KEY
Parameter Behavior
bucket Optional bucket API name; omitted means all buckets in this workspace.
bucket_id Optional numeric bucket ID, instead of bucket.
ref Exact bucket-relative reference. Combine with a bucket to identify one address.
prefix A folder path, matching that exact reference and descendants; products excludes products-old. Leading/trailing slashes are ignored. % and _ are literal characters.
view summary (default) omits data and does not load entry documents; full includes content.
locale Configured locale, default en-US; * includes the base document and all translations in full views.
per_page Page size, default 50, range 1–100.
cursor Opaque cursor returned by this endpoint.

The response uses cursor pagination:

{
  "data": [
    {
      "id": 42,
      "entry_id": "01HY8M7M7D4RT5N6P2Q3R4S5T6",
      "bucket_id": 3,
      "bucket": "general",
      "ref": "products/inflatable-pillow",
      "kind": "entry",
      "contract": null,
      "locale": "en-US",
      "locales": ["en-US"],
      "status": "published",
      "lock_version": 0,
      "created_at": "2026-10-15T12:00:00.000000Z",
      "updated_at": "2026-10-15T12:00:00.000000Z",
      "validation": { "mode": null, "issues": [], "checked_at": "2026-10-15T12:00:00.000000Z", "pending": false, "failed": false }
    }
  ],
  "path": "https://app.example.com/api/v1/acme/objects",
  "per_page": 50,
  "next_cursor": null,
  "next_page_url": null,
  "prev_cursor": null,
  "prev_page_url": null
}

Follow next_page_url while it is non-null. Ordering is object ID ascending. Pages contain at most the requested number of entries, with one extra row examined to detect another page; no total count is calculated. Pagination is not a snapshot of concurrent changes.

Read a published entry

GET /api/v1/acme/objects/42
Authorization: Bearer YOUR_API_KEY

Returns the same full, unwrapped object shape as creation. A missing, foreign, draft, non-JSON or file object returns 404.

Use ?locale=nl-NL to read that language in data. A missing translation returns data: null, without fallback. ?locale=* returns the base language in data and a translations object keyed by locale. JSON relationships and file IDs are returned as stored, without expansion or generated download URLs.

Reads resolve the current effective contract without rewriting content or running validation. validation.issues contains stored diagnostics; pending and failed explain whether those diagnostics are awaiting a newer check or a check failed. checked_at is the last completed check time. Opening a published object through the API does not acquire an editor lock or change its version.

Query native JSON

POST /api/v1/acme/objects/query
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
  "bucket": "general",
  "prefix": "products",
  "fields": ["name", "price", "dimensions.width"],
  "filter": {
    "@status": { "eq": "published" },
    "available": { "eq": true },
    "price": { "lt": 50 }
  },
  "sort": [{ "field": "price", "direction": "asc", "type": "number" }],
  "locale": "en-US",
  "offset": 0,
  "limit": 20
}

The response contains data, an array of full object envelopes, and pagination: { "offset": 0, "limit": 20, "hasMore": false }. Each envelope retains its IDs, reference, status, version and diagnostics; fields projects only its inner data. In this example, the inner data is { "name": "Inflatable pillow", "price": 24.95, "dimensions": { "width": 40 } }.

Queries include draft and published entries by default. The @status filter narrows that set. Neither a contract nor compatibility field definitions are needed. Filtering and sorting execute in the database before pagination. SQLite and MySQL are supported.

Property Behavior
bucket, bucket_id, ref, prefix Same selection rules as listing.
fields Up to 100 data property paths. Omitted means the complete selected-language document; [] returns {}. Missing properties return null. Parent and child selections cannot overlap.
filter JSON object of conditions, combined with AND. {} or omission means no additional conditions.
sort Up to five { "field", "direction", "type"? } objects. Direction is asc or desc. Optional type is string, number, or boolean.
locale One configured locale, default en-US. Applies to filters, sorting and response data; no wildcard or implicit fallback.
offset Default 0; maximum 10,000.
limit Default 20; range 1–100.

Data paths address nested JSON object properties, such as dimensions.width. Property names support letters, digits, hyphens and underscores, with up to 12 dot-separated steps and 256 characters. Arrays and objects can be selected as whole values. Paths do not traverse relationship IDs, array indexes, or property names containing dots.

Metadata filters and sorting use @id (numeric object ID), @entry_id (ULID), @bucket_id, @ref, @status, @lock_version, @created_at, and @updated_at. A data property named status remains separate from publication status @status. Metadata is always included in the envelope and does not belong in fields.

Operator Behavior
eq, ne Compare same-type JSON scalars. Numbers and decimals are compatible; strings, numbers and booleans remain distinct. null matches missing or null; ne: null requires a present non-null value. Other ne operands exclude missing/null values and other types.
lt, lte, gt, gte Numeric comparison for numbers or case-sensitive lexical comparison for strings, including consistently formatted ISO dates.
in OR-match 1–100 scalar operands using eq semantics.
startsWith, endsWith, contains Case-sensitive literal string matching. Empty strings and surrounding whitespace are preserved; % and _ are not wildcards.
$and, $or Nonempty lists of condition objects, for example { "$or": [{ "price": { "lt": 20 } }, { "sale": { "eq": true } }] }.

Missing/null sort values come last in either direction. Explicit sorts have an object-ID ascending tie-breaker. Without a type hint, mixed scalars sort by type (boolean, number, string) and then value; descending reverses this order. Typed sorts reject non-null values of other types. Numeric sorts require exact DECIMAL(38,12) values. Whole arrays and objects cannot be sorted.

The query body is limited to 64 KiB, with at most eight nested filter groups and 100 groups/operators/in operands. Arrays must be JSON arrays and filters must be JSON objects. Unsupported operators, malformed JSON, invalid locales, excessive limits and unknown properties return 422 with an error path. Array membership filters, relationship expansion, aggregations and grouped reporting are not part of this endpoint; existing typed integrations can continue using entry queries.

Update an entry

PATCH /api/v1/acme/objects/42
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
  "ref": "products/inflatable-pillow",
  "lock_version": 0,
  "data": { "name": "Updated pillow", "price": 29.95 }
}

Supply the current lock_version and the complete replacement data object. Return the saved reference in ref, or supply path instead to place the entry in a folder. Omitted status and bucket are preserved. Moving a migrated entry to another bucket is rejected. The response increments lock_version; a stale version returns 422.

Omitted contract preserves the current choice; null selects Automatic, false selects No contract, and a name selects that workspace contract explicitly. Contract validation checks every saved language.

Optional translations replaces the entire translation map, for example { "nl-NL": { "name": "Kussen" } }. Omission preserves translations; {} removes them. Translation values must be JSON objects keyed by configured locales other than en-US. Creation does not accept translations; add them with an update. Publication status remains shared across languages.

Delete an entry

DELETE /api/v1/acme/objects/42
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{ "lock_version": 1 }

Returns 204 with no body. Obtain the current version from a read or query first. Missing/negative versions or stale versions return 422 without deleting anything. A pending review also blocks deletion. Missing, foreign and file objects return 404; legacy linked form submissions are read-only and return 403.

Deletion removes the entry from normal listings, queries and delivery immediately, and moves it to Trash for 30 days. Both native and converted JSON entries use recoverable deletion. Related entries and files are retained, including fields previously configured to cascade; deleting a JSON entry no longer deletes its dependencies. Comments and tags are retained for recovery. An audit record and entry-deleted event retain workspace ownership, and relationship-bearing contracts are queued for revalidation. Files and form submissions retain their separate deletion workflows.

Revisions, comparison and recovery

Successful changes automatically preserve JSON content, translations, locale selection, bucket/reference, contract selection, publication status and scheduled publication metadata. Changes through Objects, the editor, and compatibility Entries writers are covered, including pipeline and AI changes made through those writers. No-op content saves do not create duplicate snapshots. Revision IDs increase over time; lock_version remains an independent concurrency token.

Existing content is captured before its first change after this feature is installed. History cannot reconstruct earlier changes or recover entries deleted before installation. Opening history is read-only. Contracts themselves, file binaries, tags and comments are not versioned by content revisions; Trash separately retains existing tags and comments for recovery.

Access

History includes drafts, old values and deleted content. It therefore requires the dedicated entries:history scope, rather than entries:read. The key creator must retain list and view entry permissions. Restore also requires entries:write and the creator's create/update permissions. Wildcard keys include these abilities. All operations remain workspace-scoped and require Objects activation.

List and read revisions

GET /api/v1/acme/objects/42/revisions?per_page=25
GET /api/v1/acme/objects/42/revisions/123
Authorization: Bearer YOUR_API_KEY

Lists return a cursor-paginated { "data": [...], "next_page_url": ... } response, newest first. per_page defaults to 25 and is limited to 100. Follow the returned next-page URL. List rows include id, object_id, entry_id, event, restored_from, actor, source and created_at; they omit the content snapshot. The detail endpoint returns the same metadata plus snapshot, including data and translations, as an unwrapped JSON object. JSON objects, arrays, nulls, booleans, numbers and strings retain their types.

Sources identify user, API, system, queued job, pipeline or AI operations where the writer supplies that context; API key identity is retained internally without recording its secret. A baseline records the state when history began, not an inferred original author or historical timestamp. Submissions and files do not have entry revision endpoints.

Compare two revisions

GET /api/v1/acme/objects/42/revisions/compare?from=123&to=124
Authorization: Bearer YOUR_API_KEY

Returns { "from": 123, "to": 124, "changes": [...], "truncated": false }. Each change has path, operation (added, removed, changed), before and after. Paths are JSON Pointers: literal slashes and tildes are escaped, and absent values remain distinct from explicit null through the operation. Nested objects are compared by property; arrays are compared as whole ordered values. At most 500 differences are returned. If truncated, read each revision's complete snapshot. Both revisions must belong to the requested object and workspace.

Restore an earlier version

POST /api/v1/acme/objects/42/restore
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "revision_id": 123, "lock_version": 7 }

Restores content, translations/locales, bucket/reference and contract selection as a new revision, with restored_from pointing to the selected revision. History is never rewritten. Current publication status and schedule are retained: restoring an already published entry changes the content delivered to connected apps immediately. This feature does not introduce a separate working draft beside live content.

The current contract rules and relationships must accept every restored locale. Pending reviews, another editor's lock on a web request, missing buckets/content types/contracts, stale versions and occupied references block restore. A failed restore changes neither content nor history. A revision never replaces an unrelated entry.

Recover a deleted entry

GET /api/v1/acme/objects/trash?per_page=25
Authorization: Bearer YOUR_API_KEY

Trash is cursor-paginated, newest deletion first. Rows include object_id, entry_id, bucket_id, ref, lock_version, deleted_at and expires_at, without document bodies. Use the Trash row's version token, not the entry's former token:

POST /api/v1/acme/objects/42/restore
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "lock_version": 8 }

Omit revision_id to recover the deleted content, or supply a revision belonging to that object to recover an older version. Recovery preserves the original object and entry IDs, restores retained tags and comments, and always returns the entry as a draft with no publication schedule. It does not automatically republish it. Related data can change or be deleted independently; references do not restore deleted file binaries or other objects.

Successful restore/recovery returns { "id", "entry_id", "ref", "lock_version", "status" }. Versions continue increasing across deletion and recovery. Missing, foreign, expired or already-purged objects return 404. Missing/stale tokens, reference conflicts and current validation failures return 422. Restore must succeed atomically before Trash is removed.

Expired items stop appearing in Trash and history and cannot be recovered even if the cleanup job has not yet run. The hourly objects:prune-trash task permanently removes expired archives, their revisions and retained comments in batches of at most 100 objects with a 20-second run budget. Active object history is retained until the object is deleted and its recovery window expires. Workspace deletion removes its history immediately; bucket/type deletion is not an object recovery operation and may make an old version unrestorable.