CanopyONBRANDDashboard

Documentation

OnBrand by Canopy Labs is the brand layer for AI agents: extract any brand system, search for style inspiration, and verify that what your agents make stays on brand — over REST, MCP or agent skills.

Quickstart

  1. Create a key on the API Keys page.
  2. POST /extract a URL, poll /extract/{id}/result until it's completed, and hand the brand (or /brief, /tokens) to your agent.
  3. After your agent ships a page, POST /adherence with the brand and the page, apply the returned fixes and recommendations, and re-verify.
# 1. start
ID=$(curl -s https://brand.trycanopy.space/api/v1/extract -H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' \
  -d '{ "url": "https://stripe.com" }' | jq -r .id)
# 2. poll until completed (409 means nothing has landed yet)
curl -s https://brand.trycanopy.space/api/v1/extract/$ID/result -H 'X-API-Key: YOUR_API_KEY' | jq .status
# 3. read only what you need
curl -s "https://brand.trycanopy.space/api/v1/extract/$ID/result?sections=colors,typography" -H 'X-API-Key: YOUR_API_KEY'

How it works

Extractor

Renders the page in a real browser, measures computed styles, CSS, DOM structure and screenshots, then turns them into a brand system. Async job.

Search

Ranks brands in the curated OnBrand index by your description or by similarity to one of your extractions. Synchronous.

Verifier

Extracts a reference and a candidate, measures colour distance, type, surfaces, layout and elevation, then critiques both screenshots. Async job.

Authentication

Send your key in the X-API-Key header (or as Authorization: Bearer ob_live_…). Base URL: https://brand.trycanopy.space/api/v1. Keys are hashed at rest and shown once at creation. /result and /download on public extractions, and /result on public verifications, also work without a key. Errors share one envelope: { "error": { "code", "message" } }.

Brand Extraction

Turn any URL into an agent-ready brand system plus the artifacts it was measured from. Extraction is a job: start it, poll it, read it. Sections stream in as they finish, so you can read partial results while it runs.

POST/extract2 credits · refunded on failure

Starts an extraction and returns 202 with its id (200 when a fresh cached result is reused; cache_hit tells you which). Sections: identity, colors, surfaces, typography, layout, elevation, structure, interactions, navigation, icons, motion, data_display, sections, media, tokens. Selective extractions are never used as a cache source, and can't be combined with map or deep.

{
  "url": "https://linear.app",          // required
  "force": false,                        // true = skip the cache and re-crawl
  "depth": "light" | "deep",             // deep adds an AI review pass (full extractions only)
  "sections": ["colors", "typography"],  // optional: extract only these sections
  "map": false,                          // true = also extract same-domain pages
  "max_pages": 20                        // map mode cap (max 50)
}
curl https://brand.trycanopy.space/api/v1/extract \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{ "url": "https://linear.app" }'
GET/extract/{id}/result?sections=colors,typographyfree

The brand system and artifact links. While the job runs you get 200 with whatever has landed, or 409 not_ready before the first section. Keep polling until status is completed or failed. ?sections= narrows the payload, which matters for agents because a full system is large. Public extractions are readable without a key.

curl 'https://brand.trycanopy.space/api/v1/extract/ext_8h2k.../result?sections=colors,typography' -H 'X-API-Key: YOUR_API_KEY'
{
  "id": "ext_8h2k...", "object": "extraction", "status": "completed",
  "url": "https://linear.app/", "sections": null, "cache_hit": false, "is_public": false,
  "palette": ["#08090A", "#F7F8F8", "#5E6AD2"],
  "artifacts": { "screenshot": "...", "hero": "...", "html": "...", "css": "..." },
  "brand": {
    "colors":     { "baseline": [{ "name": "Void", "hex": "#08090A", "shades": [...] }], "secondary": [...] },
    "typography": { "families": [...], "titles": [...], "body": [...], "labels": [...] }
  }
}
GET/extract/{id}free

Status, per-stage progress and partial results. Add ?fields=status for the lightest poll.

curl https://brand.trycanopy.space/api/v1/extract/ext_8h2k...?fields=status -H 'X-API-Key: YOUR_API_KEY'
GET/extract?status=&q=&api_key_id=&limit=&offset=free

Your extractions, newest first, with total and totals (completed / failed / in_progress) for the whole filter.

curl 'https://brand.trycanopy.space/api/v1/extract?status=completed&limit=20' -H 'X-API-Key: YOUR_API_KEY'
GET/extract/{id}/downloadfree

A manifest of every file in a completed extraction: brand.json and tokens.json inline, plus URLs for the captured HTML, CSS, screenshots and media. Files are stored on our side, so links survive changes to the origin site. Prefer a zip? Use /extract/{id}/bundle.

curl https://brand.trycanopy.space/api/v1/extract/ext_8h2k.../download -H 'X-API-Key: YOUR_API_KEY'
PATCH/extract/{id}/visibilityfree

Make an extraction readable from /result and /download without a key, e.g. to share it. Only the owner can change this.

{ "is_public": true }
curl -X PATCH https://brand.trycanopy.space/api/v1/extract/ext_8h2k.../visibility -H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' -d '{ "is_public": true }'
GET/extract/{id}/tokens?format=json|css|tailwindfree

Design tokens as JSON, CSS custom properties or a Tailwind v4 @theme block.

curl 'https://brand.trycanopy.space/api/v1/extract/ext_8h2k.../tokens?format=tailwind' -H 'X-API-Key: YOUR_API_KEY'
GET/extract/{id}/brieffree

A compact markdown brief of the brand, ready to drop into an agent's context.

curl https://brand.trycanopy.space/api/v1/extract/ext_8h2k.../brief -H 'X-API-Key: YOUR_API_KEY'
POST/enhancefree

Rewrites a prompt so it's grounded in the brand's exact colours, type, layout and components. Synchronous. Needs a completed full extraction.

{ "extraction_id": "ext_8h2k...", "prompt": "Design a pricing page with three tiers" }
curl https://brand.trycanopy.space/api/v1/enhance -H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' \
  -d '{ "extraction_id": "ext_8h2k...", "prompt": "Design a pricing page" }'

No brand yet? Describe a look in plain language and get ranked, real brands from the OnBrand index, each with a screenshot, palette, type pairing and taxonomy tags. Search is synchronous: nothing to poll. A card points into the index; extract its url to get the full system.

POST/searchfast 1 credit · deep 2 credits

Returns ranked cards with match (strong, good, related) and, on unfiltered searches, one rotating badge: "discovery" exemplar so repeat searches widen your set. Filters are hard constraints from a closed vocabulary (below); unknown values return 422.

{
  "query": "dark bold creative studio with expressive typography",
  "depth": "fast" | "deep",     // deep re-ranks with AI and explains each match
  "top_k": 6,                    // 1-30
  "filters": {                   // optional hard constraints
    "page_type": "homepage",
    "industry": "creative_agency",
    "hue": "red",
    "layout": "asymmetric_broken_grid"
  }
}
curl https://brand.trycanopy.space/api/v1/search \
  -H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' \
  -d '{ "query": "warm pastel skincare landing page", "top_k": 6 }'
{
  "object": "search", "id": "srch_...", "depth": "fast", "query_tags": ["Warm", "Skincare"],
  "results": [{
    "url": "https://aesop.com", "brand_name": "aesop", "match": "strong", "badge": null,
    "identity_paragraph": "...", "reason": null,
    "tags": ["Earthy", "Editorial", "Luxury"], "traits": ["Luxury", "Editorial", "Minimal"],
    "palette": { "primary": ["#FFFEF2", "#333333", "#B5A48B"], "secondary": [], "mode": "light" },
    "typography": "Suisse Intl + Zapf Humanist", "screenshot_url": "..."
  }]
}
GET/search/similar?extraction_id=&top_k=1 credit

Brands in the index that look like one of your completed full extractions: palette distance, mode, type and keywords. Good for competitor sets and moodboards.

curl 'https://brand.trycanopy.space/api/v1/search/similar?extraction_id=ext_8h2k...&top_k=12' -H 'X-API-Key: YOUR_API_KEY'
GET/search?kind=search|similar&depth=&q=&limit=&offset=free

Your search history, newest first, with total and total_by_status.

curl 'https://brand.trycanopy.space/api/v1/search?limit=20' -H 'X-API-Key: YOUR_API_KEY'

Verify Adherence

Give two URLs: the reference brand (the standard) and a candidate page (a rebuild, a generated page, a redesign). Both sides are extracted automatically and the candidate is judged against the reference. The verdict is one score from 0 to 1, recommendations in prose, and structured fixes with exact target values. Both lists are worst-first, so they double as a work queue for an agent loop.

POST/adherence2 credits · both extractions included · refunded on failure

Starts a verification and returns 202 with its id. The candidate must be a public URL.

{ "reference_url": "https://linear.app", "candidate_url": "https://my-redesign.vercel.app" }
curl https://brand.trycanopy.space/api/v1/adherence \
  -H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' \
  -d '{ "reference_url": "https://linear.app", "candidate_url": "https://my-redesign.vercel.app" }'
GET/adherence/{id}/resultfree

The verdict. 409 not_ready while both pages are extracting; 424 adherence_failed if the run failed (stop polling; credits are refunded). Public runs are readable without a key.

curl https://brand.trycanopy.space/api/v1/adherence/adh_.../result -H 'X-API-Key: YOUR_API_KEY'
{
  "object": "adherence_verdict", "status": "completed", "score": 0.78, "grade": "C",
  "recommendations": [
    "Set headline type in Inter Display, sans-serif instead of Arial.",
    "Change the background-color from #FFFFFF to #08090A (--void)."
  ],
  "fixes": [
    { "action": "replace_font_family", "category": "typography", "property": "font-family", "role": "headline", "from": "Arial", "to_value": "Inter Display, sans-serif" },
    { "action": "snap_to_token", "category": "colors", "property": "background-color", "from": "#FFFFFF", "to_value": "#08090A", "token": "--void" },
    { "action": "add_color_token", "category": "colors", "token": "--accent", "value": "#5E6AD2", "role": "Accent", "usage": ["Buttons"] }
  ],
  "categories": [{ "key": "colors", "label": "Colours", "score": 0.84, "deviations": [...] }, ...]
}
GET/adherence/{id}free

Status and stages. Add ?include=sides for both brand systems.

curl https://brand.trycanopy.space/api/v1/adherence/adh_... -H 'X-API-Key: YOUR_API_KEY'
GET/adherence?status=&q=&limit=&offset=free

Your verifications, newest first, with total.

curl 'https://brand.trycanopy.space/api/v1/adherence?limit=20' -H 'X-API-Key: YOUR_API_KEY'
PATCH/adherence/{id}/visibilityfree

Share a verdict: public runs are readable from /result without a key.

{ "is_public": true }
curl -X PATCH https://brand.trycanopy.space/api/v1/adherence/adh_.../visibility -H 'X-API-Key: YOUR_API_KEY' -H 'Content-Type: application/json' -d '{ "is_public": true }'

Account

Balance and usage for the key's owner.

GET/mefree

Profile, plan and credit balance.

curl https://brand.trycanopy.space/api/v1/me -H 'X-API-Key: YOUR_API_KEY'
GET/usage?feature=extraction&days=30free

Daily usage series, totals and latency.

curl 'https://brand.trycanopy.space/api/v1/usage?days=7' -H 'X-API-Key: YOUR_API_KEY'

MCP server

Streamable-HTTP MCP endpoint at https://brand.trycanopy.space/api/mcp, authenticated with your API key as a bearer token. The tools mirror the API one to one, so an agent can run the whole extract → build → verify loop in one conversation.

claude mcp add --transport http onbrand https://brand.trycanopy.space/api/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"
extract_brand(url, force?, sections?, map?)Start an extraction; returns extraction_id.
poll_brand_extraction(extraction_id)Lightweight status + which sections have landed.
get_brand_extraction_result(extraction_id, sections?)Read the brand system; narrow with sections.
list_brand_extractions(status?, search?)Your past extractions.
get_brand_brief / get_brand_tokens / enhance_promptMarkdown brief, CSS · Tailwind · JSON tokens, grounded prompts.
search_brands(query, depth?, top_k?, filters?)Find brands by a description of a look.
search_similar_brands(extraction_id, top_k?)Visual neighbours of a brand you extracted.
verify_brand_adherence(reference_url, candidate_url)Start a verification; returns adherence_id.
poll_brand_adherence / get_brand_adherence_resultStatus, then score + recommendations + fixes.
list_brand_adherence_jobs / get_creditsHistory and balance.

Agent skills

Skills teach skill-aware agents (Claude Code, Codex, Cursor and others) how to use the tools well. onbrand-search finds real references before the agent commits to a look and inspects every result. onbrand-adherence builds a page from a brand's exact values, then verifies it and applies the fixes. Install the skills, and connect the MCP server so they have tools to call.

npx skills add PrateekDevashetti/OnBrand-API

TypeScript SDK

import { OnBrand } from "@canopylabs/onbrand";

const onbrand = new OnBrand({ apiKey: process.env.ONBRAND_API_KEY });

const ext = await onbrand.extract({ url: "https://linear.app", wait: true });
const brand = await onbrand.result(ext.id, { sections: ["colors", "typography"] });
const tokens = await onbrand.tokens(ext.id, "tailwind");

const { results } = await onbrand.search({ query: "warm editorial fintech", filters: { industry: "fintech" } });

const verdict = await onbrand.verify({ reference_url: "https://linear.app", candidate_url: "https://my-redesign.vercel.app", wait: true });
console.log(verdict.score, verdict.fixes, verdict.recommendations);

Filter vocabulary

Search filters are hard constraints matched against this closed vocabulary.

page_typehomepage · about · pricing · careers · portfolio · product · blog · case_study · landing_page · services
industrysaas · ai · developer_tools · creative_agency · branding · design · fintech · crypto · e_commerce · fashion · beauty · food_beverage · hardware · travel · education · media · productivity · architecture
huered · orange · yellow · green · teal · blue · purple · pink · brown · neutral
layoutgenerous_whitespace · dense_packed · asymmetric_broken_grid · grid_based_strict · centered_symmetric · card_based

Errors & limits

400 invalid_request · invalid_jsonBody failed validation; the message names the field.
401 unauthorizedMissing, invalid or revoked API key.
402 insufficient_creditsNot enough credits. Includes balance and needed. Failed jobs are refunded automatically.
404 not_foundUnknown id, or it belongs to another account and isn't public.
409 not_readyThe job hasn't produced anything yet. Keep polling (retry_after_ms is a hint).
422 invalid_sections · invalid_filterA section name or filter value isn't in the vocabulary.
422 selective_map_unsupportedsections can't be combined with map mode or deep depth.
422 full_extraction_requiredSimilar search and enhance need a full extraction.
422 same_urlReference and candidate are the same page.
424 adherence_failedThe verification failed for good. Stop polling; credits are refunded.
500 internal_errorSomething broke on our side. Retries are safe.