# jaydb HTTP API

One file, everything needed to use the API. No SDK required — curl is a complete
client, and so is the browser's own `fetch`. For browser and frontend apps,
an official zero-dependency SDK (`@jaydb/cloud`) provides automated OIDC PKCE
and typed CAS document operations.

- Base URL: `https://{tenant}.jaydb.com`
- Every path is prefixed `/v1/n/{namespace}/docs`
- Every request carries one of the two credentials below
- 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.

---

## Authentication

The data plane accepts two credentials. Which one you use depends on whether a
server of yours is involved at all.

| Credential | Header | Use it for |
|---|---|---|
| OIDC bearer token | `Authorization: Bearer <jwt>` | Frontend-only browser apps. Scoped to the signed-in user. |
| API key | `X-JayDB-API-Key: <key>` | Server-side and REST integrations, where the secret stays server-side. |

A backend is **optional, never required to start**. The bearer path is what makes
a frontend-only app possible: each user signs in as themselves and the browser
receives its own token, so no shared secret ships in your JavaScript.

### Bearer tokens — sign-in from the browser

Every organization gets its own OIDC issuer at `https://{tenant}.jaydb.com`,
publishing standard discovery metadata and a JWKS under that issuer. Browser
clients use **Authorization Code + PKCE**, so no client secret is required.

Register the app first (console → App Registrations) as a public PKCE client with
its client ID, redirect URIs, and allowed origins. Registering the app is what
grants its origin cross-origin access to the data plane.

**1. Redirect to the authorize endpoint**

```
GET https://acme.jaydb.com/oauth/v2/authorize
  ?response_type=code
  &client_id=my-app
  &redirect_uri=https://myapp.example/callback
  &scope=openid%20profile%20email
  &code_challenge=<S256 challenge>
  &code_challenge_method=S256
```

**2. Exchange the code for a token**

```http
POST /oauth/v2/token
Host: acme.jaydb.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<code from the redirect>
&client_id=my-app
&redirect_uri=https://myapp.example/callback
&code_verifier=<the original verifier>
```

**3. Call the data plane with the token**

```bash
curl https://acme.jaydb.com/v1/n/app/docs/users/101 \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Org admins choose which upstream identity providers are offered — Google, GitHub,
Microsoft, any generic OIDC provider, or a custom OAuth2 provider.

### API keys — server-side

An API key is a long-lived secret scoped to an organization and namespace. Send
it as `X-JayDB-API-Key`. Because it cannot be scoped to a single end user and
carries no read/write distinction, **do not ship one in a static frontend** — use
the bearer path for that.

Every example below shows `X-JayDB-API-Key` for brevity; substitute
`Authorization: Bearer <jwt>` unchanged wherever a browser app is the caller.

---

## 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, invalid, or expired credential (bearer token or API key) |
| 403 | Request did not target a tenant subdomain, or the token is not scoped to this namespace |
| 404 | No such document |
| 412 | `If-Match` / `If-None-Match` did not hold — re-read and retry |
| 413 | Request body over the size limit |

---

## Browser notes

- The `ETag` response header is exposed cross-origin, so browser JavaScript can
  read it and use it for compare-and-swap. (Without that exposure a cross-origin
  read returns `null` for the header even though the server sent it.)
- Cross-origin access is granted by registering the app's origin as an allowed
  origin on its OIDC app registration — not by an operator-managed allowlist.
- There is no subscription, SSE, or websocket. Clients poll. `list` returns a
  per-key `etag`, so a poll loop can diff and re-read only what changed.


---

## 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/jaydb-cloud/jaydb

## Official Frontend SDK

For browser and JAMstack applications, use **`@jaydb/cloud`** (zero dependencies, ~12 KB minified):

- **Package**: `npm i @jaydb/cloud`
- **CDN (ESM)**: `https://jaydb-cloud.github.io/jaydb-cloud-sdk/jaydb-cloud.esm.min.js`
- **Docs**: https://jaydb-cloud.github.io/jaydb-cloud-sdk/
- **Source**: https://github.com/jaydb-cloud/jaydb-cloud-sdk

## Example application

- **Kanban Demo App (Live)**: https://jaydb-cloud.github.io/jaydb-kanban-demo/
- **Kanban Demo App (Source)**: https://github.com/jaydb-cloud/jaydb-kanban-demo


