Writing documents
Put, update and delete documents, in batches, with the durability, visibility and retry rules that apply to every write.
A document is {"id", "text", "meta", "vector"}. You write documents to a namespace by id:
writing an id again replaces the whole document. One request can put and delete together, and
it is atomic. This page covers every way to write, and what happens to a write after you send
it.
Put and delete
POST /v1/namespaces/{name}/docs puts (inserts or replaces) documents and deletes ids in one
request: 1 to 10,000 documents and deletes, 64 MB at most.
B=https://api.skimmerdb.com/v1
curl -s $B/namespaces/support/docs -H "Authorization: Bearer $KEY" -d '{
"docs": [
{"id": "t-3", "text": "Webhook timeouts since Tuesday", "meta": {"team": "api", "status": "open"}}
],
"deletes": ["t-2"]
}'
# {"written": 1, "deleted": 1, "version": 3, "stored_bytes": 2817, "created": false}from skimmerdb import SkimmerDB
db = SkimmerDB() # SKIMMERDB_KEY from the environment
ns = db.namespace("support")
r = ns.write(
[{"id": "t-3", "text": "Webhook timeouts since Tuesday", "meta": {"team": "api", "status": "open"}}],
deletes=["t-2"],
)
print(r.written, r.deleted)import { SkimmerDB } from "skimmerdb";
const db = new SkimmerDB(); // SKIMMERDB_KEY from the environment
const ns = db.namespace("support");
const r = await ns.write(
[{ id: "t-3", text: "Webhook timeouts since Tuesday", meta: { team: "api", status: "open" } }],
{ deletes: ["t-2"] },
);
console.log(r.written, r.deleted);- Within one request, deletes apply before puts, so deleting and putting the same id leaves the new document.
textmay be left out when the document has a vector.metais a JSON object of strings, numbers and booleans, up to 16 KB; see Data model for how to shape it.- The answer:
writtenanddeleted(counts),version(the namespace's version after the write),stored_bytes, andcreated(truewhen this write made the namespace).
Update
PATCH /v1/namespaces/{name}/docs changes existing documents without resending them whole:
text is replaced if given, meta is merged key by key, and a null value removes a key. A
vector is kept unless the patch gives a new one.
curl -s -X PATCH $B/namespaces/support/docs -H "Authorization: Bearer $KEY" \
-d '{"docs": [{"id": "t-1", "meta": {"status": "closed", "priority": null}}]}'ns.update([{"id": "t-1", "meta": {"status": "closed", "priority": None}}])await ns.update([{ id: "t-1", meta: { status: "closed", priority: null } }]);Updating a namespace that doesn't exist is a 404 (only puts create namespaces).
Delete
By id, with POST /v1/namespaces/{name}/delete:
curl -s $B/namespaces/support/delete -H "Authorization: Bearer $KEY" -d '{"ids": ["t-3"]}'ns.delete(["t-3"])await ns.delete(["t-3"]);To delete everything, drop the namespace: DELETE /v1/namespaces/{name} (ns.drop()). Its
documents are deleted from storage right after.
Read one document
GET /v1/namespaces/{name}/docs/{id} returns the whole document: id, text, meta,
written_at, and the vector if it has one. Ids may hold slashes:
GET .../docs/agent-1/run-42/step-3.
curl -s $B/namespaces/support/docs/t-1 -H "Authorization: Bearer $KEY"
# {"id": "t-1", "text": "...", "meta": {...}, "written_at": "..."}doc = ns.get("t-1")const doc = await ns.get("t-1");Creating namespaces
The first write to a name that doesn't exist makes the namespace and answers
"created": true, so a mistyped name shows. If the documents have vectors, the namespace gets
vectors of the first vector's length, with the cosine metric; otherwise it's text only.
To choose the vector settings yourself (for example the euclidean metric), create it first:
curl -s $B/namespaces -H "Authorization: Bearer $KEY" \
-d '{"name": "notes", "vector": {"dims": 768, "metric": "euclidean"}}'notes = db.create_namespace("notes", dims=768, metric="euclidean", exist_ok=True)const notes = await db.createNamespace("notes", { dims: 768, metric: "euclidean", existOk: true });dims (1 to 2,048) and metric (cosine, the default, or euclidean) are fixed for the
namespace's life. See Vector and hybrid search.
Durability and visibility
- Durable when answered. A write is answered once it is stored in object storage. Writes from every tenant on a server are logged together every 250 ms, so a write takes about that long.
- Searchable at once on the server that took it, and on every server within about a second.
"wait": trueanswers only once the write is searchable on that server. Use it when the next step reads what you just wrote (a test, an agent that writes then searches); leave it off otherwise.- Atomic. A request's documents and deletes become visible together or not at all.
More in Durability.
Batching
Many small requests work, since the server groups them every 250 ms anyway, but each one counts against your rate limit (100 requests a second on free, 1,000 on pro, per server). A batch of up to 10,000 documents is one request and one atomic entry.
For files of rows (CSV, NDJSON, Parquet), use import instead: up to 1,000,000 rows a request, parsed straight into columns.
Tip Append-only data (events, logs, traces written once) is the cheapest shape to store, compact and query. Updates are fine, but each one writes a new version and the old one waits for compaction.
Errors and retries
Writes are by id, so resending the same batch is always safe: it replaces what may already be there.
| Answer | Meaning | What to do |
|---|---|---|
429 write_busy | Writes to this namespace are arriving faster than they commit. Nothing was written. | Wait Retry-After seconds (also retry_after in the body), resend the same batch. |
429 rate_limited | Over the tenant's or the key's rate, concurrency or cap. | Wait Retry-After seconds. |
503 unavailable | Object storage failed. Nothing was written. | Retry. |
503 write_unknown | The write may or may not have been applied. | Retry it: the same ids replace whatever is there. |
413 too_large | Over 10,000 documents or 64 MB. | Split the batch. |
422 invalid | Something in the body is wrong (fields says what), e.g. a vector of the wrong length. | Fix the request. |
507 quota_full | The tenant's storage quota is full. | Delete data or upgrade. |
The Python and TypeScript clients retry 429 and 503 answers for you (three times, after
Retry-After or backing off), then raise SkimmerDBError with status, code and the
message. All codes: Errors.