PortModels
Log in

Key/value store

Small per-user JSON values scoped to your app — settings, state, and caches.

A per-user JSON store scoped to your app. Use it for settings, saved state, drafts, and small caches — the things that should survive between sessions without you running a database.

Requires the kv.read and kv.write scopes.

Store a value#

bash
curl -X PUT https://api.portmodels.com/connect/kv/preferences \
  -H "Authorization: Bearer pmc_..." \
  -H "Content-Type: application/json" \
  -d '{"value": {"theme": "dark", "volume": 7}}'

The body must be a JSON object with a value field. value itself can be any JSON — object, array, string, number, boolean.

Read a value#

bash
curl https://api.portmodels.com/connect/kv/preferences \
  -H "Authorization: Bearer pmc_..."

A missing key returns 404 with {"error": "key not found"}.

List keys#

bash
curl https://api.portmodels.com/connect/kv \
  -H "Authorization: Bearer pmc_..."
json
{ "keys": ["preferences", "draft:2026-08-01", "cache:model-list"] }

Delete a value#

bash
curl -X DELETE https://api.portmodels.com/connect/kv/preferences \
  -H "Authorization: Bearer pmc_..."
json
{ "message": "deleted" }

Limits#

LimitValue
Key length1–256 characters
Value size64KB encoded
Keys per user, per app1000

Exceeding the value size returns 400 with value exceeds 64KB limit; exceeding the key count returns 400 with key limit reached (1000 keys per user per app).

For anything larger than 64KB, use file storage.

Isolation#

Entries are namespaced per (app, user). Your app cannot see what another app stored for the same user, and cannot see another user's entries. When the user disconnects your app your token stops working, but the data stays until it is deleted.

Patterns that work well#

Namespace your keys. A flat namespace gets crowded at 1000 keys. Prefix by purpose: pref:theme, draft:2026-08-01, cache:models.

Read once per session. Load the user's settings at the start of a session and keep them in memory rather than re-reading on every interaction.

Use it as a cache. Storing a derived result the user will need again is usually cheaper than regenerating it — see Pricing and markup.

Version your shape. Store {"v": 2, ...} so an older client can recognize a newer format instead of misreading it.

js
async function loadPrefs(token) {
  const res = await fetch('https://api.portmodels.com/connect/kv/preferences', {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (res.status === 404) return DEFAULT_PREFS;   // first run
  if (res.status === 403) return DEFAULT_PREFS;   // user declined kv.read
  return (await res.json()).value;
}

Tip

Degrade gracefully when the user declined the storage scopes. An app that still works — just without remembering anything — beats one that refuses to start.