Management and access

Use a management identity to group datasets, keys, and a shared usage allowance. No name, email, or personal account is required.

Manage datasets, keys, usage, and jobs

IDs, labels, and secrets

The management ID is public and stable. It identifies a group of permissions and usage, not a verified person. Its name and description are optional, public, unverified, and can repeat. Dataset IDs are also unique and stable; dataset names and descriptions can repeat. Names are never used for access or addressing. There are no dataset aliases or custom codes.

CredentialPermissionWhere to use it
Management keyManage this identity and its datasets; issue, rotate, and revoke scoped keys; change metadata and configuration; delete datasets. Can also read/write its own data.Management screen or a trusted administration client. Keep it out of published HTML and shared worker links.
Writer keyWrite to one dataset. Cannot administer keys, delete a dataset, register watchers/subscribers/origins, bind event sources, or approve an action.One application or worker.
Reader keyRead one private dataset.Private viewers. Public reads need no key.
Approver keyRead and decide proposals in one Approve dataset; cannot create proposals, consume approvals, or manage keys.A reviewer. It grants no access to other datasets.

Keys have no automatic expiry. Queue reservations and other functional leases still expire. A key is disclosed once and cannot be retrieved later. Save it before leaving the page. Anyone possessing a key can exercise its permissions.

Create an identity and a dataset

curl -sS https://bay.farm/api/management \
  -H 'Content-Type: application/json' \
  -d '{"name":"Garden sensors","description":"Public greenhouse readings"}'

Returns management: {id,name,description,created_at,state}, management_key, management_key_id, and key_disclosed_once:true. Name: at most 64 characters. Description: at most 512. Both default to empty.

BAY_URL='https://bay.farm'
MANAGEMENT_KEY='PASTE_MANAGEMENT_KEY'

curl -sS "$BAY_URL/api/datasets" \
  -H "Authorization: Bearer $MANAGEMENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"kind":"store","name":"Temperatures","description":"Sensor readings in Celsius"}'

Dataset creation returns dataset, management_id, write_key, and key_id. The write key is restricted. Keep the management key separately. Creating without Authorization creates a fresh identity and returns its management key once. An invalid key, writer key, or reader key cannot be used to create datasets. Approve also returns a dataset-scoped decision_key and decision_key_id. Private datasets still require an active paid plan key in X-Bay-Plan; the public interface creates public data only. Billing is not connected.

Inspect and label

curl -sS "$BAY_URL/api/management?limit=30" \
  -H "Authorization: Bearer $MANAGEMENT_KEY"

curl -sS -X PUT "$BAY_URL/api/management" \
  -H "Authorization: Bearer $MANAGEMENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Weather experiments","description":"Sample data"}'

DATASET_ID='PASTE_DATASET_ID'
curl -sS -X PUT "$BAY_URL/api/datasets/$DATASET_ID" \
  -H "Authorization: Bearer $MANAGEMENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Garden temperatures","description":"Readings from three sensors"}'

GET management returns datasets, key metadata, limits, logical bytes, and current counters. Follow next_cursor using after; limit is 1–100, default 30. PUT replaces both labels; omitted labels become empty. Public identity labels are available at GET /api/management/ID/public. That public endpoint never lists private datasets or key metadata. Public dataset metadata contains its management ID, name, and description.

Issue, rotate, and revoke scoped keys

curl -sS "$BAY_URL/api/datasets/$DATASET_ID/keys" \
  -H "Authorization: Bearer $MANAGEMENT_KEY"

curl -sS "$BAY_URL/api/datasets/$DATASET_ID/keys" \
  -H "Authorization: Bearer $MANAGEMENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"operation":"issue","role":"reader","label":"Dashboard"}'

curl -sS "$BAY_URL/api/datasets/$DATASET_ID/keys" \
  -H "Authorization: Bearer $MANAGEMENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"operation":"rotate","key_id":"KEY_ID"}'

curl -sS "$BAY_URL/api/datasets/$DATASET_ID/keys" \
  -H "Authorization: Bearer $MANAGEMENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"operation":"revoke","key_id":"KEY_ID"}'

Key listing contains IDs, labels, roles, timestamps, revocation state, and a managed flag. It never returns secret values. Free datasets support one active writer and one reader. Paid datasets support twenty of each. A dataset's management marker is not a separate recoverable secret; rotate management through the identity endpoint. Existing Approver keys can also be rotated or revoked by ID. Rotation invalidates the old secret and saved grants using its key ID. Revocation does not automatically expire data.

Replace the management key

curl -sS -X POST "$BAY_URL/api/management/rotate" \
  -H "Authorization: Bearer $MANAGEMENT_KEY"

Returns a new management key once. The public ID, datasets, usage debt, writer/reader/approver keys, and saved scoped grants stay in place. The old management key and every remembered browser session stop working. Save the new key before closing the page. Existing legacy owner credentials in this identity are also invalidated by this explicit operation.

Remember browser access

The Manage screen remembers access using an independent session token in a host-only __Host-bay_management cookie with Secure, HttpOnly, SameSite=Strict, and Path=/. The management key is not placed in local storage or in the cookie. Server storage contains only token hashes. Browser scripts cannot read the cookie; authorized first-party page scripts can still make requests, so the normal content-security policy and separate Sites origins remain important.

curl -sS -c browser.cookies "$BAY_URL/api/management/session" \
  -H 'Content-Type: application/json' \
  -d '{"management_key":"PASTE_MANAGEMENT_KEY"}'

curl -sS -b browser.cookies "$BAY_URL/api/management" \
  -H 'X-Bay-Session: 1'

curl -sS -b browser.cookies -X DELETE "$BAY_URL/api/management/session" \
  -H 'X-Bay-Session: 1'

curl -sS -X DELETE "$BAY_URL/api/management/sessions" \
  -H "Authorization: Bearer $MANAGEMENT_KEY"

Remember: POST session with {management_key}. Forget this browser: DELETE session. Forget every browser: DELETE sessions. Cookie access requires X-Bay-Session: 1 and the exact first-party origin; sibling hosted sites cannot use it. Explicit Authorization takes precedence over the cookie and cannot silently become management authority. Cookie persistence is ten years, subject to browser removal; keys and server sessions have no automatic expiry. There is a limit of twenty remembered browsers per identity. HTTPS is required for remembered access; local HTTP development can use bearer keys. Treat an exported cookie jar as a secret.

Existing datasets

Migration adds one stable management ID to each existing dataset and preserves its data, visibility, and existing credentials. An old key previously called a write key may still be an owner key. It retains its original management authority for that dataset; it does not become a restricted writer automatically. Inspect its role before sharing it.

Use an old standalone owner key on the management API or remember it in the browser. To consolidate a standalone dataset under a new identity:

curl -sS "$BAY_URL/api/management/adopt" \
  -H "Authorization: Bearer $MANAGEMENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"dataset_id":"DATASET_ID","management_key":"OLD_DATASET_OWNER_KEY"}'

Adoption requires both credentials, carries usage debt forward, and never grants the old owner key authority over the new group's other datasets. The old key retains authority over its original dataset so existing integrations keep working. Replace old clients with a restricted writer, then explicitly revoke/rotate the legacy owner credential. Newly managed datasets cannot be adopted through this compatibility endpoint. Legacy endpoints, encrypted request collections, and public ntfy topics keep their existing independent contracts.

Usage and abuse limits

Limits apply to the public management ID. Replacing a key or issuing additional keys never grants another group allowance. IP-based issuance limits are a separate signal; shared networks can reach them without wrongdoing. Reaching an issuance limit does not revoke existing identities.

LimitFree identityPaid identity
Datasets / logical payload storage10 / 32 MiB100 / 512 MiB
Writes per day2,00020,000
Authenticated requests per minute / day120 / 10,000300 / 100,000
Command requests per day2,00010,000
Outbound calls per day1001,000
Completions per day20200
Each writer per minute / day30 / 500120 / 5,000

New identities: five per IP per hour, ten per IP per day, 100 globally per day, and 5,000 retained identities. Management API access also has an IP limit of 60 requests per minute. IP counters use keyed hashes internally; this release does not add public raw-IP request logs or location tracking.

Existing per-dataset, connection, destination, model, and global limits also apply and may be tighter. Management counters use UTC minute/day buckets. Background writes and outgoing work consume identity allowances. Pure anonymous commands remain available under connection and global limits; supply a management or dataset key to associate them with an identity. Command payloads are not retained. Storage counts logical payloads, not all SQLite metadata or temporary files. Limits may reject work before its configured schedule can run; inspect diagnostics instead of retrying indefinitely.

An operator can block an identity's writes, published Sites delivery, and background work together. Public reads and authorized private exports remain available. Unblocking does not automatically restart paused definitions. A network request already sent cannot be recalled.

MCP and browser client

MCP management {operation:"create", name:"Garden"}
MCP management {operation:"get", access_key:"MANAGEMENT_KEY"}
MCP dataset_create {kind:"store", access_key:"MANAGEMENT_KEY", name:"Temperatures"}
MCP dataset {operation:"update", dataset_id:"ID", access_key:"MANAGEMENT_KEY",
             name:"New name", description:"Sample data"}
MCP dataset_keys {operation:"list", dataset_id:"ID", access_key:"MANAGEMENT_KEY"}
MCP management {operation:"rotate", access_key:"MANAGEMENT_KEY"}
MCP management {operation:"adopt", access_key:"MANAGEMENT_KEY",
                dataset_id:"ID", dataset_management_key:"OLD_OWNER_KEY"}
MCP management {operation:"revoke_sessions", access_key:"MANAGEMENT_KEY"}

The public MCP endpoint is /mcp. Tools use the same handlers, permissions, and limits as HTTP. A bearer key may be supplied in Authorization instead of an access_key argument. OpenAPI is available at /openapi.json.

import {BayFarmClient} from 'https://bay.farm/static/bayfarm-client.js';
const bay = new BayFarmClient('https://bay.farm');
const identity = await bay.management('create', {name:'Garden'});
const dataset = await bay.create('store','Temperatures', {
  management_key: identity.management_key,
  description: 'Readings in Celsius'
});
await bay.act(dataset.dataset.id, dataset.write_key, 'store.put', {
  name:'latest', value:{celsius:22}, revision:0
});

This administrative setup belongs in a trusted client. Publish only the scoped credentials your app needs. Outside the Bay Farm origin the browser client omits cookies and uses explicit keys. Trusted external administration clients may use the management API with explicit bearer keys; remembered session creation is first-party only. An external client's newly created management key is held in memory and returned for backup, never put into share links automatically. Register the exact external app origin on each dataset using management authority.