mode:lightordarkcolors:accent,background,text(hex)typography: detected headings (h1–h4) andpwithfontFamily,fontFallbacks,fontSize,fontWeight,lineHeight,letterSpacingelementSpacing:xs,sm,md,lg,xlshadows:sm,md,lg,xl,innercomponents: detected button variants (primary,secondary,link) andcardwith full computed CSSfontLinks: Google or@font-facefont files keyed by family, with downloadable URLs per weight
Integrate Context.dev's Design System API in your app
Prerequisites
-
A Context.dev API key. Sign up at context.dev/signup, copy the key from the dashboard (prefix
ctxt_secret_), and export it:export CONTEXT_DEV_API_KEY="ctxt_secret_..." -
An SDK (optional). Install for your language, or skip the install and call directly with
curl:npm install context.devpip install context.devgem install context.devgo get github.com/context-dot-dev/context-go-sdkcomposer require context-dev/context-dev-php
Extract the full styleguide
GET /web/styleguide takes in a domain (or a webpage URL) and returns the website’s full design system.
curl -G https://api.context.dev/v1/web/styleguide \
-H "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
--data-urlencode "domain=vercel.com"
import ContextDev from "context.dev";
const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });
const response = await client.web.extractStyleguide({ domain: "vercel.com" });
console.log(response.styleguide.colors);
console.log(response.styleguide.typography.headings.h1.fontFamily);
import os
from context.dev import ContextDev
client = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])
response = client.web.extract_styleguide(domain="vercel.com")
print(response.styleguide.colors)
print(response.styleguide.typography.headings.h1.font_family)
require "context_dev"
client = ContextDev::Client.new(api_key: ENV.fetch("CONTEXT_DEV_API_KEY"))
response = client.web.extract_styleguide(domain: "vercel.com")
puts response.styleguide.colors.to_h.inspect
puts response.styleguide.typography.headings.h1.font_family
package main
import (
"context"
"fmt"
"os"
contextdev "github.com/context-dot-dev/context-go-sdk"
"github.com/context-dot-dev/context-go-sdk/option"
"github.com/context-dot-dev/context-go-sdk/packages/param"
)
func main() {
client := contextdev.NewClient(
option.WithAPIKey(os.Getenv("CONTEXT_DEV_API_KEY")),
)
response, err := client.Web.ExtractStyleguide(context.TODO(), contextdev.WebExtractStyleguideParams{
Domain: param.NewOpt("vercel.com"),
})
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", response.Styleguide.Colors)
fmt.Println(response.Styleguide.Typography.Headings.H1.FontFamily)
}
<?php
use ContextDev\Client;
$client = new Client(apiKey: getenv('CONTEXT_DEV_API_KEY'));
$response = $client->web->extractStyleguide(domain: 'vercel.com');
print_r($response->styleguide->colors);
echo $response->styleguide->typography->headings->h1->fontFamily, PHP_EOL;
Request Parameters
Either
domain or directUrl must be provided, but not both.| Parameter | Type | Description |
|---|---|---|
domain | string | Domain to extract the styleguide from (e.g. vercel.com). |
directUrl | string | A specific URL to fetch the styleguide from directly, bypassing domain resolution (e.g. https://stripe.com/pricing). |
colorScheme | enum (light / dark) | Browser color scheme to emulate for sites that respond to prefers-color-scheme. Included in the cache key. |
maxAgeMs | integer | Max cache age before a hard refresh. Defaults to 7776000000 (90 days). Clamped to [86400000, 31536000000] (1 day – 1 year). |
timeoutMS | integer (1000–300000) | Abort with a 408 if the request exceeds this many milliseconds. Max 300000 (5 min). |
Response
{
"status": "ok",
"domain": "vercel.com",
"code": 200,
"styleguide": {
"mode": "light",
"colors": {
"accent": "#000000",
"background": "#fafafa",
"text": "#171717"
},
"typography": {
"headings": {
"h1": {
"fontFamily": "Geist",
"fontFallbacks": ["Geist", "Arial", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"],
"fontSize": "35px",
"fontWeight": 600,
"lineHeight": "46.0687px",
"letterSpacing": "-1.945px"
}
},
"p": {
"fontFamily": "Geist",
"fontFallbacks": ["Geist", "Arial", "system-ui", "-apple-system", "Segoe UI", "Roboto", "Helvetica Neue", "sans-serif"],
"fontSize": "16px",
"fontWeight": 400,
"lineHeight": "24px",
"letterSpacing": "0px"
}
},
"elementSpacing": {
"xs": "2px",
"sm": "6px",
"md": "16px",
"lg": "22px",
"xl": "40px"
},
"shadows": {
"sm": "rgba(0, 0, 0, 0.08) 0px 0px 0px 1px, rgba(0, 0, 0, 0.04) 0px 2px 2px 0px, rgba(0, 0, 0, 0.04) 0px 8px 8px -8px",
"md": "none",
"lg": "none",
"xl": "none",
"inner": "none"
},
"fontLinks": {
"Geist": {
"type": "google",
"category": "sans-serif",
"files": {
"400": "https://fonts.gstatic.com/s/geist/v4/gyBhhwUxId8gMGYQMKR3pzfaWI_RnOM4nZPby1QNtA.ttf",
"500": "https://fonts.gstatic.com/s/geist/v4/gyBhhwUxId8gMGYQMKR3pzfaWI_RruM4nZPby1QNtA.ttf",
"600": "https://fonts.gstatic.com/s/geist/v4/gyBhhwUxId8gMGYQMKR3pzfaWI_RQuQ4nZPby1QNtA.ttf",
"700": "https://fonts.gstatic.com/s/geist/v4/gyBhhwUxId8gMGYQMKR3pzfaWI_Re-Q4nZPby1QNtA.ttf"
}
}
},
"components": {
"button": {
"primary": {
"backgroundColor": "#171717",
"color": "#ffffff",
"borderColor": "#ffffff",
"borderRadius": "100px",
"borderWidth": "0px",
"borderStyle": "none",
"padding": "13px 14px",
"minWidth": "181px",
"minHeight": "40px",
"fontFamily": "Geist",
"fontSize": "14px",
"fontWeight": 500,
"textDecoration": "none",
"boxShadow": "none",
"css": "background-color: #171717; color: #ffffff; border-radius: 100px; padding: 13px 14px; font-family: Geist; font-size: 14px; font-weight: 500;"
}
},
"card": {
"backgroundColor": "#fafafa",
"textColor": "#171717",
"borderColor": "#e5e7eb",
"borderRadius": "8px",
"borderWidth": "1px",
"borderStyle": "solid",
"padding": "16px",
"boxShadow": "none",
"css": "background-color: #fafafa; color: #171717; border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px;"
}
}
}
}
| Field | Type | Description |
|---|---|---|
status | string | "ok" on success. |
domain | string | Normalized domain that was processed. |
code | integer | HTTP status code. |
styleguide.mode | string | "light" or "dark": the page’s primary color mode. |
styleguide.colors | object | accent, background, text: all hex strings. |
styleguide.typography.headings | object | h1, h2, h3, h4: each has fontFamily, fontFallbacks[], fontSize, fontWeight, lineHeight, letterSpacing. |
styleguide.typography.p | object | Body text: same shape as a heading entry. |
styleguide.elementSpacing | object | xs, sm, md, lg, xl: CSS length strings (typically px). |
styleguide.shadows | object | sm, md, lg, xl, inner: full box-shadow declarations. "none" when no shadow at that tier. |
styleguide.fontLinks | object | Keyed by font family name. Each entry has type (google or custom), files (weight → URL), optional category (Google only), optional displayName (custom only). Omitted when no family resolves to Google or @font-face URLs. |
styleguide.components.button | object | Three variants: primary, secondary, link. Each variant has:
|
styleguide.components.card | object |
|
Extract just the fonts
GET /web/fonts takes a domain (or a direct URL) and returns only the typography subset: every font family the site uses, with weights, fallbacks, usage counts, and downloadable file URLs.
curl -G https://api.context.dev/v1/web/fonts \
-H "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
--data-urlencode "domain=vercel.com"
import ContextDev from "context.dev";
const client = new ContextDev({ apiKey: process.env.CONTEXT_DEV_API_KEY });
const response = await client.web.extractFonts({ domain: "vercel.com" });
for (const f of response.fonts) {
console.log(`${f.font}: ${f.percent_words.toFixed(1)}% of words`);
}
import os
from context.dev import ContextDev
client = ContextDev(api_key=os.environ["CONTEXT_DEV_API_KEY"])
response = client.web.extract_fonts(domain="vercel.com")
for f in response.fonts:
print(f"{f.font}: {f.percent_words:.1f}% of words")
require "context_dev"
client = ContextDev::Client.new(api_key: ENV.fetch("CONTEXT_DEV_API_KEY"))
response = client.web.extract_fonts(domain: "vercel.com")
response.fonts.each do |f|
puts "#{f.font}: #{f.percent_words.round(1)}% of words"
end
package main
import (
"context"
"fmt"
"os"
contextdev "github.com/context-dot-dev/context-go-sdk"
"github.com/context-dot-dev/context-go-sdk/option"
"github.com/context-dot-dev/context-go-sdk/packages/param"
)
func main() {
client := contextdev.NewClient(
option.WithAPIKey(os.Getenv("CONTEXT_DEV_API_KEY")),
)
response, err := client.Web.ExtractFonts(context.TODO(), contextdev.WebExtractFontsParams{
Domain: param.NewOpt("vercel.com"),
})
if err != nil {
panic(err)
}
for _, f := range response.Fonts {
fmt.Printf("%s: %.1f%% of words\n", f.Font, f.PercentWords)
}
}
<?php
use ContextDev\Client;
$client = new Client(apiKey: getenv('CONTEXT_DEV_API_KEY'));
$response = $client->web->extractFonts(domain: 'vercel.com');
foreach ($response->fonts as $f) {
echo $f->font, ': ', number_format($f->percentWords, 1), '% of words', PHP_EOL;
}
Request Parameters
Either
domain or directUrl must be provided, but not both.| Parameter | Type | Description |
|---|---|---|
domain | string | Domain to extract fonts from (e.g. vercel.com). Normalized automatically. |
directUrl | string (URI) | A specific URL to fetch fonts from directly, bypassing domain resolution. |
maxAgeMs | integer | Max cache age before a hard refresh. Defaults to 7776000000 (90 days). Clamped to [86400000, 31536000000] (1 day – 1 year). |
timeoutMS | integer (1000–300000) | Abort with a 408 if the request exceeds this many milliseconds. Max 300000 (5 min). |
Response
{
"status": "ok",
"domain": "vercel.com",
"code": 200,
"fonts": [
{
"font": "Geist",
"uses": ["body", "div", "header", "nav", "h1", "h2", "h3", "p", "button"],
"fallbacks": ["Arial", "system-ui", "-apple-system", "Segoe UI", "Roboto", "sans-serif"],
"num_elements": 534,
"num_words": 9320,
"percent_elements": 91,
"percent_words": 99
},
{
"font": "Geist Mono",
"uses": ["code", "pre", "kbd"],
"fallbacks": ["ui-monospace", "SFMono-Regular", "Menlo", "monospace"],
"num_elements": 55,
"num_words": 94,
"percent_elements": 9,
"percent_words": 1
}
],
"fontLinks": {
"Geist": {
"type": "google",
"category": "sans-serif",
"files": {
"400": "https://fonts.gstatic.com/s/geist/v4/gyBhhwUxId8gMGYQMKR3pzfaWI_RnOM4nZPby1QNtA.ttf",
"500": "https://fonts.gstatic.com/s/geist/v4/gyBhhwUxId8gMGYQMKR3pzfaWI_RruM4nZPby1QNtA.ttf",
"600": "https://fonts.gstatic.com/s/geist/v4/gyBhhwUxId8gMGYQMKR3pzfaWI_RQuQ4nZPby1QNtA.ttf",
"700": "https://fonts.gstatic.com/s/geist/v4/gyBhhwUxId8gMGYQMKR3pzfaWI_Re-Q4nZPby1QNtA.ttf"
}
}
}
}
| Field | Type | Description |
|---|---|---|
status | string | "ok" on success. |
domain | string | Normalized domain that was processed. |
code | integer | HTTP status code. |
fonts[] | array | One entry per font family on the page, ordered by usage. |
fonts[].font | string | Primary face (first family in the computed stack). |
fonts[].uses[] | array | CSS selectors or element types where this font is used. |
fonts[].fallbacks[] | array | Fallback families from the computed stack. |
fonts[].num_elements | number | Element count using this font. |
fonts[].num_words | number | Word count rendered in this font. |
fonts[].percent_elements | number | Share of all elements using this font. |
fonts[].percent_words | number | Share of all words rendered in this font. |
fontLinks | object | Optional. Same shape as styleguide.fontLinks: keyed by family with type, files, optional category and displayName. Omitted when no family resolves to Google or @font-face URLs. |
Handle errors
A non-2xx response returns an{ message, error_code, status } envelope. The common ones:
| Status | error_code | Meaning | What to do |
|---|---|---|---|
| 400 | INPUT_VALIDATION_ERROR | Missing both domain and directUrl, or both were supplied | Pass exactly one. |
| 401 | UNAUTHORIZED | API key missing, invalid, or deleted | Re-check the env var and the dashboard. |
| 408 | REQUEST_TIMEOUT | Page didn’t render before timeoutMS (or 5 min default) | Retry once with backoff; the second hit usually succeeds. |
| 500 | INTERNAL_ERROR | Transient server error | Retry once with backoff; if it persists, contact support. |
domain and directUrl looks like:
{
"status": "error",
"error_code": "INPUT_VALIDATION_ERROR",
"message": [
{ "code": "custom", "message": "Either directUrl or domain must be provided, but not both", "path": [] }
]
}
Use cases
- Build faster, brand-aware onboarding flows
- Build branded email templates from a website
- Generate branded marketing assets at scale
Next steps
Prefetch for Faster Response
Hide cold-hit latency from your users.
Handle Rate Limits
Backoff strategies, client cache, and prefetch fallbacks.
Best Practices
Caching, error handling, and key hygiene.
Troubleshooting
Status codes, retry patterns, and common errors.