[001] DEV

Build on
OnBrand

Run an Instagram brand from any agent · one API, one CLI, one skill.

What's inside: the @rizzailab/onbrand CLI, two skills · onbrand and humanizer, the Claude Code plugin + marketplace, and the publish guide.

humanizer is the anti-slop standard OnBrand enforces on every generated line: banned words and phrases, no long dashes, no "not just X it's Y", no preamble, no burned-in text on images. Install it on its own with npx skills add rizzailab/humanizer.

[002] Install

Three ways in

SKILL · ANY SKILL-COMPATIBLE AGENT

Reads SKILL.md straight from the public repo. No registry.

SHELL
npx skills add rizzailab/onbrand
CLAUDE CODE PLUGIN

The repo is its own marketplace. Two commands inside Claude Code.

CLAUDE CODE
/plugin marketplace add rizzailab/onbrand
/plugin install onbrand@rizzailab
SHIPS SLASH-COMMANDS
  • /onbrand:run · one post end to end, approve or regenerate
  • /onbrand:brands · brands this key can act on
  • /onbrand:publish · ship now, real vs simulated stated
  • /onbrand:status · credits plus what awaits approval
CLI

Plain Node ESM, no dependencies. The key is stored at ~/.onbrand/config.json.

SHELL
npm i -g @rizzailab/onbrand
onbrand login --key ob_live_...

Agents can also skip the CLI entirely and call the HTTP API directly.

[003] The agent API

One base url,
twelve tools

BASE URL

https://onbrand.rizzgroup.org/api/v1

AUTH

Mint a key on the API keys screen in the app. It looks like ob_live_... and is shown once. Send it as Authorization: Bearer ob_live_...

SCOPES

read · generate · ingest · schedule · approve · publish

New keys default to read + generate. Approve and publish are opt-in. A key can be locked to a single brand, and it then refuses every other brand with 403.

TOOL TABLELOADING
TOOLMETHODPATHSCOPEPURPOSE
brandsGET/api/v1/brandsreadList the brands this key can act on. A brand-scoped key returns exactly one.
generatePOST/api/v1/generategenerateProduce a post with OnBrand's own engines. Use dry_run first to validate a brief for free.
ingestPOST/api/v1/ingestingestPush media made outside OnBrand into a brand. Stored as a real asset labelled external.
postsGET/api/v1/postsreadList a brand's posts, newest first.
approve_postPOST/api/v1/posts/:id/approveapproveApprove a post. Same guards, verdict and teaching loop as the dashboard.
reject_postPOST/api/v1/posts/:id/rejectapproveReject or send back a post. Writes the verdict and triages the failure.
schedulePOST/api/v1/schedulescheduleMove or clear a post's slot through the same guarded path the calendar uses.
publishPOST/api/v1/publishpublishPublish a post now. The channel decides real or simulated, and simulated is always labelled.
analyticsGET/api/v1/analyticsreadReal Instagram numbers for a brand plus that brand's AI spend. Never estimated.
insightsGET/api/v1/insightsreadThe brain's latest written insights for a brand.
creditsGET/api/v1/creditsreadPlan, credit balance and period end for the account behind this key.
manifestGET/api/v1/manifestnoneThis document. Public, no key required.
RESPONSES
ENVELOPE
success  { "ok": true, "data": { ... } }
failure  { "ok": false, "error": "human readable", "code": "machine_readable" }
STATUS CODES
  • 200 · done
  • 401 · missing, malformed or revoked key
  • 403 · key lacks the scope, or the brand is not this key's
  • 404 · no such route or record
  • 409 · business refusal with a machine code: not_publishable, rights_held, missing_video, no_channel, safe_hold, brand_paused, insufficient_credits
  • 422 · invalid_input, issues[] names each field
  • 429 · 60 requests per minute per key
  • 500 · unexpected server error only. A refusal is never a 500
CURL · LIST BRANDS
curl -s https://onbrand.rizzgroup.org/api/v1/brands \
  -H "Authorization: Bearer ob_live_xxx"
CURL · GENERATE, DRY RUN
curl -s -X POST https://onbrand.rizzgroup.org/api/v1/generate \
  -H "Authorization: Bearer ob_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"brand_id":"<uuid>","brief":"rooftop golden hour, film grain","type":"image","dry_run":true}'

A dry run resolves the prompt, names the engine and model, and returns estimated_credits with spent:false. No engine call, no asset, no post row.

[004] The CLI

onbrand

Key lookup order: --key → ONBRAND_API_KEY → ~/.onbrand/config.json. Base url: --api-url → ONBRAND_API_URL → https://onbrand.rizzgroup.org. Add --json to any command for the raw envelope. Every failure exits non-zero.

COMMANDSCOPEWHAT IT DOES
onbrand manifestnonePrint the tool catalogue straight from the API.
onbrand login --key <ob_live_...>readSave the key to ~/.onbrand/config.json (mode 600) and verify it.
onbrand brandsreadBrands the key can act on, with autopilot state and approval threshold.
onbrand creditsreadPlan, credit balance, period end.
onbrand generate --brand <id> --brief "<text>" [--kind image|reel|carousel]generateCreate a candidate post. Spends credits. The API also accepts dry_run for a no-spend preview.
onbrand posts [--brand <id>] [--status <s>] [--limit N]readList posts, newest first.
onbrand approve <post_id>approveApprove, schedule into the next free slot, teach the Taste Engine.
onbrand reject <post_id> [--reason "<text>"]approveReject and record why.
onbrand schedule <post_id> --at <ISO8601>scheduleMove the post to a slot.
onbrand publish <post_id>publishPublish now. The response says mode: real, simulated or reminder.
onbrand insights --brand <id>readThe brain's latest written insights.
onbrand analytics --brand <id>readReal Instagram numbers plus AI spend.
onbrand ingest --brand <id> --url <media url> [--source <tool>]ingestPull in media made elsewhere.
THE SIX-STEP AGENT LOOP
  1. 1 · List brands. Read the brand's approval_threshold, autopilot and paused state.
  2. 2 · Generate. Dry run first if the brief is new, then spend.
  3. 3 · Read the pending post and its onbrand_score.
  4. 4 · Approve if the score is at or above the brand's approval_threshold, otherwise reject with a written reason.
  5. 5 · Schedule or publish.
  6. 6 · Learn. Pull insights and analytics before the next brief.
PUBLISH HONESTY

Publish returns a top-level mode: real, simulated or reminder, plus a simulated boolean. Placeholder numbers only ever appear under simulated_preview. A simulated result is never dressed as real, and a business refusal comes back as 409 with a machine code, never a 500.

CLI
onbrand publish <post_id> --json
# => { ok: true, data: { mode: "simulated", simulated: true, ... } }
[005] The Midjourney arm

Bring media made
somewhere else

Anything produced outside OnBrand · Midjourney, a local model, a photographer · can enter a brand and then be scored, approved, scheduled and published like anything else. It is stored as a real asset labelled external, and external media never teaches the Taste Engine.

PATH A · THE API

Mint a key carrying the ingest scope, then POST the media. Send a fetchable media_url, or POST raw bytes as application/octet-stream with the same fields as query params. Optional fields: kind (image or video), prompt, version, and request_id to fulfil a specific connector request.

CURL · INGEST
curl -s -X POST https://onbrand.rizzgroup.org/api/v1/ingest \
  -H "Authorization: Bearer ob_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"brand_id":"<uuid>","source":"midjourney","media_url":"https://cdn.example/img.png","kind":"image","prompt":"rooftop golden hour --ar 4:5"}'
PATH B · CONNECTOR AND DROP

In the app, register an external generator on the brand. A connector carries a provider, a label and a mode: manual, api (it gets its own scoped key) or runner (a machine you host polls for requests). Each brand also has a drop token, which gives you two dumb-simple inlets:

  • Drop webhook · /api/public/hooks/drops/{drop_token}
  • Drop email · drop-{drop_token}@in.onbrand.rizzgroup.org

Manage both in the app: External generators and Drops.

[006] Publish your own

Three artifacts

One folder ships three artifacts from one push to a public repo. Two of them need no registry at all.

A · SKILL

Public repo, no registry submission. The resolver reads skills/onbrand/SKILL.md straight from the repo.

SHELL
npx skills add rizzailab/onbrand
B · CLAUDE CODE PLUGIN

No registry either. marketplace.json makes the repo its own marketplace, plugin.json owns the version. Validate before pushing.

SHELL
claude plugin validate ./ --strict
/plugin marketplace add rizzailab/onbrand
C · CLI ON NPM

Scoped packages are private by default, so public access is not optional. A GitHub Release also triggers the publish workflow.

SHELL
npm login
npm version patch
npm publish --access public

Point your agent
at a real brand