API reference
Slabs
Register, read, list, unpin, and prune erasure-coded slabs — the layer that records which provider holds which shard.
A slab is one erasure-coded stripe. Registering it is how Tessera learns where your shards are, and therefore how repair and retrieval become possible.
POST /slabs
Registers one or more slabs. The body is an array, so a multi-slab object needs one call rather than several.
curl -X POST "$TESSERA_API/slabs?$AUTH" \
-H 'content-type: application/json' \
-d '[{
"version": 1,
"minShards": 10,
"encryptionKey": "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=",
"sectors": [
{ "host": "ed25519:9f3c…", "root": "22ab…", "contractID": "a1b2…" }
]
}]'
Slab object
| Field | Type | Description |
|---|---|---|
version | integer | Slab format version. Use 1. |
minShards | integer | Shards required to reconstruct. |
encryptionKey | base64 | Exactly 32 bytes. Standard base64 with padding. |
sectors | array | One entry per shard, in shard order. Order is significant — it is the erasure-coding index. |
Sector object
| Field | Type | Description |
|---|---|---|
host | string | Provider public key, ed25519: + 64 hex characters. |
root | string | Merkle root of the sector, 64 hex characters. |
contractID | string | Contract the sector was written under. |
Response — 200 OK
["4f2a1b8c9d3e...", "7b1c2d3e4f5a..."]
Hex slab IDs, in the same order as the request.
Slab IDs are content-addressed
The ID is a digest of the slab's parameters, so registering identical parameters twice returns the same ID rather than creating a duplicate. Usefully, the digest does not include provider identities — repair can relocate a shard without changing the ID, so objects referencing the slab never need updating.
Validation — all of these are rejected at registration:
| Rule | Error |
|---|---|
encryptionKey is not exactly 32 bytes | manifest: invalid erasure coding params |
| Redundancy below 1.5× or above 4.0× | manifest: invalid erasure coding params |
| The same provider appears twice | manifest: invalid erasure coding params |
| The same sector root appears twice | manifest: invalid erasure coding params |
| No account on the request | manifest: empty account |
Errors
| Status | Body | Cause |
|---|---|---|
400 | invalid request body: ... | Malformed JSON. |
400 | manifest: invalid erasure coding params | See validation above. |
402 | {"error": "storage cap exceeded", …} | Registration would exceed your storage cap. |
405 | method not allowed | Wrong method. |
Cap accounting charges 4 MiB per sector at registration, which is the sector size regardless of how
full the sector is. Packing small objects into shared slabs is therefore materially cheaper than one
slab per small object.
GET /slabs/{id}
Reads a slab: everything needed to retrieve and reconstruct it.
curl "$TESSERA_API/slabs/4f2a1b8c9d3e...?$AUTH"
{
"id": "4f2a1b8c9d3e...",
"version": 1,
"minShards": 10,
"encryptionKey": "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=",
"sectors": [
{ "host": "ed25519:9f3c…", "root": "22ab…", "contractID": "a1b2…" }
],
"createdAt": "2026-04-02T09:14:00Z"
}
The sectors array reflects current placement. If repair has moved a shard since you wrote it,
you will see the new provider here — which is the reason to re-read the slab rather than cache
placement indefinitely.
| Status | Body | Cause |
|---|---|---|
400 | invalid slab id: ... | Not 64 hex characters. |
403 | manifest: forbidden for this account | The slab belongs to another account. |
404 | manifest: not found | No such slab, for you. |
GET /slabs
Lists your slab IDs.
curl "$TESSERA_API/slabs?limit=50&offset=0&$AUTH"
| Parameter | Default | Description |
|---|---|---|
limit | 50 | Maximum IDs returned. |
offset | 0 | Number to skip. |
["4f2a1b8c...", "7b1c2d3e...", "9e8d7c6b..."]
Offset pagination drifts
offset is not a stable cursor. If slabs are created or pruned between pages, entries can be missed
or repeated. For a complete enumeration — an export, for instance — walk
GET /objects with its after cursor instead and collect slab IDs from the
object records.
DELETE /slabs/{id}
Unpins a slab from your account.
curl -X DELETE "$TESSERA_API/slabs/4f2a1b8c...?$AUTH"
Response — 204 No Content.
| Status | Body | Cause |
|---|---|---|
409 | manifest: slab still referenced by an object | Delete the referencing objects first. |
403 | manifest: forbidden for this account | Not your slab. |
404 | manifest: not found | No such slab. |
The 409 is a guard rail, not an obstacle: it exists so that deleting a slab cannot silently corrupt
an object that still points at it.
POST /slabs/prune
Reclaims slabs no object references. The normal way to clean up after deleting objects.
curl -X POST "$TESSERA_API/slabs/prune?$AUTH"
{ "pruned": 3 }
Scoped to your account, so it can never affect anyone else's data. Safe to run on a schedule.
Packing strategy
A slab costs a 15-provider round trip to create and is accounted at 4 MiB per sector. That makes one-slab-per-small-object expensive on both counts.
The model is built for packing. Buffer small writes, concatenate them, erasure-code the buffer once, and give each logical object a byte range in the resulting slab:
// Three objects sharing one slab
{ id: objA, slabs: [{ id: slabID, offset: 0, length: 1_048_576 }] }
{ id: objB, slabs: [{ id: slabID, offset: 1_048_576, length: 2_097_152 }] }
{ id: objC, slabs: [{ id: slabID, offset: 3_145_728, length: 524_288 }] }
Each object is retrieved independently; they simply happen to share a stripe. The trade-off is that the objects share a fate — the slab is a unit for repair and for deletion — so pack objects with similar lifetimes together.