my-database-api
$
npx mdskill add myapihq/myapi/my-database-apiManage per-org KV store with namespaces, JSON values, and compare-and-swap via etags.
- Store and retrieve JSON values up to 256 KB per key.
- Depends on MyAPI organization-scoped KV primitives.
- Uses etags for compare-and-swap to prevent write conflicts.
- Returns JSON values with etags for stateful agent workflows.
SKILL.md
.github/skills/my-database-apiView on GitHub ↗
---
name: my-database-api
version: 1.0.0
description: >
Per-org KV store with named namespaces, JSON values up to 256 KB, prefix-scan listing, and compare-and-swap via etag. The substrate for any stateful agent-built app on MyAPI — user tables, session stores, idempotency keys, per-user lookup maps.
triggers: [database, kv, key value, namespace, store, state, etag, cas, session, idempotency]
checksum: sha256-pending
---
# MyDatabaseAPI
A small KV primitive scoped to your MyAPI org. Two resources: namespaces (containers) and keys (the actual values). Values are JSON; binary belongs in `myapi storage`. Compare-and-swap via etags is supported on day one.
## Capabilities
<!-- llm:start -->
Use this when you need **state behind a workflow or a function** — the user table for a SaaS the agent is building on MyAPI primitives, a per-customer settings store, idempotency keys for webhook handlers, a session cache. Anything where Redis is overkill but raw env-var configuration isn't enough.
### Model
- An **organization** owns many **namespaces**
- A **namespace** owns many **keys**
- A **key** points to one JSON **value** (≤ 256 KB serialized)
- Every value has an opaque **etag** that changes on every successful write
### Limits
- **Key** ≤ 512 bytes (UTF-8)
- **Value** ≤ 256 KB serialized JSON. For blobs/binary, use `myapi storage`.
- **Namespace name** matches `^[a-z0-9][a-z0-9_-]{0,62}$`; the `__myapi_` prefix is reserved.
### Compare-and-swap
Pass `--if-match <etag>` from a previous `get` to make `set` or `del` conditional. Returns an error if another writer changed the value between your read and write. Without `--if-match`, last-write-wins.
### Listing
- Default: keys only, one per line
- `--values` adds inline values + etags (cost: a bigger response payload)
- `--prefix foo` for prefix-scoped listing
- `--limit N` (1–1000, default 50)
- Paginates via `--cursor` — the previous response's `next_cursor`
### Failure modes
- `412 ETAG_MISMATCH` on CAS conflict (CLI surfaces it as a friendly error)
- `404 NAMESPACE_NOT_FOUND` / `KEY_NOT_FOUND`
- `409 NAMESPACE_EXISTS` on re-create
- `400 VALUE_NOT_JSON` if the body's `value` isn't valid JSON
- `413 VALUE_TOO_LARGE` if you exceed 256 KB (CLI also pre-flight checks)
<!-- llm:end -->
## Commands
<!-- generated:start -->
| Command | What it does |
|---|---|
| `myapi database namespaces [--json]` | List namespaces in the org |
| `myapi database create <name>` | Create a namespace |
| `myapi database delete-namespace <name>` | Delete namespace AND all its keys (irreversible) |
| `myapi database keys --ns <ns> [--prefix <p>] [--limit N] [--values] [--cursor <c>]` | List keys, optionally with inline values |
| `myapi database get <key> --ns <ns>` | Get value + etag (etag printed to stderr) |
| `myapi database set <key> <value-json> --ns <ns> [--if-match <etag>] [--file <path>]` | Set key. CAS via --if-match |
| `myapi database del <key> --ns <ns> [--if-match <etag>]` | Delete key. CAS via --if-match |
<!-- generated:end -->
Pass `-` as `<value-json>` to read the value from stdin, or `--file <path>` to read from a file.
## Examples
<!-- llm:start -->
```bash
# Create a namespace, set a key, read it back
myapi database create my-app
myapi database set user:alice '{"plan":"pro","trial_ends":"2026-06-01"}' --ns my-app
myapi database get user:alice --ns my-app
# stdout: {"plan":"pro","trial_ends":"2026-06-01"}
# stderr: — etag=A1B2C3 · updated=2026-05-12T17:00:00Z
# List keys with a prefix
myapi database keys --ns my-app --prefix user: --values
# Compare-and-swap update
ETAG=$(myapi database get user:alice --ns my-app --json | jq -r .etag)
myapi database set user:alice '{"plan":"enterprise"}' --ns my-app --if-match "$ETAG"
# Idempotency keys for a webhook handler
myapi database set "delivery:$DELIVERY_ID" 'true' --ns idempotency --if-match '"00000000"' \
|| { echo "already processed"; exit 0; }
```
### End-to-end recipe — user table for an agent-built SaaS
```bash
# Bootstrap a "users" namespace
myapi database create users
# Sign-up handler: webhook receives { email, name } → store the user
myapi database set "by-email:$EMAIL" "$(jq -nc --arg n "$NAME" '{name:$n, plan:"trial"}')" \
--ns users
# Login handler: look up by email
myapi database get "by-email:$EMAIL" --ns users --json | jq -r .value
```
<!-- llm:end -->
## Notes
- **No transactions across keys.** Each key is independently atomic; multi-key updates aren't atomic. Use a single composite-JSON value if you need joint atomicity.
- **No secondary indexes today.** Prefix scans are the only query primitive. Design your keys for the access pattern (`user:by-email:[email protected]`, `user:by-plan:pro:[email protected]`, etc.).
- **No TTL today.** Keys live until deleted. If you need expiry, store `expires_at` in the value and let your reader drop stale rows.
- **Eventual `key_count`.** The `keys` field on a namespace is approximate; don't use it for strict pagination math.
- **Free in v1.** Metered later if usage shows a need. Cost discipline still applies — store data, not blobs.
Run `myapi database --help` for inline reference.