Folder layout, manifest, tenant.json, private/tenant.json, accepted paths
Schema streamline.config/1. JSON Schemas: https://streamliner.work/schema/v1/<kind>.schema.json (also in the Streamline repo under schema/). Run the checker after every edit (SKILL.md step 5).
Contents
- Layout
- manifest.json
- enrolment.json
- tenant.json
- private/tenant.json
- Paths the write API accepts
- URLs
Layout
<account>/
tenant.json brand, locale, auth, roles, kinds (streamline order) device-visible
agents/<kind>.json one streamline: what people see (streamlines.md) device-visible
templates/<id>.json one view template (templates.md) device-visible
forms/<id>.json one sealed form / hosted page / sealed view (forms.md) device-visible
assets/<sha256>.png|jpg|pdf logos and images; the file name prefixes the content's sha256 device-visible
manifest.json signed index of every device-visible file
enrolment.json the publisher public keys devices pin (not part of the signed set)
private/tenant.json account policy: networks, access seeds, serverKeys, budgets, transcripts
private/agents/<kind>.json instructions, model, credentialRef, MCP servers + tool allowlist, network, limits
private/connections/<id>.json login details for password / api-key connections (back end only)
private/briefs/<id>.md a brief: shared instructions streamlines include (briefs.md) relay only
Device-visible files are served only to signed-in members, and each member gets only the streamlines their roles open. private/ never reaches a member's device (an owner's or admin's approved device reads it to edit and publish it). Templates and forms are account-level and shared by id across streamlines.
manifest.json
Written by scripts/sign-config.mjs, never by hand.
{ "schema": "streamline.config/1", "tenant": "meridian", "version": 9, "published": "2026-10-09T17:40:00Z",
"minApp": "1.0.0 (1)", "refresh": { "foregroundSeconds": 900, "minSeconds": 60 },
"files": [ { "path": "tenant.json", "sha256": "4b1f…", "bytes": 3120 } ],
"signature": { "alg": "Ed25519", "keyId": "meridian-2026a", "sig": "base64url…" } }
versionmust go up on every publish, including a change toprivate/alone.files[]lists every device-visible file (notprivate/,manifest.json,enrolment.json).signature.sigis Ed25519 over the JCS (RFC 8785) bytes of the manifest withoutsignature, by one of the account's publisher keys. OptionalnextKeys: [{keyId, pub}]rotates keys (devices trust them because the current key signed them).
enrolment.json
{ "tenant": "meridian", "configHost": "/", "keys": [ { "keyId": "meridian-2026a", "pub": "<base64url Ed25519 public key>" } ] }
Public halves only. Changing it changes the keys new devices pin; treat it as an owner-only change.
tenant.json
{
"id": "meridian",
"mode": "enterprise",
"brand": { "name": "Meridian Jewelers", "shortName": "Meridian", "logo": "assets/3f9a0c6e1d….png",
"accent": { "light": "#1F4E79", "dark": "#8DB8E8" } },
"locale": { "language": "en-US", "currency": "USD", "timeZone": "America/Los_Angeles" },
"auth": { "kind": "invite" },
"gateway": { "url": "https://<back end>/v1" },
"connections": [ { "id": "pos", "name": "Meridian POS", "type": "oauth" } ],
"roles": [
{ "id": "associate", "name": "Sales associate", "claims": { "groups": ["mj-associates"] } },
{ "id": "store_manager", "name": "Store manager", "claims": { "groups": ["mj-managers"] } }
],
"kinds": ["register", "approvals"],
"imageHosts": ["img.meridian.example"],
"policy": { "chatRetentionDays": 2, "appLockAfterSeconds": 300, "sealedScreenshots": "blur" },
"support": { "text": "Questions? Call the help desk at ext. 400.", "url": "https://help.meridian.example" }
}
| Field | Notes |
|---|---|
id | [a-z0-9-]{3,40}; equals the account id the write API uses. |
mode | enterprise (an organization), personal, or demo (scripted agent, mock backend; the only mode that may have card fields). |
brand | name, shortName (≤ 16 chars), logo (an assets/ path), accent and optional background as {light, dark} #RRGGBB, optional tagline (one line under the name on the join screen and in Settings). |
theme | The account's baseline theme: a preset (base, the default; quiet, harbour, refined-calm, amber; light-only paperwhite, dark-only nord) or one of themes[].id. Keep brand.accent equal to its accent. Colors only. |
themes[] | Up to 8 own themes: { id, name, light, dark? }, each side a shared ColorTheme id or a ColorTheme document (hex colors under surfaces, accents, semantics, text, actions). |
locale | BCP 47 language, ISO 4217 currency (the money formatter's default), IANA time zone. |
auth.kind | invite (the default: Sign in with Apple, passkey or email code; memberships from invites or directory sync), oidc (the organization's IdP: issuer, clientId, scopes, resources, roleClaim), connection (sign in to a named connection), demo. See access-and-networks.md. |
gateway.url | The back end's base URL (relay, connections, enrollment). Given to you by Streamliner. |
connections[] | The systems people sign in to (access-and-networks.md). |
roles[] | Job roles: id, name, claims (for OIDC: claim → accepted values). Streamlines and tools name role ids. |
kinds[] | The streamlines, in sidebar order. Each needs agents/<kind>.json and private/agents/<kind>.json. |
imageHosts[] | The only hosts template images may load from. |
policy | chatRetentionDays (default 2 for organizations), appLockAfterSeconds, sealedScreenshots (blur), attachmentsMaxBytes, uploadRetentionDays (1–30, default 7), voice (false turns the mic and Speak replies off account-wide), syncAccountList (false keeps the organization out of members' organization list in iCloud, which otherwise shows it on their other devices, signed out; default true). |
support | text, url, optional email (where Report an Issue offers a mail link about this account's streamlines). |
private/tenant.json
{
"network": { "mode": "none" },
"networks": { "head-office": { "name": "Head office", "cidrs": ["203.0.113.0/24"] } },
"roles": { "regional": { "networks": ["head-office"] } },
"users": { "u-marco": { "networks": ["head-office"] } },
"access": { "u-marco": "admin" },
"serverKeys": [ { "id": "claude-setup-oct", "alg": "Ed25519", "publicKey": "<base64url 32 bytes>", "scopes": ["config"] } ],
"budgets": { "warnAt": 0.8, "perUser": { "tokensPerDay": 3000000, "costPerDayUSD": 5 } },
"transcripts": { "retentionDays": 365 }
}
serverKeysholds public halves only. A publish that would leave no enabled key with theconfigscope is refused (would_lock_out), so keep the key you publish with.- Networks, budgets and transcripts: access-and-networks.md.
Paths the write API accepts
tenant.json, enrolment.json, manifest.json, agents|templates|forms/<id>.json, assets/<sha256 prefix>.png|jpg|jpeg|pdf, demo/backend.js|agent.js|fixtures.json (demo accounts), private/tenant.json, private/agents|connections/<id>.json, private/briefs/<id>.md. Anything else is refused (bad_path): keep notes, fixtures and drafts outside the folder.
URLs
Absolute https:// everywhere. A URL starting with / resolves against the config host (demo accounts only). http:// is refused except http://localhost for local testing.