# jaydb HTTP API

One file, everything needed to use the API. No SDK required — curl is a complete
client.

- Base URL: `https://{tenant}.jaydb.com`
- Every path is prefixed `/v1/n/{namespace}/docs`
- Every request carries `X-JayDB-API-Key: <key>`
- Requests to a non-tenant host are rejected with 403

A **namespace** is a bucket of documents and is created implicitly by the first
write. A **key** is a `/`-separated path such as `users/101` or
`boards/42/cards/7`. A **document** is any JSON value.

---

## Read

```http
GET /v1/n/app/docs/users/101
X-JayDB-API-Key: <key>
```

```
200 OK
ETag: "3f9a1c72"

{"name":"Alice","plan":"free"}
```

`404` if the key does not exist. Keep the `ETag` if you intend to write back.

```bash
curl https://acme.jaydb.com/v1/n/app/docs/users/101 \
  -H "X-JayDB-API-Key: $JAYDB_KEY"
```

---

## Write

```http
PUT /v1/n/app/docs/users/101
X-JayDB-API-Key: <key>
Content-Type: application/json

{"name":"Alice","plan":"pro"}
```

```
200 OK
ETag: "8c1e04b5"

{"status":"ok","key":"users/101","etag":"8c1e04b5","mod_time":"2026-08-10T11:04:22Z"}
```

Unconditional writes overwrite. Use one of the guards below to avoid that.

### Create only

Fails if the key already exists.

```http
PUT /v1/n/app/docs/users/101
If-None-Match: *
```

`412 Precondition Failed` when it already exists.

### Compare-and-swap

Writes only if the document is still at the version you read.

```http
PUT /v1/n/app/docs/users/101
If-Match: "3f9a1c72"
```

`412 Precondition Failed` when another writer got there first. Re-read and retry.
Surrounding quotes are accepted and stripped, so passing the `ETag` header back
verbatim works.

```bash
curl -X PUT https://acme.jaydb.com/v1/n/app/docs/users/101 \
  -H "X-JayDB-API-Key: $JAYDB_KEY" \
  -H 'If-Match: "3f9a1c72"' \
  -d '{"name":"Alice","plan":"pro"}'
```

---

## Delete

```http
DELETE /v1/n/app/docs/users/101
X-JayDB-API-Key: <key>
```

Accepts `If-Match: <etag>` for a conditional delete. Deletes are never billed.

---

## List

```http
GET /v1/n/app/docs?list&prefix=users/&limit=100
X-JayDB-API-Key: <key>
```

```json
{
  "items": [
    { "key": "users/101", "etag": "8c1e04b5", "mod_time": "...", "size": 1204 },
    { "key": "users/102", "etag": "1a77de90", "mod_time": "...", "size": 980 }
  ],
  "next_cursor": "dXNlcnMvMTAy"
}
```

`limit` defaults to 100 and caps at 1000. When `next_cursor` is present, pass it
back as `&cursor=` to continue. Listing returns metadata only — fetch each key to
read its body.

---

## Status codes

| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Malformed path, or an invalid JSON body |
| 401 | Missing or invalid API key |
| 403 | Request did not target a tenant subdomain |
| 404 | No such document |
| 412 | `If-Match` / `If-None-Match` did not hold — re-read and retry |
| 413 | Request body over the size limit |

---

## Read-modify-write with retry

The whole concurrency story in one function.

```js
const BASE = 'https://acme.jaydb.com/v1/n/app/docs';
const headers = { 'X-JayDB-API-Key': process.env.JAYDB_KEY };

async function update(key, mutate, attempts = 5) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(`${BASE}/${key}`, { headers });
    const doc = res.ok ? await res.json() : {};

    const guard = res.ok
      ? { 'If-Match': res.headers.get('ETag') }
      : { 'If-None-Match': '*' };

    const put = await fetch(`${BASE}/${key}`, {
      method: 'PUT',
      headers: { ...headers, ...guard },
      body: JSON.stringify(mutate(doc)),
    });

    if (put.ok) return put.json();
    if (put.status !== 412) throw new Error(await put.text());
  }
  throw new Error(`too much contention on ${key}`);
}
```

---

## Consistency and durability

- A read after your own successful write returns that write.
- Documents are held in object storage; the hot tier is a bounded cache in front
  of it.
- Compare-and-swap is the only coordination primitive. There are no
  multi-document transactions.

## Limits

- No secondary indexes; no querying by field value. Access is by key or prefix.
- No joins, no aggregation, no full-text search.
- A single key under sustained concurrent writes will spend its time retrying.

## Pricing

| Dimension | Rate | Metered as |
|---|---|---|
| Storage | $0.05 / GB-month | per megabyte-hour |
| Reads | $0.20 / million | per single read |
| Writes | $0.50 / million | per single write |
| Deletes | free | — |
| Egress | included | fair use |

Free on every account, every month: 1 GB stored, 100,000 reads, 10,000 writes.
No monthly floor, no per-seat charge.

Optional prepaid packs, a discount on the same meter rather than a tier that
gates capability: $5 prepays $6.50 of usage, $10 prepays $15.

## Engine

Open source, MIT licensed: https://github.com/avivklas/jaydb
