API reference
Objects
Register, read, list, and delete objects — ordered lists of slab segments with encrypted keys and opaque metadata.
An object is the addressable unit of your data: an ordered list of slab segments, plus wrapped key material and an encrypted metadata blob Tessera never interprets.
POST /objects
Registers or replaces an object. Idempotent on id — posting the same key again replaces the record,
which is how you update an object.
curl -X POST "$TESSERA_API/objects?$AUTH" \
-H 'content-type: application/json' \
-d '{
"id": "8f14e45fce...",
"encryptedDataKey": "d3JhcHBlZC1kYXRhLWtleQ==",
"slabs": [{ "id": "4f2a1b8c...", "offset": 0, "length": 1048576 }],
"encryptedMetadata": "ZW5jcnlwdGVkLW1ldGFkYXRh",
"encryptedMetadataKey": "d3JhcHBlZC1tZXRhZGF0YS1rZXk=",
"dataSignature": "9f3c1a4b...",
"metadataSignature": "22ab7c1d..."
}'
| Field | Type | Required | Description |
|---|---|---|---|
id | hex, 32 bytes | yes | The object key. You choose it. |
encryptedDataKey | base64 | yes | The object's data key, wrapped by your key. |
slabs | array | yes | Ordered slab segments. Concatenated in order, these are the object. |
dataSignature | hex, 64 bytes | yes | Your signature over the data portion. |
encryptedMetadata | base64 | no | Opaque blob. Filenames, content types, anything. |
encryptedMetadataKey | base64 | no | Wrapped key for the metadata blob. |
metadataSignature | hex, 64 bytes | no | Your signature over the metadata. |
Slab segment
| Field | Type | Description |
|---|---|---|
id | hex | Slab ID from POST /slabs. |
offset | uint32 | Byte offset within the reconstructed slab. |
length | uint32 | Byte length of this segment. |
Response — 200 OK, empty body.
Register slabs first
Referencing a slab that does not exist is rejected with 400 manifest: object references missing slab. Always POST /slabs before POST /objects — the object record is a set of references, and
the references are checked.
Errors
| Status | Body | Cause |
|---|---|---|
400 | invalid request body: ... | Malformed JSON. |
400 | manifest: object references missing slab | A slab ID does not exist for your account. |
400 | manifest: empty account | No account on the request. |
402 | {"error": "storage cap exceeded", "capBytes": …} | Metadata registration would exceed your cap. |
405 | method not allowed | Wrong method. |
GET /objects/{key}
Reads an object's placement record.
curl "$TESSERA_API/objects/8f14e45fce...?$AUTH"
{
"id": "8f14e45fce...",
"encryptedDataKey": "d3JhcHBlZC1kYXRhLWtleQ==",
"slabs": [{ "id": "4f2a1b8c...", "offset": 0, "length": 1048576 }],
"encryptedMetadata": "ZW5jcnlwdGVkLW1ldGFkYXRh",
"encryptedMetadataKey": "d3JhcHBlZC1tZXRhZGF0YS1rZXk=",
"dataSignature": "9f3c1a4b...",
"metadataSignature": "22ab7c1d...",
"createdAt": "2026-04-02T09:14:00Z",
"updatedAt": "2026-04-02T09:14:00Z"
}
This returns metadata, not data. To get bytes: read the object, read each referenced slab, fetch
minShards shards per slab directly from providers, reconstruct, decrypt.
| Status | Body | Cause |
|---|---|---|
400 | missing object key | Empty key, or a key containing /. |
400 | invalid object key: ... | Not 64 hex characters. |
403 | manifest: forbidden for this account | The object belongs to another account. |
404 | manifest: not found | No such object, for you. |
Keys may not contain slashes
The key is parsed as a single path segment, so /objects/{key}/anything is rejected with 400, not
routed. There is no sub-resource under an object.
GET /objects
Lists object change events — a log, not a snapshot. This is the endpoint to use for synchronisation and for exports.
curl "$TESSERA_API/objects?limit=100&after=2026-04-02T09:14:00Z&$AUTH"
| Parameter | Default | Description |
|---|---|---|
limit | 50 | Maximum events returned. |
after | (none) | RFC 3339 timestamp. Returns only events strictly after it. |
[
{
"key": "8f14e45fce...",
"deleted": false,
"updatedAt": "2026-04-02T09:14:00Z",
"object": { "id": "8f14e45fce...", "slabs": [ ... ] }
},
{
"key": "3c59dc048e...",
"deleted": true,
"updatedAt": "2026-04-02T09:20:00Z"
}
]
| Field | Description |
|---|---|
key | The object key this event concerns. |
deleted | true for a tombstone. object is absent in that case. |
updatedAt | When the change occurred. Use the last one as your next after. |
object | The full record, for non-deletion events. |
Always an array, never null.
Iterating to completion
let after: string | undefined
const state = new Map<string, unknown>()
for (;;) {
const qs = new URLSearchParams({ limit: '100', ...(after ? { after } : {}) })
const events = await call('GET', `/objects?${qs}`)
if (!events.length) break
for (const ev of events) {
if (ev.deleted) state.delete(ev.key)
else state.set(ev.key, ev.object)
}
after = events.at(-1).updatedAt
}
Why a change log rather than a listing
Because the log lets you maintain a local index incrementally with a single cursor, and because tombstones let you learn about deletions you did not perform locally. Persist your cursor and synchronisation costs one call per interval regardless of how much you have stored.
Timestamp cursors and ties
The cursor is a timestamp, so events sharing a timestamp can straddle a page boundary and be seen twice. Make your event application idempotent — the code above is, because it sets and deletes by key rather than appending.
DELETE /objects/{key}
Deletes an object.
curl -X DELETE "$TESSERA_API/objects/8f14e45fce...?$AUTH"
Response — 204 No Content.
| Status | Body | Cause |
|---|---|---|
400 | missing object key / invalid object key | Bad key. |
403 | manifest: forbidden for this account | Not your object. |
404 | manifest: not found | No such object. |
What happens, precisely:
- The object is removed from the manifest immediately. It is no longer readable through the API and we can no longer locate it.
- A tombstone appears in
GET /objectsso other clients learn of the deletion. - Slabs it referenced are not deleted — another object may reference them. Run
POST /slabs/pruneto reclaim any that are now orphaned. - Shards on providers stop being retained as the covering storage contracts lapse.
Deletion is not instantaneous erasure
Step 4 is not immediate. Encrypted shards may persist on providers until reclamation completes or the contract expires — at most the contract term. What is immediate is the loss of the ability to locate and reconstruct them, since we discard the placement record. If your compliance requirement is provable erasure within a fixed window, discuss it with us rather than assuming this satisfies it.
Updating an object
There is no PATCH. To update, write new slabs and POST /objects with the same id — the record
is replaced. Then prune to reclaim the slabs the previous version referenced, if nothing else uses
them.
Because objects reference immutable, content-addressed slabs, an update that changes one region of a large object only needs to rewrite the slabs covering that region. Keep the segment list aligned to your natural write boundaries and partial updates stay cheap.