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..."
  }'
FieldTypeRequiredDescription
idhex, 32 bytesyesThe object key. You choose it.
encryptedDataKeybase64yesThe object's data key, wrapped by your key.
slabsarrayyesOrdered slab segments. Concatenated in order, these are the object.
dataSignaturehex, 64 bytesyesYour signature over the data portion.
encryptedMetadatabase64noOpaque blob. Filenames, content types, anything.
encryptedMetadataKeybase64noWrapped key for the metadata blob.
metadataSignaturehex, 64 bytesnoYour signature over the metadata.

Slab segment

FieldTypeDescription
idhexSlab ID from POST /slabs.
offsetuint32Byte offset within the reconstructed slab.
lengthuint32Byte length of this segment.

Response200 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

StatusBodyCause
400invalid request body: ...Malformed JSON.
400manifest: object references missing slabA slab ID does not exist for your account.
400manifest: empty accountNo account on the request.
402{"error": "storage cap exceeded", "capBytes": …}Metadata registration would exceed your cap.
405method not allowedWrong 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.

StatusBodyCause
400missing object keyEmpty key, or a key containing /.
400invalid object key: ...Not 64 hex characters.
403manifest: forbidden for this accountThe object belongs to another account.
404manifest: not foundNo 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"
ParameterDefaultDescription
limit50Maximum 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"
  }
]
FieldDescription
keyThe object key this event concerns.
deletedtrue for a tombstone. object is absent in that case.
updatedAtWhen the change occurred. Use the last one as your next after.
objectThe 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"

Response204 No Content.

StatusBodyCause
400missing object key / invalid object keyBad key.
403manifest: forbidden for this accountNot your object.
404manifest: not foundNo such object.

What happens, precisely:

  1. The object is removed from the manifest immediately. It is no longer readable through the API and we can no longer locate it.
  2. A tombstone appears in GET /objects so other clients learn of the deletion.
  3. Slabs it referenced are not deleted — another object may reference them. Run POST /slabs/prune to reclaim any that are now orphaned.
  4. 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.