> ## Documentation Index
> Fetch the complete documentation index at: https://docs.context.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Extract a Website's Design System

> Pull colors, typography, spacing, shadows, and component styles from a single API call

Context.dev's Styleguide API returns a website's full styleguide:

* `mode`: `light` or `dark`
* `colors`: `accent`, `background`, `text` (hex)
* `typography`: detected headings (`h1`–`h4`) and `p` with `fontFamily`, `fontFallbacks`, `fontSize`, `fontWeight`, `lineHeight`, `letterSpacing`
* `elementSpacing`: `xs`, `sm`, `md`, `lg`, `xl`
* `shadows`: `sm`, `md`, `lg`, `xl`, `inner`
* `components`: detected button variants (`primary`, `secondary`, `link`) and `card` with full computed CSS
* `fontLinks`: Google or `@font-face` font files keyed by family, with downloadable URLs per weight

A separate fonts-only endpoint returns the typography subset for a fraction of the cost.

<Prompt description="Integrate Context.dev's Design System API in your app" icon="sparkles" actions={["copy", "cursor"]}>
  I'm integrating Context.dev's Design System API into my app. Help me:

  1. Install the official SDK for my language (`context.dev` on npm / PyPI / RubyGems, `context-dev/context-dev-php` on Packagist, `github.com/context-dot-dev/context-go-sdk` for Go).
  2. Read the API key from the `CONTEXT_DEV_API_KEY` environment variable. Never hardcode it.
  3. Call `/web/styleguide` with either `domain` or `directUrl` (not both) to extract `mode`, `colors`, `typography`, `elementSpacing`, `shadows`, `components`, and `fontLinks`. Use `/web/fonts` instead when I only need the typography subset.
  4. The methods live on `client.web.extractStyleguide` / `client.web.extractFonts` (snake\_case `extract_styleguide` / `extract_fonts` in Python and Ruby).
  5. Map the response onto my theme: `styleguide.colors` to a palette, `typography.headings.h1.fontFallbacks` to a font stack, `elementSpacing` to a spacing ramp, `shadows` to elevation tokens, `fontLinks[<family>].files` to `@font-face` URLs.
  6. Wrap calls with retry logic for HTTP 408 (timeout) and 429 (rate limit) using exponential backoff.

  Docs: [https://docs.context.dev/guides/extract-design-system-from-website](https://docs.context.dev/guides/extract-design-system-from-website)
</Prompt>

## Prerequisites

* **A Context.dev API key.** Sign up at [context.dev/signup](https://context.dev/signup), copy the key from the [dashboard](https://context.dev/dashboard) (prefix `ctxt_secret_`), and export it:

  ```bash theme={null}
  export CONTEXT_DEV_API_KEY="ctxt_secret_..."
  ```

* **An SDK (optional).** Install for your language, or skip the install and call directly with `curl`:

  <CodeGroup>
    ```bash TypeScript theme={null}
    npm install context.dev
    ```

    ```bash Python theme={null}
    pip install context.dev
    ```

    ```bash Ruby theme={null}
    gem install context.dev
    ```

    ```bash Go theme={null}
    go get github.com/context-dot-dev/context-go-sdk
    ```

    ```bash PHP theme={null}
    composer require context-dev/context-dev-php
    ```
  </CodeGroup>

## Extract the full styleguide

`GET /web/styleguide` takes in a domain (or a webpage URL) and returns the website's full design system.

<CodeGroup>
  ```bash cURL theme={null}
  curl -G https://api.context.dev/v1/web/styleguide \
    -H "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
    --data-urlencode "domain=vercel.com"
  ```

  ```typescript TypeScript theme={null}
  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);
  ```

  ```python Python theme={null}
  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)
  ```

  ```ruby Ruby theme={null}
  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
  ```

  ```go Go theme={null}
  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 PHP theme={null}
  <?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;
  ```
</CodeGroup>

<Badge color="blue" icon="coins">10 credits per successful call</Badge>

### Request Parameters

<Warning>
  Either `domain` or `directUrl` must be provided, but not both.
</Warning>

| 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`).          |
| `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

```json expandable theme={null}
{
  "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: <ul><li>`backgroundColor`</li><li>`color`</li><li>`borderColor`</li><li>`borderRadius`</li><li>`borderWidth`</li><li>`borderStyle`</li><li>`padding`</li><li>`minWidth`</li><li>`minHeight`</li><li>`fontFamily`</li><li>`fontFallbacks`</li><li>`fontSize`</li><li>`fontWeight`</li><li>`textDecoration`</li><li>`textDecorationColor`</li><li>`boxShadow`</li><li>`css`: ready-to-paste declaration block</li></ul> |
| `styleguide.components.card`     | object  | <ul><li>`backgroundColor`</li><li>`textColor`</li><li>`borderColor`</li><li>`borderRadius`</li><li>`borderWidth`</li><li>`borderStyle`</li><li>`padding`</li><li>`boxShadow`</li><li>`css`: ready-to-paste declaration block</li></ul>                                                                                                                                                                                                                                                  |

## 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.

<CodeGroup>
  ```bash cURL theme={null}
  curl -G https://api.context.dev/v1/web/fonts \
    -H "Authorization: Bearer $CONTEXT_DEV_API_KEY" \
    --data-urlencode "domain=vercel.com"
  ```

  ```typescript TypeScript theme={null}
  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`);
  }
  ```

  ```python Python theme={null}
  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")
  ```

  ```ruby Ruby theme={null}
  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
  ```

  ```go Go theme={null}
  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 PHP theme={null}
  <?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;
  }
  ```
</CodeGroup>

<Badge color="blue" icon="coins">5 credits per successful call</Badge>

### Request Parameters

<Warning>
  Either `domain` or `directUrl` must be provided, but not both.
</Warning>

| 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

```json expandable theme={null}
{
  "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. |

A 400 from omitting both `domain` and `directUrl` looks like:

```json expandable theme={null}
{
  "status": "error",
  "error_code": "INPUT_VALIDATION_ERROR",
  "message": [
    { "code": "custom", "message": "Either directUrl or domain must be provided, but not both", "path": [] }
  ]
}
```

For the full catalog of error codes, see [Troubleshooting](/optimization/troubleshooting).

## Use cases

* [Build faster, brand-aware onboarding flows](/use-cases/faster-onboarding-flows)
* [Build branded email templates from a website](/use-cases/custom-email-templates)
* [Generate branded marketing assets at scale](/use-cases/branded-campaign-assets)

## Next steps

<CardGroup cols={2}>
  <Card title="Prefetch for Faster Response" icon="bolt" href="/optimization/prefetching">
    Hide cold-hit latency from your users.
  </Card>

  <Card title="Handle Rate Limits" icon="gauge-high" href="/optimization/rate-limits">
    Backoff strategies, client cache, and prefetch fallbacks.
  </Card>

  <Card title="Best Practices" icon="list-check" href="/optimization/best-practices">
    Caching, error handling, and key hygiene.
  </Card>

  <Card title="Troubleshooting" icon="bug" href="/optimization/troubleshooting">
    Status codes, retry patterns, and common errors.
  </Card>
</CardGroup>
