API reference · v1

Manage brands from your own app

Read brands and generate assets over HTTP. Every endpoint below is generated from the running API's own definitions, so this page cannot drift out of step with it.

https://nabu.ammoura.me

Authentication

Send your key as a bearer token on every /api/v1 request.

Authorization: Bearer nabu_sk_…

Keys are stored as a SHA-256 hash. The plaintext is shown once when you create it and cannot be recovered afterwards — if you lose it, revoke it and make another.

Scopes

There is no hierarchy: assets:write does not imply assets:read. Ask for each scope you need. New keys default to brands:read.

brands:read
List and read brands.
brands:write
Reserved. No v1 endpoint requires it yet.
assets:read
List assets and fetch their bytes.
assets:write
Generate assets.

Brand access

A key acts as the person who created it. For each brand that resolves to a role — owner, manager, editor or viewer — and only the first three can write.

A brand you cannot reach returns 404, not 403. That is deliberate: a 403 would confirm the id exists to someone with no business knowing.

Endpoints

GET /api/v1/brands brands:read

List brands

Brands this key can reach — owned, plus any shared with you — newest first, capped at 100. A brand-scoped key returns only its own.

Returns `200` with `{ data: [ { id, name, tagline, status, industry, colors, logo_url, role, … } ] }`.

curl https://nabu.ammoura.me/api/v1/brands \
  -H "Authorization: Bearer $NABU_KEY"
GET /api/v1/brands/[id]/logos assets:read

List logos

Logo assets for the brand, newest first, plus which one is currently assigned as the brand's logo.

Returns `200` with `{ data: [ { id, name, url, mime_type, width, height, … } ], current_logo_url }`.

POST /api/v1/brands/[id]/logos assets:write

Generate a logo

Generates a logo and stores it as an asset. Runs synchronously, so one request returns a finished asset with no polling. The prompt is built from the brand's own name, industry, personality and palette, plus constraints that keep output usable as a mark rather than as illustration.

Body
style enum default abstract

One of the styles listed above.

instruction string optional

Extra direction, up to 500 characters. Added to the constraints rather than replacing them, so output stays printable.

set_as_logo boolean default false

Assign the result as the brand's logo. Defaults to false: generating and choosing are separate decisions, and an app usually wants to offer candidates before overwriting a mark already in use.

model string default Workers AI FLUX

v1 accepts Workers AI image models only.

Returns `201` with `{ data: { id, generation_id, url, style, prompt, model, width, height, set_as_logo } }`.

curl -X POST https://nabu.ammoura.me/api/v1/brands/<brand-id>/logos \
  -H "Authorization: Bearer $NABU_KEY" \
  -H 'Content-Type: application/json' \
  --data '{"style":"lettermark","instruction":"geometric, single weight"}'
GET /api/v1/brands/[id]/assets/[assetId]/content assets:read

Fetch asset bytes

Streams the asset itself. Asset URLs from this API point here rather than at the app's own file route, which needs a browser session and would be a dead link to an API client. The lookup is scoped to the brand in the path, so an asset id belonging to another brand resolves to nothing.

Returns The raw file, with its `content-type`.

Managing keys

These use your browser session, not an API key — a key cannot mint another key, or a single leak would outlive revoking the original.

POST /api/keys session

Create an API key

Returns the only copy of the key that will ever exist. Only a SHA-256 hash is stored, so it cannot be shown again or recovered from a database dump. Requires a browser session — a key can never mint another key, or one leak would outlive revoking the original.

Body
name string required

Label, up to 80 characters.

scopes string[] default ["brands:read"]

Any of the scopes above. Unknown values are rejected outright.

brand_profile_id string optional

Pins the key to one brand. Every other brand then answers 404, even ones you own.

expires_at string optional

ISO-8601. The key stops working at that moment.

Returns `201` with `{ id, name, scopes, key, warning }`.

curl -X POST https://nabu.ammoura.me/api/keys \
  -H 'Content-Type: application/json' \
  --data '{"name":"my-app","scopes":["brands:read","assets:write"]}'
GET /api/keys session

List your keys

Never returns key material — only the visible prefix and usage counters.

Returns `200` with `{ keys: [...] }`.

DELETE /api/keys/[id] session

Revoke a key

Effective on the next request. Sets a revocation timestamp rather than deleting the row, so the audit trail survives a compromise.

Returns `200` with `{ revoked: true, id }`.

Logo styles

wordmark
The brand name set as a custom letterform.
lettermark
A monogram built from the brand initials.
abstract
A geometric mark with no letters or objects.
mascot
A single stylised character mark.
emblem
A badge, with the mark inside a bounding shape.

Errors

Every failure uses one envelope, so you can branch on code instead of parsing prose.

{
  "error": {
    "code": "insufficient_scope",
    "message": "This key is missing the `assets:write` scope."
  }
}
400
invalid_json
Body was not valid JSON.
invalid_body
Body was not a JSON object.
invalid_style
`style` was not a known logo style.
instruction_too_long
`instruction` exceeded 500 characters.
unsupported_model
Model is not a Workers AI image model.
401
missing_credentials
No `Authorization: Bearer` header was sent.
invalid_key
Key is unknown, revoked or expired. One code covers all three on purpose — telling them apart would let a caller probe which keys exist.
403
insufficient_scope
The key lacks the scope this route needs.
read_only_access
Your role on this brand cannot write.
404
brand_not_found
The brand does not exist, or this key cannot reach it. Deliberately the same answer: a 403 would confirm the id exists.
asset_not_found
No such asset on this brand.
410
asset_content_missing
The record exists but its file is gone.
502
generation_failed
The model failed. The generation id is included so you can correlate it.
503
unavailable
Database unavailable.
ai_unavailable
Image generation unavailable.
storage_unavailable
Asset storage unavailable.