# Canopy - Full documentation

> Every Canopy docs page, concatenated for LLMs and agents. Canonical pages live under https://canopy.8starlabs.com/docs. Short index: https://canopy.8starlabs.com/llms.txt


---

# Source: https://canopy.8starlabs.com/docs

import { ExportFlip } from "@/components/landing/viz/export-flip";

export const metadata = {
  title: "Introduction | Canopy Docs",
  description:
    "Learn how Canopy maps software systems and operational workflows, imports repositories, tracks cost, and exports context for AI agents.",
  alternates: { canonical: "/docs" },
};

# Introduction

Canopy is a visual operational architecture workspace. **Draw a system before you
build it**, or point it at a repo to **map a stack you've shipped** - then
**operate** it with cost, metadata, and AI exports. Your nodes, edges, and cost
stay first-class, machine-readable data: one map is at once an interactive
canvas, a cost rollup, Markdown in sync, and  your agents can read.

  A "system" isn't only a tech stack - map any **operational workflow** too: a
  marketing pipeline (HubSpot → Zapier → Mailchimp → GA4), RevOps, or internal
  automation. The library covers CRM, marketing, and automation tools, and maps
  tag as **Marketing** or **Operations**.

## The ideal flow

## Who is Canopy for

If you can describe how the pieces connect - a tech stack or an operational
workflow - Canopy keeps it as living, structured data you own.

## What makes Canopy different

- **Guided input, not a blank canvas.** Import a GitHub repo or pick from 200+ tools, AI platforms, and workflows.
- **Operational by default.** Owner, SLA, status, and cost live on every node.
- **AI-native.** Export `CLAUDE.md` / `AGENTS.md` and expose your map over MCP.
- **Always in sync.** `canopy.json` ⇄ canvas - describe a system and Canopy renders it both ways.
- **Structured & yours.** Every map is data you own - exportable and portable.

## Where to next


---

# Source: https://canopy.8starlabs.com/docs/accounts

export const metadata = {
  title: "Accounts & workspaces | Canopy Docs",
  description:
    "Understand Canopy accounts, workspaces, roles, members, maps, and the permissions that control access to your systems.",
  alternates: { canonical: "/docs/accounts" },
};

# Accounts & workspaces

Canopy is built around two layers: an **account** (you) and a **workspace** (your
team and its maps). Everything useful - [maps](/docs/maps), members, roles, and
billing - lives inside a workspace.

## Accounts

An account is you. You sign in once - via **GitHub** or **email and password** -
and your account follows you across every workspace you belong to. One email
address, one account.

Your account holds your personal profile, your [Canopy Card](/docs/canopy-card),
and settings like your display name, handle, and avatar. That public identity is
used on marketplace author cards and account previews. Deleting your account
removes your personal data across all workspaces.

You can add **two-factor authentication** - a 6-digit code from an authenticator
app, required at every sign-in including GitHub ones. It comes with 10 single-use
recovery codes in case you lose your phone. See
[Two-factor authentication](/docs/security/two-factor).

## Workspaces

A workspace is the **team and billing boundary**. [Maps](/docs/maps), members,
[roles](/docs/team-billing), and subscriptions all live inside a single workspace.

- Every workspace has exactly one **Owner**, who controls billing and
  invitations.
- Every invited member - regardless of [role](/docs/team-billing) - occupies a
  **paid seat** on the workspace's Pro [subscription](/docs/team-billing/subscriptions).
- Each workspace has its own **subscription**, separate from any other workspace
  you own or belong to.
- Each workspace gets a unique **handle** (e.g. `/w/acme-corp`), which becomes
  its public profile URL if you choose to publish maps. Marketplace workspace
  previews use this workspace identity, separate from your account preview.

## Field rules

The same limits apply in the app and on the server, so what you type is what
gets saved:

| Field                              | Requirement                                                                     |
| ---------------------------------- | ------------------------------------------------------------------------------- |
| Display name                       | 2 to 50 characters                                                              |
| Handle (account **and** workspace) | 3 to 30 characters; lowercase letters, numbers and hyphens only; must be unique |
| Password                           | 8 to 72 characters                                                              |
| Workspace name                     | 2 to 40 characters                                                              |
| Role title                         | up to 60 characters                                                             |
| Email                              | up to 120 characters                                                            |

Most text fields let you keep typing past the limit but flag it inline and block
saving until it is back in range. Handles are lowercased automatically, and
passwords are hard-capped at 72 characters (the authentication limit).

## How they fit together

```
Account (you)
  └─ owns → Workspace (team + billing)
               ├─ Members (each a paid seat)
               └─ Maps
                    └─ Nodes + Edges
```

One person can own multiple workspaces (each billed separately) and be a member
of workspaces owned by others - with a different [role](/docs/team-billing) in each.

## Free workspaces

Every account can own **up to two free workspaces**. To create more, upgrade one
to Pro first - that frees a slot. Paid workspaces (Pro) are unlimited. See
[Subscriptions & seats](/docs/team-billing/subscriptions) for plan limits.

  A workspace downgraded from Pro to Free is never deleted. If a downgrade would
  put you over the two-free-workspace cap, the surplus one goes **read-only**
  until you re-upgrade one, delete one, or free a slot. Your two oldest free
  workspaces stay editable; newer surplus ones become read-only (you can still
  read them, and re-upgrading or deleting a free workspace unlocks the rest).

## Workspace verification

Pro workspace owners can apply for a **verified badge** (the blue seal) to show
buyers that their public maps and templates come from a trusted source. Apply
from **Workspace settings → Verification**. See [Trust &
verification](/docs/marketplace/trust) for the full requirements.

## Roles are per workspace

The same person can be an Owner in one workspace and a Viewer in another. Roles
and seat costs are always scoped to a single workspace. See [Roles &
permissions](/docs/team-billing) for what each role can do.

## Dormant accounts

Inactive **free** accounts move through a notice-first lifecycle before any data
is removed. Signing in resets the clock at any stage, and **accounts on an active
paid plan are never treated as dormant**.

- **3 months inactive** → a **warning email** with ~30 days to sign back in.
- **+30 days** with no sign-in → **suspended**: your login is disabled and all
  workspaces you own become inaccessible, but **nothing is deleted**. The data
  is preserved.
- **+30 days** still suspended → **permanently deleted** (a final-warning email
  goes out ~7 days before). Maps other people have **purchased are kept**
  (archived) so buyers aren't affected.

Reactivate a suspended account anytime before deletion through **chat support**
(the chat bubble on any page, available even when you're signed out); we'll
confirm the account with you in the conversation. If your paid plan lapses to
Free, the dormancy clock starts from the **downgrade date**, not your last
sign-in. Full details, including marketplace and Stripe specifics, are in the
[Dormant Account Policy](/dormant-account-policy).


---

# Source: https://canopy.8starlabs.com/docs/api

export const metadata = {
  title: "API & MCP | Canopy Docs",
  description:
    "Connect coding agents and applications to Canopy with the REST API, MCP server, workspace tokens, and map operations.",
  alternates: { canonical: "/docs/api" },
};

# API 

Canopy's **REST API** lets you read and write your [maps](/docs/maps)
programmatically - a map is plain JSON you own. Prefer to let coding agents query
your architecture instead? That's the [MCP server](/docs/mcp), a sibling of this
API that shares the same token and rate limit.

  The REST API and MCP server require Pro - token creation is only available on
  paid workspaces. Building and exporting maps in the app stays free; only
  programmatic access needs a paid workspace. [See plans](/pricing).

## What's here

- **[Authentication](/docs/api/authentication)** - create and use a workspace token.
- **[Maps API](/docs/api/maps)** - list, get, create, update, delete, and export maps.
- **[Rate limits & errors](/docs/api/rate-limits)** - quotas, headers, and the error catalog.

The JSON you read and write follows the
[map syntax](/docs/maps/architecture-as-code) - that reference lists every node
and edge field and its accepted values.

## x402 & MPP  

Agent-native payments. **x402** and **MPP** (machine protocol payments) let an
agent pay for a template or metered API access on its own, over the API, with no
human in the loop.

  This is a heads-up, not a shipped capability. x402 & MPP are on the roadmap as
  a Pro feature - the badges above mark it as coming soon.


---

# Source: https://canopy.8starlabs.com/docs/api/authentication

export const metadata = {
  title: "Authentication | Canopy Docs",
  description:
    "Authenticate Canopy REST API and MCP requests with a workspace token and control which workspace an agent can access.",
  alternates: { canonical: "/docs/api/authentication" },
};

# Authentication 

The REST API authenticates with a **workspace token** sent as a bearer header.
(The [MCP server](/docs/mcp) uses the same token, and additionally supports
[OAuth](/docs/mcp/oauth) for agent clients.)

## Workspace tokens

A workspace API token is a long-lived secret you create yourself. It's scoped to
a single workspace and carries that workspace's permissions - treat it like a
password. Send it as a bearer header on every request:

The same token works for the MCP server - see
[Connect with a token](/docs/mcp/token).

## Create a token

Only the **workspace owner** can create, view, or revoke API tokens - Admins,
Editors, and Viewers can't. Create one in **Settings → Workspace →
[API tokens](/dashboard/settings/workspace/api)**:

  
    Go to **Dashboard → Settings → Workspace → API tokens**. Tokens belong to
    the workspace you have open, so switch workspaces first if you need a
    different one.
  
  
    Give it a name (e.g. `CI pipeline`), pick an **expiry** (never, or 30–365
    days), and click **Create**. The secret - `cnpy_live_…` - is shown **once**;
    copy it immediately, since only a SHA‑256 hash is stored.
  
  
    Send it as a bearer token (above), stored in an env var like `CANOPY_TOKEN`
    - never commit it to source control.
  

  Anyone holding a token can read and write that workspace's maps. If one leaks,
  revoke it right away and create a replacement.

A workspace can hold up to **10 active tokens** at once - revoke one to free a
slot.

## Revoke a token

Open **Settings → Workspace → [API](/dashboard/settings/workspace/api)**, find the
token, and click **Revoke**. It stops working **immediately and everywhere** - the
next request from the REST API **or** any [MCP](/docs/mcp/token) client using it
returns `401 invalid_token`. To **rotate**, create a new token, update your
client, then revoke the old one.

For browser-based sign-in instead of a pasted token, see
[OAuth for MCP clients](/docs/mcp/oauth) (read-only).


---

# Source: https://canopy.8starlabs.com/docs/api/maps

export const metadata = {
  title: "Maps API | Canopy Docs",
  description:
    "Read and write Canopy maps programmatically with the Maps API, including nodes, edges, metadata, and exportable JSON.",
  alternates: { canonical: "/docs/api/maps" },
};

# Maps API 

The REST endpoints for reading and writing [maps](/docs/maps). Every call needs a
[workspace token](/docs/api/authentication); responses are JSON unless noted.
Base URL: `https://canopy.8starlabs.com/api/v1`.

## List maps

List every map in the workspace.

**Responses**

| Status                  | Meaning                                                         |
| ----------------------- | --------------------------------------------------------------- |
| `200 OK`                | An array of maps in `maps`.                                     |
| `401 Unauthorized`      | Missing or invalid token.                                       |
| `429 Too Many Requests` | Rate limit exceeded - see [Rate limits](/docs/api/rate-limits). |

## Get a map

Fetch a single map with its nodes and edges.

**Responses**

| Status                  | Meaning                                |
| ----------------------- | -------------------------------------- |
| `200 OK`                | The map, under `map`.                  |
| `401 Unauthorized`      | Missing or invalid token.              |
| `404 Not Found`         | No map with that id in your workspace. |
| `429 Too Many Requests` | Rate limit exceeded.                   |

## Create a map

Create a new map. The body is the architecture JSON.

**Responses**

| Status                  | Meaning                                              |
| ----------------------- | ---------------------------------------------------- |
| `201 Created`           | The new map, under `map`.                            |
| `400 Bad Request`       | Invalid payload (e.g. missing `name`); see `fields`. |
| `401 Unauthorized`      | Missing or invalid token.                            |
| `403 Forbidden`         | Map exceeds your plan's node limit.                  |
| `429 Too Many Requests` | Rate limit exceeded.                                 |

## Update a map

Update a map's name, nodes, edges, or metadata. Only the fields you send change.

**Responses**

| Status                  | Meaning                                |
| ----------------------- | -------------------------------------- |
| `200 OK`                | The updated map, under `map`.          |
| `400 Bad Request`       | Empty or invalid payload.              |
| `401 Unauthorized`      | Missing or invalid token.              |
| `404 Not Found`         | No map with that id in your workspace. |
| `429 Too Many Requests` | Rate limit exceeded.                   |

## Delete a map

Permanently delete a map.

**Responses**

| Status                  | Meaning                                |
| ----------------------- | -------------------------------------- |
| `200 OK`                | The map was deleted.                   |
| `401 Unauthorized`      | Missing or invalid token.              |
| `404 Not Found`         | No map with that id in your workspace. |
| `429 Too Many Requests` | Rate limit exceeded.                   |

## Export a map

Export a map as `png`, `markdown`, `claude`, `agents`, `figma`, or `notion`.

**Responses**

The body is the exported artifact itself - `text/markdown` for `markdown` /
`claude` / `agents`, `application/json` for `figma` / `notion`, `image/png` for
`png` - not a JSON envelope.

| Status                  | Meaning                                              |
| ----------------------- | ---------------------------------------------------- |
| `200 OK`                | The exported file, in the format's content type.     |
| `400 Bad Request`       | Unknown `:format`.                                   |
| `401 Unauthorized`      | Missing or invalid token.                            |
| `403 Forbidden`         | Format is gated behind Pro (e.g. `figma`, `notion`). |
| `404 Not Found`         | No map with that id in your workspace.               |
| `429 Too Many Requests` | Rate limit exceeded.                                 |

 Architecture map exported from Canopy.\n\n## Services\n\n- **API** - node · trpc ($2.1k)\n\n## Dependencies\n\n- web → api`,
    },
    {
      label: "400 Bad Request",
      lang: "json",
      code: `{\n  "error": "invalid_request",\n  "message": "Unknown export format \\"pdf\\". Supported: png, markdown, claude, agents, figma, notion."\n}`,
    },
    {
      label: "401 Unauthorized",
      lang: "json",
      code: `{\n  "error": "invalid_token",\n  "message": "The API token is invalid, expired, or revoked."\n}`,
    },
    {
      label: "403 Forbidden",
      lang: "json",
      code: `{\n  "error": "forbidden",\n  "message": "The \\"figma\\" export requires a Pro plan."\n}`,
    },
    {
      label: "404 Not Found",
      lang: "json",
      code: `{\n  "error": "not_found",\n  "message": "Map not found."\n}`,
    },
    {
      label: "429 Too Many Requests",
      lang: "json",
      code: `{\n  "error": "rate_limited",\n  "message": "Rate limit exceeded. Retry after 41s.",\n  "retryAfter": 41\n}`,
    },
  ]}
/>

## The map object

A `GET` returns a map wrapped in `{ "map": … }` (or `{ "maps": [ … ] }` for the
list). The full object:

| Field                     | Type    | Notes                                    |
| ------------------------- | ------- | ---------------------------------------- |
| `id`                      | string  | Stable map id (the `:id` in URLs).       |
| `workspaceId`             | string  | The workspace the map belongs to.        |
| `name`                    | string  | Map name.                                |
| `isPublic`                | boolean | Whether the map has a public share link. |
| `nodes`                   | Node[]  | The services - see below.                |
| `edges`                   | Edge[]  | The connections - see below.             |
| `createdAt` / `updatedAt` | string  | ISO‑8601 timestamps.                     |

**Node** - `id` is always present; the rest appear only when set:

| Field      | Type     | Notes                                                                                       |
| ---------- | -------- | ------------------------------------------------------------------------------------------- |
| `id`       | string   | Stable node key (edges reference it).                                                       |
| `label`    | string   | Display name.                                                                               |
| `tech`     | string   | Sub-label, e.g. `"node · trpc"`.                                                            |
| `spend`    | number   | Monthly cost as a plain number, e.g. `2100`. No currency symbol.                            |
| `currency` | string   | Optional ISO 4217 code (USD, EUR, GBP, JPY, CNY, CAD, AUD, CHF, INR, SGD). Defaults to USD. |
| `billing`  | string[] | Any of `seat`, `team`, `usage`, `flat`.                                                     |
| `x`, `y`   | number   | Canvas position.                                                                            |

**Edge** - `source` and `target` are node `id`s; every other field is optional
styling, matching the visual editor and `canopy.json` exactly (so an API
round-trip preserves it):

| Field        | Type    | Notes                                                                              |
| ------------ | ------- | ---------------------------------------------------------------------------------- |
| `source`     | string  | Start node `id` (**required**).                                                    |
| `target`     | string  | End node `id` (**required**).                                                      |
| `line`       | string  | Curve style: `bezier` · `smoothstep` · `step` · `straight`. Defaults to `bezier`.  |
| `label`      | string  | Text at the edge midpoint (max 120 chars). Handy for workflow handoffs/conditions. |
| `labelColor` | string  | Label text color: hex, `rgb()`/`hsl()`, or a named color.                          |
| `labelSize`  | number  | Label font size in px, `6`-`48` (default `10`).                                    |
| `labelFont`  | string  | Label typeface: `sketch` (Excalifont, default) · `sans` · `mono`.                  |
| `arrow`      | string  | Arrowheads / direction: `none` · `end` (single) · `both` (bidirectional).          |
| `color`      | string  | Stroke color: hex, `rgb()`/`hsl()`, or a named CSS color. Overrides the gradient.  |
| `dotColor`   | string  | Moving-dot color; defaults to the edge `color`.                                    |
| `animated`   | boolean | The moving flow dot. Defaults to `true`; `false` for a static line.                |
| `dashed`     | boolean | Dashed stroke. Defaults to `false`.                                                |

Unsafe colors (anything that isn't a hex / `rgb()` / `hsl()` / named color) are
rejected with `400`.

```json
{
  "map": {
    "id": "acme-platform",
    "workspaceId": "ws_8s7d6f5g",
    "name": "8StarLabs Platform",
    "isPublic": false,
    "nodes": [
      {
        "id": "web",
        "label": "Web",
        "tech": "next.js",
        "spend": 1200,
        "x": 0,
        "y": 96
      },
      {
        "id": "api",
        "label": "API",
        "tech": "node · trpc",
        "spend": 2100,
        "billing": ["flat"],
        "x": 240,
        "y": 120
      }
    ],
    "edges": [{ "source": "web", "target": "api", "line": "bezier" }],
    "createdAt": "2026-05-01T10:00:00.000Z",
    "updatedAt": "2026-06-01T12:30:00.000Z"
  }
}
```

### Request body

When you **create** or **update** a map you send only the editable fields -
`name`, `nodes`, `edges`, and optional `isPublic`. The server assigns `id`,
`workspaceId`, and the timestamps.

```json
{
  "name": "8StarLabs Platform",
  "nodes": [{ "id": "web", "label": "Web", "tech": "next.js" }],
  "edges": [{ "source": "web", "target": "api" }]
}
```


---

# Source: https://canopy.8starlabs.com/docs/api/rate-limits

export const metadata = {
  title: "Rate limits & errors | Canopy Docs",
  description:
    "Understand Canopy API and MCP rate limits, error responses, retry behavior, and practical ways to handle failed requests.",
  alternates: { canonical: "/docs/api/rate-limits" },
};

# Rate limits &amp; errors 

## Rate limits

The API is a **Pro feature**. Requests are rate limited **per workspace** (all of
a workspace's tokens share one quota - minting more doesn't raise it), and
[MCP](/docs/mcp) tool calls count against the **same** quota:

| Window                         | Limit        |
| ------------------------------ | ------------ |
| Reads (GET)                    | 600 / min    |
| Writes (POST · PATCH · DELETE) | 60 / min     |
| Burst (read + write)           | 30 / sec     |
| Daily total                    | 10,000 / day |

**Writes are held about ten times tighter than reads.** A write is the expensive
side: updating a map replaces its whole node and edge set, so one call rewrites
many rows. It is also the destructive side. Nothing legitimate needs write
volume - importing a repo is a single create, an editing session is a handful -
so the ceiling is set well above real use and well below what a looping client
can burn through. This mirrors how [GitHub](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api)
caps content-generating requests far below its general budget.

[MCP](/docs/mcp) tools draw from the same two buckets: the read tools count as
reads, and `create_map` / `update_map` / `delete_map` count as **writes**. The
same operation costs the same whether it arrives over REST or MCP.

The per-minute and burst limits cap how _fast_ you can call; the **daily total**
caps overall volume and **resets daily at 00:00 UTC** - the same instant for
every workspace. Hitting the daily cap returns `429` with
`"Daily request quota exceeded."` and a `Retry-After` counting down to midnight
UTC. Running into these ceilings at scale? [Talk to support](/support) and we'll
raise them.

Every response carries the current limit state in its headers, so you can back
off before you're throttled:

| Header                | Example | Meaning                                |
| --------------------- | ------- | -------------------------------------- |
| `RateLimit-Limit`     | `600`   | Requests allowed in the current window |
| `RateLimit-Remaining` | `588`   | Requests left before you're throttled  |
| `RateLimit-Reset`     | `41`    | Seconds until the window resets        |
| `Retry-After`         | `41`    | Seconds to wait - only sent on a `429` |

When you exceed the limit the API responds with `429 Too Many Requests` and a
JSON body describing when to retry:

  Honor the `Retry-After` header with exponential backoff and jitter rather than
  retrying immediately - tight retry loops just burn your remaining budget.
  Batch writes where you can.

You can watch today's usage (REST API vs MCP, split out) and the daily history in
**Dashboard → Settings → Workspace → API**.

  [Embedded maps](/docs/maps/embed) are a public, cached, credential-free
  surface - they carry no token, so they never touch these quotas. A
  widely-embedded map costs you nothing against your API limits.

## Abuse protection & auto-disable

A `429` is not a penalty - it just means "slow down." A well-behaved client that
honors `Retry-After` is never punished. But a client that **keeps hammering
through its own `429`s at a high sustained rate** (ignoring `Retry-After`) is
treated as abuse, and the token is **automatically disabled**.

- Detection is **rate-based**: it only trips on a sustained flood of requests
  sent _while already rate-limited_ (on the order of tens per second). Normal
  usage, and even an occasional retry after a limit, never trigger it.
- A disabled token stops authenticating immediately (`401 invalid_token`),
  exactly like a revoked one.
- **There is no re-enable.** The token shows as **Disabled** in
  **Settings → Workspace → API** with the reason; the fix is to **delete it and
  create a new one**. (Rotating is also the right move if a token has leaked -
  the disable usually means someone is misusing it.)

  Honor the `Retry-After` header with exponential backoff and jitter. Tight
  retry loops don't just burn your budget - sustained ones will get the token
  disabled.

## Log retention

Each request (REST and MCP) is recorded to a per-workspace activity log, surfaced
as **Recent requests** on the API page: what ran (the endpoint, or the MCP tool
name), which map it touched, who authorized it, the status, and how long it took.
Logs are **retained for 30 days, then automatically deleted**, and can be
filtered and exported as CSV. The log is for observability only; it's never used
for billing.

Two things it deliberately doesn't capture: **rejected sign-ins** (a `401` never
reaches the log) and requests **turned away during a burst** (those are refused
before any database work, which is what keeps a flood cheap). Logging is also
best-effort, so a database blip can drop a row. A quiet log is not proof that
nothing happened - for abuse specifically, rely on the auto-disable behaviour
above rather than on log volume.

## Errors

Every error shares the same JSON envelope - a machine-readable `error` code, a
human `message`, and sometimes extra fields (`fields`, `limit`, `retryAfter`).
Always branch on the HTTP status and the `error` code, never on `message` text.

| Status | `error` code      | When                                                                                                        |
| ------ | ----------------- | ----------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_request` | Malformed body, missing required field, or unknown export format.                                           |
| `401`  | `unauthorized`    | No bearer token supplied.                                                                                   |
| `401`  | `invalid_token`   | Token unknown, expired, or revoked.                                                                         |
| `403`  | `node_limit`      | Map exceeds your plan's node cap.                                                                           |
| `403`  | `forbidden`       | Feature gated behind a higher plan, or the credential is missing a required [scope](/docs/mcp#permissions). |
| `404`  | `not_found`       | The map doesn't exist in your workspace.                                                                    |
| `429`  | `rate_limited`    | Too many requests - back off per `Retry-After`.                                                             |
| `500`  | `server_error`    | Something failed on our side; safe to retry.                                                                |

A `500` carries an `X-Request-Id` header - include it when you contact support so
we can trace the failure. These are transient; retry with backoff.


---

# Source: https://canopy.8starlabs.com/docs/canopy-card

export const metadata = {
  title: "Canopy Card & badges | Canopy Docs",
  description:
    "Learn how Canopy Cards and badges represent your mapped systems, public profile, workspace identity, and product activity.",
  alternates: { canonical: "/docs/canopy-card" },
};

# Canopy Card & badges

Every Canopy account has a shareable **Canopy Card** - a living trophy that
reflects how much you've mapped. You earn a **badge** for each thing you do, and
the **combination** of your badges unlocks your overall **title**. Find yours in
**Account settings → Your Canopy Card**, and your public profile lives at
**canopy.8starlabs.com/a/&lt;your-handle&gt;**.

  Click a title preset, toggle **Free / Pro workspace** to see the premium
  background, and hover the title ladder to preview each title on the card.

## Badges & tiers

You earn three badges - one per "component" - and each rises through four
**tiers**: Bronze, Silver, Gold, Platinum. Here's exactly how each looks, and the
count needed to reach each tier:

| Badge           | Bronze | Silver | Gold | Platinum |
| --------------- | ------ | ------ | ---- | -------- |
| **Public maps** | 1      | 3      | 6    | 12       |
| **Likes**       | 3      | 10     | 25   | 60       |
| **Clones**      | 1      | 4      | 12   | 30       |

- All three badges count **public maps only** - private maps never affect your
  card. The **Public maps** badge counts how many of your maps are currently
  public; **likes** and **clones** are summed across only those public maps.
- The totals track your **current** public maps. Make a public map private and it
  (and its likes and clones) **immediately drops out** of the counts shown on your
  card and public profile - only your earned **title** is kept (see Titles below).
- Every tier - including **Platinum** - is **free to earn**. Platinum wears a
  premium holographic finish.
- Bronze/Silver/Gold/Platinum apply **only to the badges** - not to your title.

## Titles

Your **title** is simple: to unlock the next one, **all three badges** must reach
its tier. So you're only as high as your lowest badge - keep all three moving.

Every title is **free to earn** - Pro only changes the card's looks. A title,
once earned, is **kept forever** - deleting a map or losing likes never demotes
you. Your title is per **workspace** (the card shows whichever workspace you pick).

## How to earn it

  
    Every map grows the **Maps** badge. Import a repo, clone a template, ask
    Canopy AI, or build by hand.
  
  
    Make a map public; likes on it grow the **Likes** badge.
  
  
    When someone clones one of your public maps, your **Clones** badge grows - a
    sign your architecture is worth copying.
  
  
    Your title only moves up when **all three badges** reach the next tier - so
    grow whichever badge is furthest behind.
  

## What the card shows

- Your **title**, with its custom emblem icon (sprout → crown).
- Your three **badges** (maps, likes, clones) as small metal icons.
- The **workspace** you've chosen to display, with a **Pro** badge and a
  **Verified** seal when it qualifies.
- A **scannable QR code** to your public profile, the date you joined, and a
  link to canopy.8starlabs.com.

## Pro card aesthetics 

A Pro workspace gives the card a premium finish - badges and titles stay free
either way:

- The card body picks up the signature **pastel mesh + dot grid** background.
- Richer card art at the higher titles.
- The **full set of card colors** unlocks as you climb.

Free cards are clean and shareable too - Pro just makes them shine. See the
[pricing page](/pricing) for the full comparison.

## Your public profile

Your Canopy Card is displayed on **your public profile**. Set a unique **handle**
in [Account settings](/dashboard/settings) to claim your page at
**canopy.8starlabs.com/a/&lt;handle&gt;** - it shows your card and totals (maps,
likes, clones) plus the workspaces you're on. The card's QR code links straight
to it. A handle is **3 to 30 characters**, using lowercase letters, numbers and
hyphens only, and must be unique.

Your marketplace account preview uses the same public profile identity: display
name, handle, selected avatar, Canopy Card title, and public map stats. Email
addresses are not shown on public cards or marketplace previews.

## Sharing your card

Use **Share on X**, **Copy PNG**, or **Download** beneath the card. The export is
a crisp 2x PNG - perfect for a profile, a README, or a "now mapping" post.


---

# Source: https://canopy.8starlabs.com/docs/glossary

export const metadata = {
  title: "Glossary | Canopy Docs",
  description:
    "The vocabulary used across Canopy - maps, nodes, workspaces, seats, templates, licenses and more, each defined in plain language. Searchable.",
  alternates: { canonical: "/docs/glossary" },
};

# Glossary

Learn the terms we use across Canopy and these docs. Each one maps to a real
concept in the product, so you can read the rest of the documentation - and the
app - without guessing what a word means.

Filter by term or definition, and click any linked term to jump to its full
page.

## The shape of it

If you remember nothing else, remember the hierarchy - everything else hangs off
it:

> An **account** owns one or more **workspaces**. A workspace holds **maps**, and
> each map is made of **nodes** and **edges**. The workspace is the team and
> billing boundary: roles, **seats**, and the plan all live there.

## Next

- [Accounts & workspaces](/docs/accounts) - how the layers fit together.
- [Map syntax](/docs/maps/architecture-as-code) - the node and edge schema in full.
- [Marketplace](/docs/marketplace) - templates, licenses, and selling.


---

# Source: https://canopy.8starlabs.com/docs/maps

export const metadata = {
  title: "Maps | Canopy Docs",
  description:
    "Learn how to build, import, edit, group, export, and share living Canopy maps for software systems and workflows.",
  alternates: { canonical: "/docs/maps" },
};

# Maps

Everything about building a Canopy map - from capturing your stack to exporting it
for AI and docs. Pick whichever starting point fits; they all land on the same
editable canvas.

## Where to go next

- [Import a repo](/docs/maps/import) - scan a GitHub repo and generate a first-draft map automatically.
- [Clone a template](/docs/maps/templates) - start from a community map or curated template.
- [Build by hand](/docs/maps/build) - drop nodes and draw edges on a blank canvas.
- [Import from canopy.json](/docs/maps/import-json) - paste a map file and Canopy renders the architecture.
- [Use your own AI](/docs/maps/use-your-own-ai) - let Claude, Codex or any MCP client build the map for you with `create_map`.
- [Export formats](/docs/maps/exports) - Markdown, PNG, AI context, Figma, Notion, and JSON.
- [Map syntax](/docs/maps/architecture-as-code) - the full JSON schema you edit, paste, or generate.


---

# Source: https://canopy.8starlabs.com/docs/maps/architecture-as-code

export const metadata = {
  title: "Map syntax (architecture as code) | Canopy Docs",
  description:
    "The full Canopy map syntax and JSON schema - node fields, edge fields, accepted values, and examples.",
  alternates: { canonical: "/docs/maps/architecture-as-code" },
};

# Map syntax

Every Canopy map is plain **JSON** - easy to read, diff, and edit by hand or with
an agent. This page is the complete **syntax / schema reference**: every field, its
type, and its **accepted values**. (Canopy calls this "architecture as code".)
Open any map and switch the editor to **Split** or **Code** to see it; edit the
canvas or edit the JSON and they stay in sync.

  Plain JSON means your maps are portable - version them in git, generate them
  from a script, or read and write them through the [API](/docs/api) and [MCP
  server](/docs/mcp).

  The **Code** editor has autocomplete (IntelliSense). As you type, it suggests
  field names, **icon slugs** (type a tool or step - `stripe`, `next`,
  `approval` - and pick the mark), **billing models**, **edge line types**, and
  the **node ids** an edge can point to. Press `⌘` `.` (`Ctrl` `.` on
  Windows/Linux) to re-open suggestions, `↑` `↓` to move, `Enter` or `Tab` to
  accept.

## Shape

A map is an object with two arrays, `nodes` and `edges` (an optional `name` is
also stored):

```json
{
  "name": "AI support workflow",
  "canvas": { "currency": "USD" },
  "nodes": [
    { "id": "inbox", "label": "Inbox", "brand": "intercom", "spend": 199 },
    {
      "id": "agent",
      "label": "Triage agent",
      "brand": "openai",
      "spend": 840,
      "currency": "EUR"
    },
    {
      "id": "review",
      "label": "Human review",
      "brand": "generic-approval",
      "spend": 0
    }
  ],
  "edges": [
    { "source": "inbox", "target": "agent" },
    { "source": "agent", "target": "review" }
  ]
}
```

Only `id` is required on a node; `source` and `target` are required on an edge.
Every other field is optional.

Canopy also stores a `canvas` object with your per-map view settings
(`background`, `edgeStyle`, `minimap`, `snapToGrid`) plus the map's `currency`.
The view settings are written automatically when you save and restored on load,
so you don't need to author them by hand, and older files without them fall back
to the defaults. `currency` is the one required `canvas` field: the map's base
currency as an ISO 4217 code (one of USD, EUR, GBP, JPY, CNY, CAD, AUD, CHF, INR,
SGD). It defaults to the workspace currency, and any node without its own
`currency` override uses it.

## Node fields

| Field           | Type     | Required | Accepted values                                                                                  | Notes                                                                                                                                        |
| --------------- | -------- | -------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | string   | **yes**  | Letters, digits, `-`, `_` only - `/^[A-Za-z0-9_-]+$/`. Unique within the map.                    | The node's stable key; edges reference it.                                                                                                   |
| `label`         | string   | –        | Any text (kept ≤ 80 chars).                                                                      | Display name on the canvas. Defaults to `id` when omitted.                                                                                   |
| `tech`          | string   | –        | Any text (kept ≤ 60 chars).                                                                      | Tool, runtime, role, or step under the name, e.g. `"node · trpc"` or `"approval"`. Also accepted as `sub`.                                   |
| `brand`         | string   | –        | An **icon slug** from the [Icon library](/docs/maps/icons), e.g. `"postgresql"`, `"openai"`.     | Brand icon. Also accepted as `icon`. An unknown slug → a neutral box node.                                                                   |
| `purpose`       | string   | –        | Any text (kept ≤ 160 chars).                                                                     | What the service does; shown in the inspector and in `CLAUDE.md`/`AGENTS.md` exports.                                                        |
| `spend`         | number   | –        | A plain number, e.g. `840`. No symbol.                                                           | **Monthly** cost as a plain number in the map's currency. Always the monthly figure, even when entered at another interval. Defaults to `0`. |
| `currency`      | string   | –        | One of `"USD"`, `"EUR"`, `"GBP"`, `"JPY"`, `"CNY"`, `"CAD"`, `"AUD"`, `"CHF"`, `"INR"`, `"SGD"`. | Optional ISO 4217 code overriding the map's `canvas.currency` for this node's spend.                                                         |
| `spendInterval` | string   | –        | One of `"day"`, `"week"`, `"month"`, `"year"`. Defaults to `"month"`.                            | The interval the cost was **entered** at. `spend` is still stored monthly; this just records how it was typed so the editor shows it back.   |
| `billing`       | string[] | –        | Any of `"seat"`, `"team"`, `"usage"`, `"flat"`. Unknown values are dropped.                      | Billing-model tags shown on the node and rolled up on the Intel page.                                                                        |
| `billingConfig` | object   | –        | Per-type inputs: `flat`/`usage` → `{ "amount": n }`; `seat`/`team` → `{ "price": n, "qty": n }`. | Optional raw cost breakdown per billing type (at `spendInterval`). The editor computes `spend` (monthly) from it.                            |
| `x`, `y`        | number   | –        | Any number (pixels).                                                                             | Canvas position. **Auto-assigned** when omitted - see Auto-layout.                                                                           |

  There is no per-node `color`. The map has a single accent colour set from the
  editor; individual nodes take their hue from their `brand` icon.

## Edge fields

| Field        | Type    | Required | Accepted values                                                    | Notes                                                                                                         |
| ------------ | ------- | -------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| `source`     | string  | **yes**  | An existing node `id`.                                             | The edge's start. (This is `source`, **not** `from`.)                                                         |
| `target`     | string  | **yes**  | An existing node `id`.                                             | The edge's end. (This is `target`, **not** `to`.)                                                             |
| `line`       | string  | –        | One of `"bezier"`, `"smoothstep"`, `"step"`, `"straight"`.         | Edge curve style. Defaults to `"bezier"`; an invalid value falls back to `"bezier"`.                          |
| `label`      | string  | –        | Any short string (max 120 chars).                                  | Text drawn at the edge midpoint. Omit for no label.                                                           |
| `labelColor` | string  | –        | A CSS color: hex, `rgb()`/`hsl()`, or a named color.               | Label text color. Defaults to a muted foreground.                                                             |
| `labelSize`  | number  | –        | Integer `6`-`48`.                                                  | Label font size in px. Defaults to `10`.                                                                      |
| `labelFont`  | string  | –        | One of `"sketch"`, `"sans"`, `"mono"`.                             | Label typeface. `"sketch"` (Excalifont) is the default; all are open-source (SIL OFL).                        |
| `arrow`      | string  | –        | One of `"none"`, `"end"`, `"both"`.                                | Arrowheads / direction. `"end"` = single arrow at the target, `"both"` = bidirectional. Defaults to `"none"`. |
| `color`      | string  | –        | A CSS color: hex (`"#38bdf8"`), `rgb()`/`hsl()`, or a named color. | Overrides the default gradient stroke. Invalid values are ignored.                                            |
| `dotColor`   | string  | –        | A CSS color: hex, `rgb()`/`hsl()`, or a named color.               | Color of the moving flow dot. Defaults to the edge `color` (or the gradient accent).                          |
| `animated`   | boolean | –        | `true` or `false`.                                                 | The moving-dot flow animation. Defaults to `true`; set `false` for a static line.                             |
| `dashed`     | boolean | –        | `true` or `false`.                                                 | Dashed stroke instead of solid. Defaults to `false`.                                                          |
| `id`         | string  | –        | Any string.                                                        | Optional stable edge id; auto-generated when omitted.                                                         |

Both ends must reference a node `id` that exists in the same `nodes` array -
edges with a missing or unknown `source`/`target` are silently dropped.

## Auto-layout

When `x`/`y` are omitted on any node, Canopy assigns positions automatically
using a **left-to-right layered layout**: each depth level from the edge graph
gets its own column (spaced 240 px apart), and nodes in the same column are
evenly distributed vertically (96 px row height). Once you drag a node, its
position is written back to the JSON and no longer auto-managed.

## Minimal map

Only `id` (nodes) and `source`/`target` (edges) are required:

```json
{
  "nodes": [{ "id": "web" }, { "id": "api" }, { "id": "db" }],
  "edges": [
    { "source": "web", "target": "api" },
    { "source": "api", "target": "db" }
  ]
}
```

## Full example

```json
{
  "name": "AI SaaS",
  "canvas": { "currency": "USD" },
  "nodes": [
    {
      "id": "web",
      "label": "Web App",
      "tech": "next.js",
      "brand": "nextdotjs",
      "purpose": "Frontend application",
      "spend": 1200,
      "billing": ["flat"],
      "x": 0,
      "y": 96
    },
    {
      "id": "api",
      "label": "API",
      "tech": "node · trpc",
      "brand": "nodedotjs",
      "purpose": "Backend REST API",
      "spend": 2100,
      "billing": ["flat"],
      "x": 240,
      "y": 144
    },
    {
      "id": "db",
      "label": "Postgres",
      "brand": "postgresql",
      "purpose": "Primary database",
      "spend": 840,
      "billing": ["usage", "team"],
      "x": 480,
      "y": 48
    },
    {
      "id": "llm",
      "label": "LLM",
      "brand": "openai",
      "purpose": "AI completions",
      "spend": 2800,
      "billing": ["usage"],
      "x": 240,
      "y": 240
    }
  ],
  "edges": [
    { "source": "web", "target": "api" },
    { "source": "api", "target": "db", "line": "step" },
    { "source": "api", "target": "llm" }
  ]
}
```

## Editing

- **Add node** opens the [tool and icon library](/docs/maps/icons) - search every
  brand, workflow platform, AI tool, or pick a generic service or workflow box.
- The **inspector** edits a node's label, tool/step sub-label, icon (`brand`),
  monthly cost, purpose, and billing tags.
- **Drag between handles** to connect; drag a node to move it - its `x` / `y` are
  written back to the JSON.
- A **map colour** swatch sets the map's accent, reflected on the dashboard overview.
- If the JSON is invalid, the offending line is flagged and the canvas keeps your
  last valid render until you fix it.

## Importing

Already have a map written to this syntax? You can paste it straight in via **New
map → Paste canopy.json** - see [Import from canopy.json](/docs/maps/import-json)
for the full flow and what gets validated.

## Next

- [Icon library](/docs/maps/icons) - every `brand` slug, searchable.
- [Nodes](/docs/maps/nodes) - the service inspector and metadata.
- [Edges](/docs/maps/edges) - connections in depth.
- [Exports](/docs/maps/exports) - JSON, Markdown, CLAUDE.md and more.


---

# Source: https://canopy.8starlabs.com/docs/maps/blast-radius

import { BlastRadiusDemo } from "@/components/docs/blast-radius-demo";

export const metadata = {
  title: "Blast Radius | Canopy Docs",
  description:
    "See what services are connected to a map node, how far away they are, and where to stop the impact review.",
  alternates: { canonical: "/docs/maps/blast-radius" },
};

# Blast Radius

Blast Radius answers a practical question: **if I change this service, what
else should I check?**

It starts at the service you select, then follows the lines connected to it.
The result is based on your map, not live traffic or production monitoring.

  Select a service. The orange highlight shows the connected services. Stronger
  color means closer to the selected service. Softer color means farther away.
  Gray lines are outside the current review. There is one color for the whole
  impact view.

## The rule: Blast Radius follows connected map topology

Every line has a start and an end, stored as `source → target`.

For example:

```text
Web app → API → Payments
```

If **API** is selected:

- Web app is **incoming**, also called **upstream**.
- Payments is **outgoing**, also called **downstream**.
- Blast Radius shows both sides in the same color. Incoming and outgoing are
  labels that help you read the arrows, not separate modes or colors.

| Term                      | Meaning                                 |
| ------------------------- | --------------------------------------- |
| **Focus**                 | The service you selected.               |
| **Direct**                | A service one line away from the focus. |
| **Transitive**            | A service two or more lines away.       |
| **Incoming / upstream**   | The line points into the focus.         |
| **Outgoing / downstream** | The line points away from the focus.    |

The meaning of an arrow depends on your map. In a request map, upstream is often
a caller and downstream is often a dependency. In an event map, upstream may be
the producer and downstream the consumer.

The walk stops at the depth you choose, when there are no more connected lines,
or at a boundary that you set.

## A concrete map

In this example, the arrows describe request and data flow:

```text
Browser → Web app ─┐
                    ├→ API gateway → Auth
Mobile app ─────────┘              └→ Payments → Postgres
                                             └→ Queue
```

Select **API gateway** in the example below:

- **Direct services:** Web app, Mobile app, Payments, and Auth.
- **Transitive services:** Browser, Postgres, and Queue.
- **Affected services:** every service reached within the selected depth.

Use the controls in the example to change the depth or add a boundary. Click a
different node to see its connected services.

## How to use Blast Radius

1. Open a map. Press `B`, choose **Blast radius** in the left toolbar, or press
   `⌘K` and search for **Toggle Blast Radius**. On Windows and Linux, use
   `Ctrl K`.
2. Click the service you are about to change. This becomes the **focus**.
3. Start with **All** to see the full connected map. Use **1 hop** or **2 hops**
   for a smaller review.
4. Follow the orange lines. Strong lines are close to the focus. Softer lines
   are farther away.
5. Open **Review services** to see a compact list of affected services and their
   hop count. Use **Block here** or **Allow past** on a row to control that
   branch.
6. Use **Block here** when the highlight should stop at a service.
7. Use **Fit impact** if part of the result is off screen.
8. Use **Pin focus** if you want to edit another node or line without changing
   the focus.

Press `B`, click the left toolbar button, or choose **Toggle Blast Radius** from
`⌘K` again to exit.

Blast Radius is a temporary view. It does not save the focus, depth, color,
boundaries, or pin state into the map.

## What the controls mean

### Color and distance

Blast Radius uses one color for the whole review:

- **Orange** is the default.
- Direct connections use the strongest color.
- Farther connections become softer, but stay visible.
- Lines outside the result stay muted and are not part of the impact count.
- Flow dots pause in Blast Radius so the impact path stays still while you review it.

Use the color swatch or picker in the panel to choose another accent. The same
color is used for the focus, nodes, and highlighted lines.

The four node handles are only connection positions. They do not create four
Blast Radius directions.

### Depth

Depth is the number of lines Canopy may follow from the focus. It is not zoom:

- **1 hop:** show only services directly connected to the focus.
- **2 hops:** include one more layer.
- **All:** continue until there are no more reachable services or a boundary.

### Boundaries

Use **Block here** when the highlight should stop at a service. This is useful
when the next system is outside the part of the map you are reviewing, such as:

- another team's service;
- a queue or webhook reviewed separately;
- a shared platform service;
- a database or vendor boundary.

The boundary service stays highlighted. The lines after it stay visible in gray,
but they are not counted or highlighted. Choose **Allow past** to remove the
boundary. Use **Clear** to remove all boundaries.

### Pin focus

Pinning keeps the current focus while you work on the map:

- Clicking another **node** opens it in the editor without changing the focus.
- Clicking a **line** still lets you change its style, label, arrows, or delete it.
- **Review services** and **Fit impact** continue to work.

Turn off **Pin focus** when you want to choose a new focus from the canvas.

## Important edge-direction details

### An invisible arrow still has a direction

Even when an edge has no visible arrowhead, it still has a stored start and end.
Blast Radius can follow it from either endpoint, but other readers cannot tell
the intended direction. Add an arrowhead when direction matters.

### Two arrowheads do not create two edges

Choosing **Both** draws arrowheads at both ends, but it still creates one edge.
For a real two-way relationship, create two edges:

```text
service-a → service-b
service-b → service-a
```

This makes the relationship explicit in the map and in exports.

### Labels explain the relationship

Use a label such as `REST`, `webhook`, `reads`, `publishes`, or `owns` to explain
why a line exists. Labels do not change the result, but they make it easier to
understand.

## Reading the result in a real change review

Suppose you are changing **Payments** in the example:

- Direct impact includes API gateway, Postgres, and Queue.
- Transitive impact includes Web app, Mobile app, Browser, and Auth.
- The full result includes every service reached within the selected depth.

That gives you a practical review order:

1. Check direct neighbors first. They are closest to the change.
2. Check transitive services next. They may need tests or rollout coordination.
3. Open the affected-service list and confirm that the result matches the change
   you are planning.

## What Blast Radius does not know

Blast Radius only knows what is represented in the map. It does not know:

- live request volume, latency, errors, or uptime;
- whether an edge is synchronous or asynchronous unless you label it;
- ownership, permissions, or team boundaries;
- whether two arrowheads should be treated as two graph paths;
- hidden dependencies that are not represented by edges.

For a useful result, add important handoffs as edges, keep their directions
current, and label ambiguous connections.

## Troubleshooting

### Nothing is highlighted

The focus may have no connected lines, or the lines may be outside the selected
depth. Try **All**, then check the lines connected to the focus.

### The result is too large

Switch to **1 hop** or **2 hops** for a smaller review. Return to **All** when
you want the full connected result.

### A service appears on the wrong side

Blast Radius follows the stored start and end of each line. If a service appears
on the wrong side, edit the line's direction or add the correct arrowhead.
Changing the line shape does not change the relationship.

### I need to edit while reviewing

Turn on **Pin focus**, then select the node or line. The bottom editing controls
and right inspector remain available while the focus stays fixed.


---

# Source: https://canopy.8starlabs.com/docs/maps/build

export const metadata = {
  title: "Build by hand | Canopy Docs",
  description:
    "Build a Canopy map manually for greenfield systems, services outside a repo, AI workflows, internal automations, and business processes.",
  alternates: { canonical: "/docs/maps/build" },
};

# Build by hand

Start with a blank canvas and drop nodes one at a time. Good for greenfield systems,
services not in your repo, AI workflows, internal automations, or anything you
want to sketch before you build it.

## Naming your map

Set the map's **name** and **description** in the **Canvas** tab of the right panel.
Both have a limit and show a live character counter as you type:

| Field       | Limit                | Notes                                                   |
| ----------- | -------------------- | ------------------------------------------------------- |
| Name        | up to 40 characters  | Shown on the canvas, in the maps list, and in exports   |
| Description | up to 280 characters | Optional summary shown in the maps list and share cards |

The limits are enforced in the editor and again on save, so anything longer is
trimmed to fit.

## Adding nodes

Click **Add node** (or the `+` button in the toolbar) to open the integration picker.
Search tools, services, workflows, and AI platforms - pick one and Canopy fills
in the name and icon automatically.

If the service isn't in the library, use a **Custom** node from the top of the
picker. These carry a generic icon - database, server, compute, storage, cloud,
queue, cache, notebook, workflow, approval, webhook, AI agent, background job,
and more - so you can represent anything that doesn't have a brand mark. Drop
one, rename it, and set its cost like any other node. To change a node's icon
later, type a keyword in the **Tool / step** field in the inspector and pick a
suggested mark (brand or generic).

Custom nodes still count toward cost and node totals, but they're **excluded
from the tool-usage stats** because they don't represent a specific vendor.

Each node has the same fields in the **Inspector** panel on the right:

| Field         | What it does                                                                                                        |
| ------------- | ------------------------------------------------------------------------------------------------------------------- |
| Name          | How this service is labelled on the canvas                                                                          |
| Tool / step   | Runtime, vendor, workflow step, or role - e.g. `Node.js · Express`, `approval`, `research agent`                    |
| Icon          | The brand or generic mark. Type a keyword and Canopy suggests matching marks from its library - click one to set it |
| Purpose       | One-line description shown in exports and the inspector                                                             |
| Monthly cost  | Attached cost rolls up to the group and workspace totals                                                            |
| Billing model | Usage-based, flat, per-seat, or custom                                                                              |

## Drawing edges

Hover any node to reveal the **edge handle** on its right side. Drag it onto another
node to draw a directed arrow - `web → api → db`. Edges describe request flow and
don't count toward your node limit.

Double-click any edge to add a label (`gRPC`, `webhook`, `REST`, `async`) for clarity.
Hit **Auto-layout** in the toolbar to re-flow the whole graph at any time.

## Find &amp; fix issues

Press `⌘` `F` (`Ctrl` `F` on Windows) to **find a node** by name, tool, or step -
start typing and pick a match to jump straight to it on the canvas.

The toolbar also runs a live **map lint**. The **Issues** panel (the checklist
icon in the left rail, or the ⚠ badge by the map name) flags the things a JSON
paste or AI-generated map tends to leave broken:

- **Dangling edges** - an edge whose source or target node doesn't exist
- **Duplicate node ids**
- **Unknown or missing icons** - an icon slug Canopy doesn't recognise, or none set
- **Missing cost** - a node with no monthly cost (a suggestion, not an error)

Click any node-scoped issue to select and centre that node.

## Keyboard shortcuts

Press `?` anywhere in the editor to open this cheat-sheet (or click the keyboard
icon in the toolbar). On Windows and Linux, `⌘` is `Ctrl`.

| Shortcut        | Action                   |
| --------------- | ------------------------ |
| `⌘` `S`         | Save map                 |
| `⌘` `Z`         | Undo                     |
| `⌘` `Shift` `Z` | Redo                     |
| `⌘` `F`         | Find a node              |
| `⌘` `K`         | Open the command palette |
| `V`             | Select tool              |
| `H`             | Pan tool                 |
| `A`             | Open Add node panel      |
| `I`             | Open Node info panel     |
| `S`             | Open Optimisations panel |
| `B`             | Toggle Blast Radius      |
| `C`             | Open Code editor         |
| `,`             | Open Canvas settings     |
| `E`             | Open Download panel      |
| `Shift` + click | Add to selection         |
| `Delete`        | Delete selection         |
| `⌘` `.`         | Autocomplete (code)      |
| `?`             | Toggle this help         |
| `Esc`           | Close panels / search    |

Undo and redo also cover **canvas drags** - move a node and `⌘` `Z` puts it back.

## Combining approaches

You can mix methods on the same map. A common pattern: import a repo to detect the
core services, then hand-add the third-party SaaS tools (Stripe, Segment, Slack) that
don't show up in a lockfile.

For workflow maps, use the same canvas vocabulary: a webhook can point to an AI
agent, which points to a review step, then to a CRM update or background job.
Edges describe the handoff, data flow, or dependency between each step.


---

# Source: https://canopy.8starlabs.com/docs/maps/edges

export const metadata = {
  title: "Edges | Canopy Docs",
  description:
    "Connect nodes with dependency, API, workflow, event, ownership, and data-sync edges to show how a system works.",
  alternates: { canonical: "/docs/maps/edges" },
};

# Edges

An **edge** is a directional connection between two nodes. It describes how
requests, data, ownership, or events flow through your system.

## Line styles

An edge is directional and can be drawn in one of four line styles. Pick what
reads best for the layout:

Select an edge and switch its line in the **Connection** panel, or set a
map-wide default from **Canvas settings**. The choice is saved per edge in
`canopy.json` as `"line": "step"`, `"smoothstep"`, `"bezier"`, or `"straight"`.

## Arrows and direction

By default an edge is undirected (just a line). Select it and open the
**Connection** panel to add arrowheads:

- **None** - no arrowhead (an association, not a flow).
- **Single** - one arrow at the target end, a directed flow (`A → B`).
- **Both** - arrows at both ends, a bidirectional link (`A ↔ B`).

Saved per edge as `"arrow": "none"`, `"end"`, or `"both"`. Omitting `arrow` (or
setting `"none"`) keeps the classic undirected look.

Blast Radius still follows the stored `source` to `target` relationship even
when an edge has no visible arrowhead. See [Blast Radius](/docs/maps/blast-radius)
for the full upstream and downstream model, hop controls, and a worked example.

## Labels

Give an edge a short label to name what flows across it (e.g. `webhook`,
`gRPC`, `read replica`). Type it in the **Connection** panel; it is drawn at
the midpoint of the line and saved as `"label": "webhook"`. Clear the field to
remove it. Labels hold up to **120 characters**.

### Explaining business and AI workflows

Edge labels shine on **workflow maps** - business processes and AI agent
pipelines - where the interesting part is _what happens between the steps_.
Label each connection with the handoff, trigger, or condition so the map reads
as a runbook:

- **Business workflow** - `Intake form → Review agent` labelled `on submit`,
  `Review agent → Human approval` labelled `if confidence < 0.8`, `→ CRM`
  labelled `on approve`.
- **AI workflow** - `Router → Vision model` labelled `image input`,
  `Retriever → LLM` labelled `top-k context`, `LLM → Guardrail` labelled
  `before send`.

For a longer description of a step itself, use the node's **purpose** field
(up to 160 chars); keep the edge label to the short handoff or condition so it
stays readable on the canvas. Both flow straight into your `CLAUDE.md` /
`AGENTS.md` exports, so an agent reads the sequence, not just the boxes.

### Styling a label

Once a label is set, the **Connection** panel exposes its styling:

- **Font** - `sketch` (Excalifont, the hand-drawn default), `sans`, or `mono`.
  All three are open-source (SIL OFL) and bundled with the app.
- **Size** - 6-48 px (`"labelSize": 14`).
- **Text color** - a swatch or any hex (`"labelColor": "#ef4444"`); **Default**
  uses a muted foreground.

Saved per edge as `"labelFont"`, `"labelSize"`, and `"labelColor"`.

## Color

An edge uses the animated gradient by default. Pick a swatch in the
**Connection** panel to override the stroke (handy for grouping flows by
concern - data vs events vs auth). Saved as `"color": "#10b981"` (any hex, rgb,
or named CSS color). Choose **Default** to drop the override and return to the
gradient.

## Moving dots

Each edge shows a dot travelling along it to convey flow. Toggle **Moving dots**
in the **Connection** panel to turn the motion on or off per edge (saved as
`"animated": false` when off). While dots are on, set a **Dot color** - it
defaults to **Match line** (the dot follows the edge color) but you can give it
its own swatch, saved as `"dotColor": "#3b82f6"`.

## Working with edges

- **Draw** - drag between node handles to connect them.
- **Style** - swap an edge&apos;s line type, add arrows, a label, or a color.
- **Direction** - set arrowheads to none / single / both.
- **Delete** - select an edge and remove it without affecting the nodes.

Edges do not count toward your node limit, and they make dependencies explicit
so you can trace what depends on what across your system.

  In exported `architecture.md`, `CLAUDE.md`, and `AGENTS.md`, edges render as
  arrows so agents can parse the topology, and any **label** comes along in
  parentheses (`- inbox → agent (on submit)`). Bidirectional edges use `↔`.


---

# Source: https://canopy.8starlabs.com/docs/maps/embed

export const metadata = {
  title: "Embed a map | Canopy Docs",
  description:
    "Embed a live public Canopy map in a README, Notion page, Confluence document, or website with an always-current view.",
  alternates: { canonical: "/docs/maps/embed" },
};

# Embed a map

Put a live map in a README, a Notion page, a Confluence doc, or your own site. It
stays current: edit the map in Canopy and every embed updates.

## Two snippets, and they are not interchangeable

**Notion, Confluence, a blog, a wiki** get an iframe. Readers can pan and zoom;
nobody can edit.

```html
<iframe
  src="https://embed.canopy.8starlabs.com/embed/map/<map-id>"
  width="100%"
  height="460"
  loading="lazy"
  allow="fullscreen"
  style="border:1px solid #e5e7eb;border-radius:12px"
  title="Architecture map"
></iframe>
```

**A GitHub README** needs the image form instead:

```md
[![Architecture map](https://embed.canopy.8starlabs.com/embed/map/<map-id>/map.svg)](https://canopy.8starlabs.com)
```

  GitHub's markdown sanitizer strips `<iframe>` entirely, so the interactive
  embed cannot appear there at all. The image is not a lesser fallback, it is the
  only thing that works. It updates whenever you save the map, and because GitHub
  proxies external images through its own CDN, even a heavily-read README barely
  touches Canopy.

Both snippets are generated for you: open a map and click the **embed** icon in
the header.

## Dark theme

Choose **Dark** in the embed dialog. Canopy puts `theme=dark` in the generated
URL, so the theme is explicit and stays consistent in a cross-origin iframe or
an image proxy such as GitHub Camo.

```html
<iframe
  src="https://embed.canopy.8starlabs.com/embed/map/<map-id>?theme=dark"
  width="100%"
  height="460"
  loading="lazy"
  allow="fullscreen"
  style="border:1px solid #2b343a;border-radius:12px"
  title="Architecture map"
></iframe>
```

The same option works for a README image:

```md
[![Architecture map](https://embed.canopy.8starlabs.com/embed/map/<map-id>/map.svg?theme=dark)](https://canopy.8starlabs.com)
```

The embed does not guess the parent page theme. The explicit URL keeps the
interactive and image versions stable, including when a cached image is shown.

## The map must be public

An embed follows the map's **sharing state**. A private map's embed returns 404,
and setting a public map back to private stops every existing embed immediately.

There is no separate embed link or token. Deciding to embed a map is the same
decision as making it public: anyone with the URL can see it.

  Canopy blocks framing for dashboard pages, but the embed route intentionally
  allows framing from any site with `frame-ancestors *`. It is served from a
  separate cookieless host, can only read public map data, and is read-only. The
  generated iframe requests fullscreen only.

## Options

| Parameter | Values           | Default |
| --------- | ---------------- | ------- |
| `theme`   | `light`, `dark`  | `light` |
| `spend`   | `1` to show cost | hidden  |

Append them to either URL, e.g. `/embed/map/<map-id>?theme=dark&spend=1`.
Anything else is ignored.

  Monthly spend is **off by default** in embeds, even though a public map page
  shows it. An embed travels further than a page someone chose to visit - into
  READMEs, wikis, and search results - so turning cost on is a deliberate act.

## Attribution

Every embed carries a **Made with Canopy** badge, on every plan:

This differs from [exports](/docs/maps/exports), where Pro can remove the
watermark: an export is your own data leaving Canopy, whereas an embed is Canopy
rendering inside someone else's page.

## Sizing

The iframe fills whatever box you give it. `460px` tall suits most maps; give a
large map more height, or let readers zoom. The image form scales to its
container like any other image.

## Why embeds are served from a different domain

Embeds come from `embed.canopy.8starlabs.com`, never from the app domain. That is
deliberate: an embed renders inside a page Canopy does not control, so it is
served from an origin that holds **no Canopy session**. An embed cannot read your
cookies, act as you, or reach the API, because on that origin there is nothing to
reach with.

The embed also loads no external scripts or fonts and makes no network calls of
its own.


---

# Source: https://canopy.8starlabs.com/docs/maps/exports

export const metadata = {
  title: "Export formats | Canopy Docs",
  description:
    "Export a Canopy map as PNG, Markdown, CLAUDE.md, AGENTS.md, Figma, or Notion content for teams and AI tools.",
  alternates: { canonical: "/docs/maps/exports" },
};

# Export formats

A Canopy map is portable. Export it to the formats your team and your agents
already use.

## Formats

- **`architecture.md`**: the canonical Markdown description of your system.
- **`CLAUDE.md` / `AGENTS.md`**: AI context files that live in your repo.
- **`README.png`**: a rendered diagram for your README.
- **`diagram.fig`**: an editable Figma frame. 
- **JSON**: the structured map data: nodes, edges, positions, and metadata.
- **`notion.page`**: a synced Notion doc. 

Every workspace can export PNG, Markdown, `CLAUDE.md`, and `AGENTS.md`. Each
export carries a small "Made with Canopy" badge - always, on every plan.
[Pro](/pricing) adds Figma and Notion exports.

## canopy.json ⇄ canvas

Your `canopy.json` and the canvas stay in sync both ways. Describe the system as
JSON and Canopy renders a live, editable map; drag the map and the `canopy.json`
updates.

## Add it to your README

  An exported PNG is a snapshot that goes stale. An [embedded
  map](/docs/maps/embed) updates whenever you save, and GitHub proxies the image
  through its own CDN so it costs you almost nothing to serve.

There's no iframe or live embed because GitHub strips `<iframe>` from READMEs anyway.
Instead you share the **`README.png`** export as a normal Markdown image: export
it, host the file, and paste one line. It's a snapshot, so re-export and replace
the file when your architecture changes.

  
    In the map editor, open **Export → README.png**. Every export carries the
    "Made with Canopy" badge.
  
  
    Pick whichever is easiest. The file just needs a URL or a repo path:

    - **Commit it to your repo.** Drop the file at e.g. `docs/architecture.png`
      and reference it with a relative path. Versioned alongside your code.
    - **Drag-and-drop into GitHub.** Paste the image into any issue or PR comment
      box (don't submit); GitHub uploads it and gives you a
      `…githubusercontent.com` URL you can copy. No commit needed.
    - **Use any image host.** Cloudflare R2, S3, or similar, then use the
      absolute URL.

  
  
    Reference the file with a standard image tag:

    ```md
    ![Architecture](docs/architecture.png)
    ```

    Using a hosted URL instead of a repo path? Swap it in:

    ```md
    ![Architecture](https://your-host.com/architecture.png)
    ```

  
  
    Wrap the image in a link so readers can open the full, interactive map:

    ```md
    [![Architecture](docs/architecture.png)](https://canopy.8starlabs.com/m/your-map)
    ```

  

  The PNG is a point-in-time snapshot, not a live view. When your architecture
  changes, re-export and replace the file. Keep the same filename and the
  Markdown doesn't change. Need something always-fresh? Point agents at the [MCP
  server](/docs/mcp) instead of a static image.

## Next

- [MCP server](/docs/mcp): let agents query your live map directly
  instead of a static file.
- [Nodes](/docs/maps/nodes): the metadata fields that get exported.
- [Intel](/docs/operate/intel): cost data included in every export.


---

# Source: https://canopy.8starlabs.com/docs/maps/groups

export const metadata = {
  title: "Groups & cost rollups | Canopy Docs",
  description:
    "Group Canopy nodes by domain and roll up monthly spend across frontend, backend, AI, platform, and other system areas.",
  alternates: { canonical: "/docs/maps/groups" },
};

# Groups & cost rollups

You rarely reason about cost one service at a time - you think in **domains**:
frontend, backend, AI, platform. Canopy adds up the monthly
[cost](/docs/operate/intel) you attach to each node so you can see the shape of
the bill, not just the shape of the system.

## Two ways to read a map

The same architecture reads differently depending on what you're asking:

- **Flow view** - the request paths between services. This is the canvas you
  build and edit.
- **Group view** - the same services clustered into cost domains: a way to
  picture where the money goes.

## Where your cost is rolled up

Give each node a monthly cost and Canopy aggregates it for you on the
**dashboard** and the [Intel page](/docs/operate/intel):

- **By map** - total monthly cost for every map in the workspace.
- **By billing model** - usage, per-seat, per-team and flat, so you can tell how
  much of your bill scales with usage versus what's fixed.

  Intel sorts high-to-low, so the priciest map - and the billing model driving
  it - is always at the top.


---

# Source: https://canopy.8starlabs.com/docs/maps/icons

export const metadata = {
  title: "Tool and icon library | Canopy Docs",
  description:
    "Every brand and generic workflow icon available for a node, with its slug and name - searchable. Use a slug in a node's brand field.",
  alternates: { canonical: "/docs/maps/icons" },
};

# Tool and icon library

Each node can show a **brand or generic icon**. Set the node's [`brand` field](/docs/maps/architecture-as-code#node-fields)
(also accepted as `icon`) to one of the slugs below - e.g. `"brand": "postgresql"`.
An unknown slug falls back to a neutral box node, so a map never breaks.

Search by name or slug and click any tile to **copy its slug**.

Alongside the brand marks you'll find **generic icons** for custom nodes and
workflows - `generic-workflow`, `generic-agent`, `generic-approval`,
`generic-webhook`, `generic-job`, `generic-database`, `generic-server`, and
more. Use them for internal processes, AI agent steps, approvals, background
jobs, queues, or anything that doesn't have a brand mark. Custom nodes still
appear on maps and exports, but they are excluded from workspace tool-usage stats
because they are not a specific vendor.

  The library is curated, so not every brand is included yet. Pick the closest
  match or leave `brand` off for a generic service box - and request the missing
  one so we can add it.

  

## Next

- [Map syntax](/docs/maps/architecture-as-code) - the full node & edge schema.
- [Nodes](/docs/maps/nodes) - the service inspector and metadata.


---

# Source: https://canopy.8starlabs.com/docs/maps/import

export const metadata = {
  title: "Import a repo | Canopy Docs",
  description:
    "Import a GitHub repository into Canopy to generate a first-draft architecture map from dependency manifests and configuration files.",
  alternates: { canonical: "/docs/maps/import" },
};

# Import a repo

Point Canopy at a GitHub repo and it builds a first-draft map automatically - no
manual diagramming. It reads only your **manifest and config files** (never your
source) and lays out a node for each tool it finds, linked to a central repo node.

  Direct repo scanning currently supports **GitHub** only. **GitLab, Bitbucket
  and other remotes are coming.** In the meantime, you can map any repo by
  generating a [canopy.json](/docs/maps/import-json) from its docs or file tree
  (there's a copyable AI prompt for that) and pasting it in.

## How to import

GitHub access always goes through the **Canopy GitHub App** - required for **every**
repo, public or private. There's no anonymous scanning: Canopy reads through a
short-lived installation token you grant.

1. **Connect GitHub.** Go to **Workspace settings → Integrations → Connect
   GitHub** (or choose **Import a repo** when creating a map). You'll install the
   Canopy GitHub App and pick exactly which repos it can access.
2. **Pick a repo.** Choose any repo the App can see.
3. **Scan.** Canopy reads the repo's manifests and builds a starter map - a
   central repo node with a node for each detected tool, edges drawn between them.
4. **Edit.** Rename nodes, recategorize them, set cost and billing, and add or
   remove nodes and edges to match reality.

  Both public and private repos go through the installed GitHub App - there's no
  anonymous scan. Canopy only ever reads metadata and manifests, never your
  source, and you can revoke access anytime from GitHub → Settings →
  Applications → **Installed GitHub Apps**.

## Connecting one org to several workspaces

A GitHub App installation belongs to the **org**, not to a Canopy workspace, and
GitHub won't install the same app on one org twice. So the second connection
never goes through the install flow.

**Workspaces you own.** Open **Settings → Integrations** on the second
workspace. Under _"Already installed"_ you'll see the org you connected before,
with a **Connect** button. One click, no GitHub round trip. (Clicking _Connect
GitHub_ instead just lands you on GitHub's "Configure" page, which cannot create
a second installation - Canopy now redirects you back here rather than let that
happen.)

**A different Canopy account.** Not currently possible, and the reason is
permissions rather than plumbing. Canopy only offers you installations **you**
connected: it knows you own the org because GitHub proved it during install. A
second account has proven nothing, so honouring "attach installation 12345"
would let anyone who learns an installation id attach someone else's private
repos to their own workspace.

Two ways around it today:

- **Own both workspaces with one account**, then use the reuse button above.
- **Invite the other person into your workspace** instead of running a parallel
  one - they get the maps built from those repos without a second installation.

  Verifying GitHub org membership (sign in with GitHub, we check you're in the
  org, then allow the attach) is the proper fix and is on the list. Until then,
  cross-account sharing of one installation is deliberately blocked.

## What Canopy reads

Canopy detects tools from the manifest and config files below (metadata only).
Search the full, live list of what's auto-detected:

When an import detects more tools than your plan's node cap allows, Canopy maps
the first (limit − 1) and tells you how many were left out.

## What it produces

A **starter map**, not a finished one:

- A central **repo node** - your repository.
- One **node per detected tool**, each with its real brand icon.
- **Edges** from the repo node out to every detected tool.

Detection is a starting point. After import you can rename and recategorize
nodes, set each one's **cost** and **billing model**, and add services or edges
the scan couldn't infer.

  If the repo has more tools than the Free plan's 25-node limit, Canopy maps the
  first 14 (leaving one slot for the repo node) and shows a toast with how many
  were left out. Upgrade to Pro for up to 150 nodes per map.


---

# Source: https://canopy.8starlabs.com/docs/maps/import-json

export const metadata = {
  title: "Import from canopy.json | Canopy Docs",
  description:
    "Paste a canopy.json map file - exported from Canopy, generated by a script, or written by an agent - and Canopy renders the system or workflow.",
  alternates: { canonical: "/docs/maps/import-json" },
};

# Import from canopy.json

Already have a map as JSON? Paste it and Canopy renders the system or workflow
on the canvas, ready to edit. Useful when a map was **exported from Canopy**,
**generated by a script**, or **written by an agent** - anything that follows the
[map syntax](/docs/maps/architecture-as-code).

## Paste a map

  
    On the dashboard or the **Maps** page, open **New map** and pick **Paste
    canopy.json**.
  
  
    Paste the JSON. It must be an object with a `nodes` array. Canopy auto-fills
    the map name from the file's `name`; pick a category.
  
  
    Canopy validates and sanitises the JSON, then opens the rendered map on the
    canvas.
  

The smallest valid file is an object with a `nodes` array (edges are optional):

```json
{
  "name": "Lead review workflow",
  "nodes": [
    { "id": "form", "label": "Intake form", "brand": "typeform" },
    { "id": "agent", "label": "Review agent", "brand": "openai" },
    { "id": "approval", "label": "Human approval", "brand": "generic-approval" }
  ],
  "edges": [
    { "source": "form", "target": "agent" },
    { "source": "agent", "target": "approval" }
  ]
}
```

See [Map syntax](/docs/maps/architecture-as-code) for every field and its accepted
values, and the [Icon library](/docs/maps/icons) for `brand` slugs.

## Don't have a file? Generate one with AI

Don't hand-write the JSON. Copy the prompt below into ChatGPT, Claude, Gemini or
any LLM, paste in your own architecture or workflow (a description, README, SOP,
agent flow, or infra list), and paste the JSON it returns into **Paste
canopy.json**. The prompt tells the model to read these docs first, so it uses
the right schema and real icon slugs.

This is also how you map a stack that lives on **GitLab, Bitbucket, or any
non-GitHub remote**, or a workflow that lives in a doc, runbook, or SOP: point
the model at the source material and import the result.

## What's validated

Canopy is forgiving - a slightly-off file still imports cleanly:

| Situation                                  | What happens                                                      |
| ------------------------------------------ | ----------------------------------------------------------------- |
| Not valid JSON, or no `nodes` array        | Rejected before the map is created - nothing is saved.            |
| Unknown `brand` slug                       | Falls back to a generic box node.                                 |
| Edge `source`/`target` with no matching id | That edge is dropped; the rest import.                            |
| HTML / scripts / hidden unicode in fields  | Stripped on import (the same sanitisation applied on every save). |
| Very large file                            | Capped (512 KB) so a runaway file can't be imported.              |

  Importing counts as **creating a map**, so the Free
  [2-maps-per-workspace](/docs/accounts) limit applies - if you're already at
  the limit, free up a slot or upgrade to Pro first. Pro allows up to 50 maps.

## Combining approaches

You can mix methods on the same map afterwards - paste a base architecture or
workflow, then [hand-add](/docs/maps/build) the services, agents, approvals, and
jobs that weren't in the file, or [export](/docs/maps/exports) it back to JSON
to round-trip through your own tools.


---

# Source: https://canopy.8starlabs.com/docs/maps/nodes

export const metadata = {
  title: "Nodes | Canopy Docs",
  description:
    "Add services, databases, SaaS tools, AI components, workflow steps, and human handoffs as structured nodes on a Canopy map.",
  alternates: { canonical: "/docs/maps/nodes" },
};

# Nodes

A **node** is any service, datastore, SaaS tool, AI component, internal process,
workflow step, or human handoff on your canvas. Nodes are the building blocks of
every [map](/docs/maps), and each one carries operational metadata.

## Anatomy of a node

Every node has an identity (an icon and label) and the fields you edit in the
inspector when it is selected:

| Field        | Example          | Max | Notes                                            |
| ------------ | ---------------- | --- | ------------------------------------------------ |
| Name         | `Review agent`   | 80  | the service or step name (`label`)               |
| Tool / step  | `OpenAI · queue` | 60  | the vendor, runtime, role, or sub-label (`tech`) |
| Purpose      | `Summarise lead` | 160 | what it does                                     |
| Monthly cost | `$2.1k`          | 40  | with a [billing type](/docs/operate/intel)       |
| Billing type | `usage`          | –   | `usage` · `seat` · `team` · `flat`               |

These are single-line display fields, so they are capped (and any longer text is
trimmed on save). The same limits apply when you [import a `canopy.json`](/docs/maps/import-json)
or edit [map syntax](/docs/maps/architecture-as-code) directly.

## Icons from the library

A node&apos;s icon comes from Canopy&apos;s tool and icon library - pick from
brand marks, AI tools, workflow platforms, SaaS apps, infra services, and generic
workflow icons. The category drives which icon you get; cost and billing roll
up into [groups](/docs/maps/groups).

## Limits

The Free plan allows **25 nodes per map** and **2 maps per workspace**. Pro
allows **150 nodes per map** and up to **50 maps**. Groups and edges do not count
toward the node limit. When a GitHub import detects more libraries than the node
limit, Canopy maps the first ones up to the cap and tells you how many were left
out. See [pricing](/pricing) for plan details.

  Click any node on the canvas to open its inspector and edit its name, tool or
  step, purpose, monthly cost, and billing type.

## Next

- [Edges](/docs/maps/edges) - how nodes connect.
- [Groups](/docs/maps/groups) - rolling nodes up into cost domains.
- [Intel](/docs/operate/intel) - attaching monthly costs and roll-ups.


---

# Source: https://canopy.8starlabs.com/docs/maps/suggestions

export const metadata = {
  title: "Optimisations | Canopy Docs",
  description:
    "Use Canopy Optimisations to spot overlapping tools and likely gaps in a mapped technology stack or workflow.",
  alternates: { canonical: "/docs/maps/suggestions" },
};

# Optimisations

The **Optimisations** panel reads only your map, then points out two things worth
a second look: tools that overlap, and gaps a stack like yours usually fills.
It is a helper, not a nag: open it when you want a second opinion, and hide the
hint badge whenever you like.

Open it from the lightbulb on the left rail, or press `S` in the map editor. A
small badge on the rail shows how many signals are live.

Use the **All**, **Overlaps**, and **Gaps** filters to focus the review. The
counts show how many signals are in each view.

## What it flags

Optimisations are rule-based and derived entirely from the nodes already on your
canvas (their integration category) plus the map's category. Nothing leaves
your browser, and they **update live** as you add, remove, or retype nodes - so
the panel always reflects the map in front of you, not a stale snapshot.

Each card shows the observation, the **example / overlapping tools** as logos,
and an **outcome** line (after the `→`) that spells out the trade-off, so you
can weigh having the tool against not having it:

- **Overlap** - two or more tools doing the same job, in a category where one
  vendor is usually enough: database, authentication, payments, observability,
  analytics, CRM, support desk, marketing, storefront, finance, HR, team chat,
  CMS, and automation. Example: _"2 database tools → Consolidating to one drops
  a bill, an integration to maintain, and a source of drift."_ Multi-tool
  domains (frontend, backend, mobile, language, AI, infra, dev tools, workflow,
  blockchain, security, ops, productivity) are never flagged, because stacking
  those is normal.
- **Gap** - a capability the shape of your stack usually pairs with but is
  missing: observability, authentication, analytics, and payments (for a
  storefront). Example: _"No observability yet → Without it, users report your
  outages before your dashboards do."_ with Sentry / Datadog / Grafana shown as
  starting points.

Hover any tool logo on a card for a quick preview - its name, categories, and a
link to its site - so you can size up a suggestion without leaving the map.

The detection is real (it groups your actual nodes and checks which
capabilities your map has); the rules and example tools are a curated
heuristic, not an AI model.

  Optimisations are advisory. They never change your map, block a save, or count
  toward any limit - they just make overlaps and gaps easy to see so you can
  optimise your stack deliberately.

## Hide the badge

Prefer to work without the nudge? In the Optimisations panel, choose **Hide
badge**. The rail badge disappears and the panel stays available if you want
to check back. The preference is remembered on this device; re-enable it any
time with **Show badge**.


---

# Source: https://canopy.8starlabs.com/docs/maps/templates

export const metadata = {
  title: "Clone a template | Canopy Docs",
  description:
    "Start from a proven Canopy architecture template, clone it into your workspace, and adapt the map to your own system.",
  alternates: { canonical: "/docs/maps/templates" },
};

# Clone a template

Templates are proven, clonable maps in the [gallery](/templates). Instead of
starting from a blank canvas, pick a template and make it yours in minutes.

## Cloning a free template

Open the [templates gallery](/templates), find a free template, and click
**Clone to workspace**. Canopy copies the map - nodes, edges, and default
metadata - into your workspace as an editable map. No payment, no waiting.

If you belong to more than one workspace, the button reveals a **destination
picker** so you choose where the copy lands.

### One copy per workspace

A template can be cloned into a workspace **once**. In the picker, any workspace
that can't receive a copy says why:

| Label                | Meaning                                                                |
| -------------------- | ---------------------------------------------------------------------- |
| **(already cloned)** | That workspace already has a copy of this template.                    |
| **(published here)** | That workspace published the template, so it already has the original. |
| **(view-only)**      | You're a Viewer there and can't add maps.                              |

**Deleting the copy frees it up again** - the restriction is derived from the
clone itself, so once it's gone you can clone the template again into that
workspace.

  Your own templates show **Clone** and **Like** disabled - the map already
  lives in your workspace, and self-likes would distort the counts your Canopy
  Card and the gallery ranking are based on.

## Cloning a paid template

Paid templates show a **Buy** button with a one-time USD price. Clicking it opens
a Stripe-hosted checkout. After payment clears:

1. A **live entitlement** is added to your **Maps → Purchased templates** library.
2. The entitlement reflects the seller's latest updates automatically.
3. You clone it to edit - either the **original snapshot** from purchase or the
   **latest version** from the seller.

  The one-time price is what you pay to buy the template. It is separate from
  the muted "~$x/mo" figure, which only estimates what running that architecture
  would cost on your cloud bill.

Paid template entitlements live in your Library and do **not** count toward the
Free 2-map cap. Editable clones you make from them are normal maps and do count.

See [Buying templates](/docs/marketplace/buying) for the full license details,
clone actions, refund policy, and what happens when a seller updates or deletes
their template.

## Publishing your own template 

Pro workspace owners can list any map as a free or paid template from the map
editor - click **Sell** in the header. See the [Marketplace docs](/docs/marketplace)
for payout setup, fees, verification, and the full seller lifecycle.


---

# Source: https://canopy.8starlabs.com/docs/maps/use-your-own-ai

export const metadata = {
  title: "Use your own AI | Canopy Docs",
  description:
    "Let Claude, Codex, Cursor or any MCP client build the map for you. Your agent already reads your codebase - connect Canopy's MCP server and it calls create_map directly.",
  alternates: { canonical: "/docs/maps/use-your-own-ai" },
};

# Use your own AI 

Your coding agent has already read your repo. It knows the services, the
dependencies, and which one calls which - so instead of describing your stack to
Canopy, let the agent write the map.

Connect Canopy's [MCP server](/docs/mcp) once and any MCP client - **Claude Code**,
**Codex**, **Cursor**, **Claude Desktop** - can call `create_map` and the map
appears in your workspace, ready to edit.

  [Canopy AI](/docs/maps) (coming soon) is *our* model generating a map from a
  prompt. This is *your* agent, with your codebase already in context, writing
  the map through the API. It works today.

## Build a map from your agent

  
    On the dashboard or the **Maps** page, open **New map** and pick **Use your
    own AI**. It hands you a ready-made prompt - copy it.
  
  
    Paste the prompt into Claude Code, Codex, or whichever client you use, in
    the project you want mapped.
  
  
    The agent adds the Canopy MCP server and asks you to approve it. Grant
    `maps:write` - without it the server exposes read-only tools and the map
    can't be created.
  
  
    The agent inspects the project and calls `create_map` with a node per
    service and an edge per real connection. Open the map on the canvas to
    arrange it and attach [spend](/docs/operate/intel#attaching-cost).
  

The prompt in the dialog is the canonical one - it points the agent at
`/docs/mcp.md` so it reads the setup and tool reference before doing anything. In
substance it says:

```text
Connect to my Canopy MCP server, then build an architecture map of this project.

1. Add the Canopy MCP server to this client and approve the maps:write scope.
2. Read this project's dependencies, services, and how they connect.
3. Call create_map with a sensible name, each service as a node, and every real
   connection or data flow as an edge.

Be accurate to what's actually in the repo - don't invent services, and don't
guess at connections that aren't there.
```

That last line matters. An agent asked to produce a diagram will happily invent a
Redis you don't run, and a map that is _plausible_ is worse than one that is
incomplete - it gets exported into `CLAUDE.md` and believed.

## What you need

| Requirement            | Why                                                                                                                                   |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **Pro** workspace      | The MCP server and API are Pro features.                                                                                              |
| MCP connected          | Either [OAuth](/docs/mcp) (any workspace member) or a [token](/docs/mcp/token).                                                       |
| `maps:write` scope     | `tools/list` hides write tools without it, so `create_map` won't even be offered.                                                     |
| Editor access or above | Effective power is always your granted scopes ∩ your live workspace role - a Viewer cannot write maps however the grant was approved. |

## What the agent can and can't do

Writes go through the same service layer as the REST API, so an agent gets no
shortcuts that a script wouldn't:

- **Validation** - the map is schema-checked before anything is saved.
- **Plan limits** - the node cap for your plan applies (Free 25, Pro 150).
- **Sanitisation** - HTML, scripts and hidden unicode are stripped on write. This
  matters more here than anywhere else: the agent both writes that text and later
  reads it back out of a `CLAUDE.md` export.
- **No spend without `spend:read`** - a spend-blind agent that reads a map and
  writes it back carries the stored costs forward untouched, so a GET → PATCH loop
  can't silently wipe every figure.

## Seeing the result

Ask the agent to call `preview_map` and, in a client that supports
[MCP Apps](/docs/mcp/tools#see-the-map-not-just-the-json), the map renders as an
actual diagram in the conversation rather than a wall of JSON. In clients that
don't, it degrades to a text summary.

## When to use something else

- **Just want the repo scanned?** [Import a repo](/docs/maps/import) reads your
  manifests directly - no agent, no MCP, works on Free.
- **Not on GitHub, or mapping something that isn't code?** Have any LLM produce a
  file and [import the JSON](/docs/maps/import-json). That path needs no
  connection at all.
- **Want a starting point rather than your own stack?**
  [Clone a template](/docs/maps/templates).

## Where to go next

- [MCP server](/docs/mcp) - connect with OAuth or a token, and what the scopes mean.
- [Tools](/docs/mcp/tools) - every tool, including `create_map`, `update_map` and
  `preview_map`, with example prompts.
- [Map syntax](/docs/maps/architecture-as-code) - the shape the agent is writing.


---

# Source: https://canopy.8starlabs.com/docs/marketplace

export const metadata = {
  title: "Marketplace | Canopy Docs",
  description:
    "Learn how Canopy's marketplace works for buying, selling, licensing, cloning, and maintaining community architecture templates.",
  alternates: { canonical: "/docs/marketplace" },
};

# Marketplace

The marketplace is the [templates gallery](/templates) - where free and paid
architecture maps live side by side. Templates are real Canopy maps made by
the community and Pro sellers.

## How buying works

Anyone can buy, including Free workspaces - no [Pro plan](/pricing) required
to purchase.

## Creator identity

Template cards separate the creator account from the publishing workspace. Hover
the author to preview the person's Canopy Card and public stats. Hover the
workspace to preview the workspace profile, verification status, and public
listing identity. Public cards use display names, handles, selected avatars, and
workspace handles. Email addresses are never exposed.

## How selling works

[Pro workspace owners](/docs/team-billing) can list any map as a paid template
(one-time, up to $199 USD) after completing Stripe payout verification. The
platform takes 10% + $0.50 per sale; the rest goes straight to the seller's
connected Stripe account. See [Selling templates](/docs/marketplace/selling) for
the full setup guide.

## In this section

- [Buying templates](/docs/marketplace/buying) - checkout, live licenses,
  cloning, and what happens if the seller disappears.
- [Selling templates](/docs/marketplace/selling) - listing a map, payout setup,
  and fee breakdown.
- [Trust & verification](/docs/marketplace/trust) - workspace verification,
  watermarks, and anti-forgery.
- [Seller lifecycle](/docs/marketplace/lifecycle) - updates, archiving, and
  downgrade behavior.


---

# Source: https://canopy.8starlabs.com/docs/marketplace/buying

export const metadata = {
  title: "Buying templates | Canopy Docs",
  description:
    "Buy Canopy architecture templates from any plan, manage live licenses, and clone purchased maps into your workspace.",
  alternates: { canonical: "/docs/marketplace/buying" },
};

# Buying templates

Anyone can buy templates, including [Free workspaces](/docs/accounts). A
[Pro plan](/pricing) is only required to **sell** - not to buy.

## Your license after purchase

  The purchase price is a one-time charge to buy the template. It is separate
  from the map's estimated monthly infra cost (the muted "~$x/mo" figure), which
  only estimates what running that architecture would cost on your cloud bill.
  See [Intel](/docs/operate/intel) for how Canopy handles costs.

## What happens when you buy

1. **Checkout** - Stripe hosts the one-time payment. Canopy takes the marketplace
   fee and routes the rest to the seller through Stripe Connect.
2. **License created** - after payment clears, a durable entitlement is recorded
   in your account and a snapshot of the map at this exact moment is saved permanently.
3. **Library** - you land in **Maps → Purchased templates** where your
   entitlement lives, accessible from any workspace.

## Your two clone actions

A paid entitlement gives you two distinct ways to clone:

Both produce a normal, editable private [map](/docs/maps) in your workspace. You
can run either action repeatedly at no extra charge.

## What happens if the seller disappears

Your entitlement and snapshot are stored independently of the seller's workspace.

| State              | What changed              | Clone original | Clone latest |
| ------------------ | ------------------------- | -------------- | ------------ |
| **Archived**       | Seller deleted the map    | ✓ works        | ✗ blocked    |
| **Source removed** | Seller deleted workspace  | ✓ works        | ✗ hidden     |
| **Paused**         | Seller downgraded to Free | ✓ works        | ✓ works      |

## Quotas

Purchased entitlements live in your Library and are exempt from the Free 2-map
cap. Editable clones made from them are normal [maps](/docs/maps) and do count
toward the cap - see [Accounts & workspaces](/docs/accounts) for free workspace limits.

## Transaction history & receipts

Every template purchase is recorded in Canopy under **Settings → Plans &
billing → Template purchases**, with the date, amount, seller and status. The
list is **account-wide** - your purchases follow you across every workspace.

  **Template purchases** (one-time marketplace buys) are shown in Canopy on the
  billing page. **Subscription invoices** (your Pro per-seat plan) are billed
  and receipted by Stripe - open the **Stripe customer portal** from the
  **Manage** button on the billing page to view, download or print those
  invoices.

## Refunds

  Template purchases are **non-refundable**. Once your purchase completes and
  the entitlement is granted, the sale is final - you're not entitled to a
  refund, including the platform fee. See the
  [Terms](/terms#5-template-marketplace).

A refund may be issued only at the **seller's discretion** (for example, to
resolve a dispute) - it isn't something you can request as of right. If a seller
does refund you (or you win a chargeback), the purchase shows as **Refunded** in
your transaction history and your **entitlement is revoked**: you lose read
access to the seller's map, can no longer clone it, and **any editable copies you
cloned from it are deleted**. If you believe a template was materially
misrepresented or harmful, [talk to support](/support) with your order details.


---

# Source: https://canopy.8starlabs.com/docs/marketplace/lifecycle

export const metadata = {
  title: "Seller lifecycle | Canopy Docs",
  description:
    "Understand how Canopy paid templates behave through updates, purchases, downgrades, suspensions, archival, and deletion.",
  alternates: { canonical: "/docs/marketplace/lifecycle" },
};

# Seller lifecycle

Paid templates stay connected to the seller's source map through live licenses.
That keeps buyer entitlements durable while letting sellers improve their work
over time - and gives buyers a permanent safety net through the snapshot captured
at purchase.

## Template states

## Updates reach buyers automatically

Buying a template does not hand over a static copy. It grants a live entitlement
to the seller's source map. When the seller improves the template, buyers can
clone the latest version on demand and get those improvements immediately via
**Clone latest**.

## Snapshot at purchase

At the moment a buyer pays, Canopy captures a frozen copy of the map's nodes and
edges and saves it permanently to the entitlement. This snapshot is the buyer's
**Clone original** baseline - it never changes, even if the seller heavily
rewrites the template or deletes their workspace afterward.

## Deleting a sold template

If a seller deletes a template that has at least one buyer, Canopy **archives**
it rather than erasing it:

- Removed from the gallery immediately - no new purchases.
- **Clone latest** is blocked for all existing buyers.
- The entitlement and its snapshot survive untouched.
- Buyers see an **Archived** badge in their Library.
- **Clone original** still works - buyers can always clone the frozen snapshot.

A template with **no buyers** is hard-deleted (cascade removes nodes and edges).

## Seller downgrades to Free

When a Pro seller downgrades, all their listings are **paused** - hidden from
the gallery, buy button removed. No new purchases are possible until the workspace
re-upgrades to Pro. **Existing buyers are entirely unaffected** - entitlements,
snapshots, Clone original, and Clone latest all continue to work as before.

The seller can also flip a paused listing to free (price = $0) to keep it
cloneable by anyone without re-upgrading.


---

# Source: https://canopy.8starlabs.com/docs/marketplace/selling

export const metadata = {
  title: "Selling templates | Canopy Docs",
  description:
    "List a Canopy map as a paid architecture template, connect Stripe payouts, set pricing, and manage your marketplace listing.",
  alternates: { canonical: "/docs/marketplace/selling" },
};

# Selling templates 

Open a map, click **Sell** in the editor header, toggle **List as a paid
template**, and set a one-time price. Your listing then shows in the gallery with
a **Paid** badge and price tag.

  Listing a map for sale is an **owner-only** action. See [Roles &
  permissions](/docs/team-billing) for what Admins and Editors can do. They can
  still share [maps](/docs/maps) publicly for free - they just cannot set a
  price.

  A **paid**  plan unlocks paid-template listing, but buyers cannot
  purchase until you have set up payouts and passed Stripe's identity check
  (KYC). These are two separate gates: your Canopy plan and your Stripe payout
  account. The **7-day free trial does not include selling** - listing paid
  templates unlocks once your trial converts to a paid plan (everything else in
  Pro works during the trial).

## Setting up payouts

Go to **Dashboard → Sales → Set up payouts**. Canopy creates a Stripe Connect
Express account and sends you to Stripe's hosted onboarding - enter your identity
and bank details. You can onboard as an individual; a registered company is not
required.

Once Stripe clears the KYC check, **Sales** shows _Payouts active_ and your
templates become buyable. Your payout details and buyers' card details stay with
Stripe; they never touch Canopy's servers.

### Supported countries

**Payouts are Singapore-only for now.** To sell paid templates you need to be
based in Singapore and able to provide a Singapore bank account, and you'll be
asked to confirm that before setup starts.

This is a Canopy limit, not just a Stripe one. Stripe fixes a payout account's
country **when the account is created** and it can never be changed afterwards -
the account has to be abandoned and a new one made. So we ask up front rather
than letting you get most of the way through identity checks before discovering
the bank account you have isn't one Stripe will accept.

You can still **publish free public templates** from anywhere; the limit is only
on getting paid. We expect to widen this - if you want to sell from another
country, tell us at **hello@8starlabs.com** and it helps us prioritise.

  Buyers don't need a Stripe account and can purchase from almost any country -
  Stripe accepts international cards and handles currency conversion. The only
  blocks are Stripe's own compliance ones (e.g. sanctioned regions), which it
  applies automatically at checkout. The country limit is on **sellers**
  receiving payouts, not on buyers paying.

### Disconnecting payouts

From **Account settings → Seller payouts** you can **Disconnect payout account**.
This pauses your for-sale templates (they stay listed but can't be bought) and
doesn't touch your Stripe account - the Express account stays open at
express.stripe.com with any pending balance, which you can still withdraw there.

  Stripe doesn't reattach a disconnected account: setting up payouts again
  creates a **brand-new** connected account and you'll **redo KYC**. Your old
  account and its balance stay separate at express.stripe.com. So only
  disconnect if you're switching accounts or stepping away from selling - not as
  a temporary pause.

## Seller fees

- Prices are one-time in **USD**, from **$1** to **$199**. Set `$0` to keep a
  template free.
- Canopy charges **10% + $0.50** per sale. The rest is paid to you via Stripe
  Connect on Stripe's normal payout schedule, with **no other fees deducted from
  your earnings** - Canopy absorbs Stripe's payout and account fees.
- You are responsible for taxes on your earnings.

### When you get paid

Payouts are **automatic**. After a sale, the money lands in your Stripe balance
as pending, clears once the payment settles, and is then paid to your bank
account on Stripe's automatic schedule - typically about a week from the sale
for a Singapore account.

There is **no "pay me now" button**, and the schedule isn't adjustable from your
Express dashboard. That delay is deliberate: refunds and chargebacks can arrive
after a sale, and paying out instantly would mean clawing money back from you
later instead of simply not sending it yet.

You can always see your balance, upcoming payout, and full payment history in
your Stripe Express dashboard via **Sales → Manage payouts**.

## Refunds

Sales are **final** - buyers are not entitled to a refund, and there is no
self-service refund. Issuing one is entirely **at your discretion** (for example,
to resolve a dispute): contact us at **hello@8starlabs.com** to arrange it. If a
refund is issued, the buyer is refunded **in full** and immediately loses access
to the template, and the refunded amount - **including Canopy's platform fee** -
is reversed from your payout, so the cost is borne entirely by you. Refunds are
final and can't be undone.

## Next

- [Trust & verification](/docs/marketplace/trust) - workspace verification and
  anti-forgery.
- [Seller lifecycle](/docs/marketplace/lifecycle) - updates, archiving, and
  downgrade behavior.
- [Buying templates](/docs/marketplace/buying) - see what the buyer experience
  looks like from the other side.


---

# Source: https://canopy.8starlabs.com/docs/marketplace/trust

export const metadata = {
  title: "Trust and provenance | Canopy Docs",
  description:
    "See how Canopy uses verified workspaces, provenance trails, watermarks, and live licenses to protect marketplace trust.",
  alternates: { canonical: "/docs/marketplace/trust" },
};

# Trust and provenance

Marketplace trust comes from verified workspaces, provenance trails, and
watermarked clones.

## Public identity

Marketplace cards use public-safe creator and workspace identity. The creator
preview shows the account profile and Canopy Card stats. The workspace preview
shows the publishing workspace, its handle, logo, and verification status. These
are intentionally separate, so a personal author hover does not masquerade as a
workspace hover.

Email addresses are not exposed in gallery data. Avatars come from the selected
Canopy avatar or a public-safe avatar hash.

## Workspace verification

A verified workspace earns a blue seal across Canopy - on your public profile,
templates, and original listings. Verified originals show the **Verified
original** badge so buyers can tell the authentic source from a re-listed copy.

Request verification from **Dashboard → Settings → Workspace → Workspace
verification**. The card shows your current status: _not verified_, _pending
review_, or _verified_. Once you submit, you cannot send another request until
we have reviewed it.

  Only the workspace owner can request verification. Verification, billing,
  Stripe payouts, and listing a map for sale are all owner-only.

Before submitting, make sure you have:

- A complete public profile - workspace handle, logo, and a short bio.
- Proof you own the brand - a GitHub org, company domain, or official socials.
- Original maps or templates you created, not clones of others.
- A reachable contact email.
- An account in good standing.

We review by hand and email you the decision.

## Anti-forgery

Every clone Canopy makes, free or paid, is stamped with provenance: the original
handle and name, the cloning account, and a unique watermark. That means leaked
or repackaged copies are traceable back to the buyer.

- Verified workspaces show **Verified original** on their originals.
- Cloned maps show **Cloned from @origin**.
- The durable value - Workspace Intel, drift, and `CLAUDE.md` / `AGENTS.md`
  exports - stays tied to the Canopy entitlement, so a raw repackaged diagram
  loses what people actually pay for.

No platform can make copying physically impossible. Watermarking,
verified-seller trust, and takedowns make the original the only copy worth
buying.


---

# Source: https://canopy.8starlabs.com/docs/mcp

export const metadata = {
  title: "MCP server | Canopy Docs",
  description:
    "Connect Claude, Cursor, Codex, and other MCP clients to Canopy so agents can read and update live architecture maps.",
  alternates: { canonical: "/docs/mcp" },
};

# MCP server 

**Let Claude, Cursor or Codex read your architecture directly.**

Instead of pasting a diagram into a prompt and hoping it's current, your agent
asks Canopy. It gets your real services, what they cost, and how they connect -
live, every time it asks.

It's a **remote** server: a URL you paste into your client. Nothing to install,
nothing to run.

## Connect

Pick whichever your client supports. Both end up in the same place.

  
  

Both use the same address:

### Which one should I use?

Use **OAuth** if your client offers it - Claude, Cursor and Codex all do. You
sign in, tick what the agent may see, and you can take it back later without
disturbing anyone else.

Use a **token** for CI, scripts, or any client that can't open a browser.

|               | Workspace token                     | [OAuth](/docs/mcp/oauth)             |
| ------------- | ----------------------------------- | ------------------------------------ |
| Setup         | Paste a token into a header         | Approve in a browser, no token       |
| Permissions   | Scopes you pick when you create it  | Scopes the approver ticks at consent |
| Ceiling       | Owner, so any scope                 | Capped by the approver's role        |
| Who can grant | Owner only                          | Any member                           |
| Revoke        | Settings → API → revoke the token   | Settings → API → **Connections**     |
| Best for      | CI, headless, clients without OAuth | Claude, Cursor, Codex                |

## Then what?

  
  
  

## Why bother

A diagram in a prompt is a photograph - true when it was taken. Ask an agent
"what breaks if the API goes down?" against a pasted diagram and it answers from
whatever was true that day.

Connected over MCP, it calls `find_dependencies` and answers from the map as it
is right now.

## Coming soon

  Support for **x402** and **MPP** (machine protocol payments) is on the
  roadmap, so agents can transact programmatically (e.g. pay for a template or
  metered access) without a human in the loop. Not available yet, this is a
  heads-up, not a live feature.


---

# Source: https://canopy.8starlabs.com/docs/mcp/build

export const metadata = {
  title: "Build a map from your agent | Canopy Docs",
  description:
    "Use Claude, Codex, or another MCP client to create and update a Canopy system map directly from your codebase.",
  alternates: { canonical: "/docs/mcp/build" },
};

# Build a map from your agent 

You don't have to build maps in Canopy. If you already have Claude or Codex open
in your repo, connect Canopy as an MCP server and ask:

Your agent already understands your stack, so it does the mapping. It calls
`create_map` with the services and connections it found, and the map appears in
your workspace ready to edit. This is a fourth way in alongside
[importing a repo](/docs/maps/import), [pasting canopy.json](/docs/maps/import-json),
and [cloning a template](/docs/maps/templates) - useful when the source of truth
is something Canopy can't scan for you, like an internal wiki, a Terraform state,
or a whiteboard photo your agent just read.

Two things to know:

- **It needs `maps:write`.** Tick it at consent (or include it when you create a
  token). A connection without it can read your maps but can't create one.
- **`update_map` replaces arrays wholesale.** If you send `nodes`, that becomes
  the entire node list. Have the agent read the map first and send the full set,
  or it will drop whatever it left out.

  `delete_map` cannot be undone. Ask your agent to confirm with you before it
  calls that one. A map other people have purchased as a template is archived
  rather than deleted, so existing buyers keep their access.


---

# Source: https://canopy.8starlabs.com/docs/mcp/oauth

export const metadata = {
  title: "Connect with OAuth | Canopy Docs",
  description:
    "Connect an MCP client to Canopy with OAuth, sign in once, choose a workspace, and avoid copying or rotating tokens.",
  alternates: { canonical: "/docs/mcp/oauth" },
};

# Connect with OAuth 

OAuth (**sign-in-with-Canopy**) is the easiest, safest way to connect - **no
token to copy, rotate, or leak**. You add the URL, approve once in your browser,
and the client holds a short-lived, revocable, scoped grant. It's standard
**OAuth 2.1 with PKCE**, so any client that supports remote MCP auth just works.

  Canopy implements the full MCP authorization spec: **OAuth 2.1 + PKCE
  (S256)**, **Dynamic Client Registration** ([RFC
  7591](https://www.rfc-editor.org/rfc/rfc7591)), discovery via
  **protected-resource** ([RFC 9728](https://www.rfc-editor.org/rfc/rfc9728))
  and **authorization-server** ([RFC
  8414](https://www.rfc-editor.org/rfc/rfc8414)) metadata, and **resource
  indicators** ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707)) so tokens
  are audience-bound to this server. Any client that speaks remote-MCP OAuth
  connects with no extra setup - no client ID to register by hand.

## Add the server

Point your client at the MCP URL with **no token** - it runs a one-time browser
sign-in to get access. How you trigger that sign-in differs by client:

### Prefer a UI?

Most apps let you add the server without touching a config file - paste the
URL, and the OAuth sign-in runs in your browser:

- **Claude Desktop / claude.ai** - Settings → **Connectors** → **Add custom
  connector** → paste the URL.
- **Cursor** - Settings → **MCP** → **Add server** → paste the URL.
- **Codex** - no UI: add the `url` to `~/.codex/config.toml`, then run
  `codex mcp login canopy`.

## Approve access

Your client opens Canopy in the browser. Sign in (if you aren't already) and
you'll see exactly what's being requested:

Pick the **workspace** to connect from the dropdown (only Pro workspaces you
belong to are listed), click **Approve**, and the client is connected.

## What you're granting

  An OAuth grant can only **read** your maps (services, cost, dependencies) - it
  can't create, edit, or delete anything, and it can't touch the REST write
  endpoints. It's scoped to the **one workspace** you approved, **expires**
  after 30 days, and can be **revoked** anytime.

|                     |                                                                    |
| ------------------- | ------------------------------------------------------------------ |
| **Scope**           | the permissions you tick at consent, capped by your workspace role |
| **Workspace**       | the single workspace you approved                                  |
| **Plan**            | requires Pro (free workspaces are prompted to upgrade)             |
| **Who can approve** | any member of the workspace                                        |
| **Lifetime**        | ~30 days, then the client re-authorizes silently                   |

## Revoke access

Open **Dashboard → Settings → Workspace → API → Connections**, find the agent,
and click **Revoke** - it's cut off immediately (the next tool call returns
`401`). Grants also **expire after 30 days** on their own, after which the client
silently re-authorizes (you approve again). Removing the Canopy server from a
client's config also stops it.

## Troubleshooting

- **Client doesn't open a browser / shows a token field.** It may not support
  remote-MCP OAuth yet - use a [workspace token](/docs/mcp/token) instead.
- **"MCP requires a Pro workspace."** OAuth (like all of MCP) is Pro - upgrade
  the workspace you're approving, or switch to a Pro workspace and reconnect.
- **It connected to the wrong workspace.** The grant is bound to the workspace
  you picked from the dropdown at approval time. Remove and re-add the server,
  then choose the right workspace when you re-approve.


---

# Source: https://canopy.8starlabs.com/docs/mcp/permissions

export const metadata = {
  title: "MCP permissions | Canopy Docs",
  description:
    "Control what MCP agents can see and change in Canopy, who can grant access, and how to revoke connections safely.",
  alternates: { canonical: "/docs/mcp/permissions" },
};

# Permissions & connections 

What an agent is allowed to see, who can grant it, and how to take it back.

## Permissions

Both ways of connecting use the **same four scopes**, so an agent's access reads
the same whether it arrived by token or by sign-in:

| Scope         | Lets the agent                                     |
| ------------- | -------------------------------------------------- |
| `maps:read`   | Read maps: services, connections, dependencies     |
| `maps:write`  | Create, change, and delete maps                    |
| `spend:read`  | See monthly cost, currency, and billing model      |
| `export:read` | Export Markdown, CLAUDE.md / AGENTS.md, and images |

Two rules make this safe on a shared workspace:

- **Your role is the ceiling.** A grant can never exceed what the person who
  approved it can do. A **Viewer** cannot hand an agent `maps:write`, no matter
  what the client asks for. This is re-checked on every request, so changing
  someone's role resizes their agent immediately, and **removing them from the
  workspace kills their connections outright.**
- **Cost is a real boundary.** Without `spend:read`, spend figures are stripped
  from every response and `get_spend` disappears from the tool list. An agent can
  map your architecture without ever seeing what it costs.

Tools that need more than you granted simply aren't listed, so an agent won't
plan around something it will be refused on.

## Managing what's connected

**Settings → API → Connections** lists the agents attached to your workspace.
Workspace **owners and admins see every connection** (which client, who
authorized it, when, when it was last used, and what it can do); everyone else
sees the ones they authorized themselves.

You can **remove a permission** from a live connection and it applies on the
agent's very next request. Adding one is deliberately not possible here: to widen
access, reconnect the client and approve the larger set, so a connection can
never quietly gain permissions its approver never saw.

## Rate limits &amp; usage

MCP is **not a separate quota**. Tool calls count against the _same_
[per-workspace rate limit](/docs/api/rate-limits) and daily cap as the REST API -
one budget. Read tools draw from the read allowance; `create_map`, `update_map`,
and `delete_map` draw from the **write** allowance, which is held about ten times
tighter. The same operation costs the same whichever way it arrives. Calls are logged the same way and show up in
**Settings → API** under "Usage", tagged **MCP** so you can tell them apart from
direct API traffic.


---

# Source: https://canopy.8starlabs.com/docs/mcp/token

export const metadata = {
  title: "Connect with a token | Canopy Docs",
  description:
    "Connect any MCP client to Canopy with a workspace token sent in the Authorization header.",
  alternates: { canonical: "/docs/mcp/token" },
};

# Connect with a workspace token 

A **workspace token** works with **every** MCP client - you paste it into the
`Authorization` header. Reach for this when your client doesn't support
[OAuth](/docs/mcp/oauth), or for headless / CI setups where a browser sign-in
isn't practical. It's the **same token** the [REST API](/docs/api) uses.

## Create a token

In the dashboard, go to **Settings → Workspace →
[API tokens](/dashboard/settings/workspace/api)** and create one (owner-only).
The secret - `cnpy_live_…` - is shown **once**, so copy it immediately. See
[Authentication](/docs/api/authentication#workspace-tokens) for the full
walkthrough.

## Add the server

Put the token in the `Authorization` header as a bearer:

"`,
    },
    {
      label: "Cursor",
      lang: "json",
      code: `// .cursor/mcp.json\n{\n  "mcpServers": {\n    "canopy": {\n      "url": "https://canopy.8starlabs.com/api/mcp",\n      "headers": { "Authorization": "Bearer <your-token>" }\n    }\n  }\n}`,
    },
    {
      label: "Codex",
      lang: "toml",
      code: `# ~/.codex/config.toml - token sourced from an env var\n[mcp_servers.canopy]\nurl = "https://canopy.8starlabs.com/api/mcp"\nbearer_token_env_var = "CANOPY_TOKEN"`,
    },
    {
      label: "Generic",
      lang: "json",
      code: `// any client that supports remote / Streamable-HTTP MCP servers\n{\n  "mcpServers": {\n    "canopy": {\n      "type": "http",\n      "url": "https://canopy.8starlabs.com/api/mcp",\n      "headers": { "Authorization": "Bearer <your-token>" }\n    }\n  }\n}`,
    },
  ]}
/>

## What it grants

  Pick the [permissions](/docs/mcp#permissions) when you create the token, and
  grant only what the client needs. If you include `maps:write`, that token can
  change and delete maps through the [REST API](/docs/api) - the MCP server
  itself only ever **reads**, but the secret you paste in could do more if it
  leaks or is used elsewhere. A token is also **owner-minted**, so unlike an
  [OAuth](/docs/mcp/oauth) grant it isn't capped by anyone's role. Treat it like
  a password: store it in an environment variable, never commit it, and prefer
  OAuth when your client supports it.

|                    |                                                                   |
| ------------------ | ----------------------------------------------------------------- |
| **Scope**          | the scopes you pick when you create it (MCP tools only ever read) |
| **Who can create** | the workspace **owner** only                                      |
| **Plan**           | Pro                                                               |
| **Limit**          | up to **10 active tokens** per workspace                          |
| **Lifetime**       | until it expires (you choose) or you revoke it                    |

## Revoke access

Open **Dashboard → Settings → Workspace →
[API](/dashboard/settings/workspace/api)**, find the token, and **Revoke** it. It
stops working **immediately and everywhere** - the next request from **any MCP
client _or_ the REST API** using that token returns `401`. There's nothing to
un-configure on the client side; the token is simply dead.

A workspace can hold **only one active token at a time**. To **rotate**, delete
the current token, create a new one, and update your client (there's a brief gap
while no token is live - schedule rotations accordingly).

For the side-by-side comparison, see
[Token vs OAuth](/docs/mcp#token-vs-oauth) on the MCP overview.


---

# Source: https://canopy.8starlabs.com/docs/mcp/tools

export const metadata = {
  title: "MCP tools | Canopy Docs",
  description:
    "Explore the tools available through Canopy MCP, including map creation, architecture context, spend, and service queries.",
  alternates: { canonical: "/docs/mcp/tools" },
};

# MCP tools 

What an agent can actually do once it's connected: the eight tools, the one that
draws your map in the chat, and prompts you can paste to try them.

## Tools

Once connected, agents get these tools (each takes an optional `map` id to scope
to a single map). **Every MCP tool only reads** - there is no write tool, so
nothing an agent does over MCP can change a map. Which tools appear depends on
the [permissions](#permissions) you granted:

| Tool                | Returns                                                                          |
| ------------------- | -------------------------------------------------------------------------------- |
| `get_architecture`  | a map's full nodes + edges (or the map list)                                     |
| `list_services`     | every node with its tech, cost and billing                                       |
| `get_spend`         | cost rolled up per map and across the workspace, grouped by each node's currency |
| `find_dependencies` | upstream/downstream blast radius for a node                                      |
| `preview_map`       | the map **drawn**, as an interactive diagram in the chat                         |
| `create_map`        | build a new map from services and connections                                    |
| `update_map`        | change an existing map                                                           |
| `delete_map`        | remove a map (archived instead if it has buyers)                                 |

The first five only read. The last three need the
[`maps:write` permission](#permissions), so a read-only connection never even
sees them listed.

## See the map, not just the JSON

`preview_map` renders the map as a real diagram inside your client, instead of
printing nodes and edges as JSON for you to read. Ask "show me my production
map" and the map appears in the conversation:

- **Laid out like Canopy.** Nodes you positioned in the editor keep their
  coordinates; a map built from the API or a pasted `canopy.json` is
  auto-arranged with the same layout the editor's auto-arrange uses.
- **Click a service** to trace its blast radius: everything upstream and
  downstream lights up, the rest dims, and a panel lists both sides.
- **Pan, zoom, and fullscreen**, plus **Open in Canopy** to jump to the real
  editor.
- **Switch maps** from the dropdown without going back to the agent.
- **Matches your theme**, light or dark.

  This uses [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview)
  (SEP-1865), the official MCP extension for interactive UI. The tool points at
  a `ui://` resource that Canopy serves; your client renders it in a sandboxed
  iframe. The view is a single self-contained document with **no external
  scripts, fonts, or network calls**, so it only ever sees the map data your
  client hands it.

Support varies by client. In a client **without** MCP Apps support, `preview_map`
still works: it returns a short text summary instead of a drawing, and every
other tool is unaffected. Check the [MCP client support
matrix](https://modelcontextprotocol.io/extensions/client-matrix) for where the
diagram renders today.

## Example prompts

Ask your MCP-connected agent questions like these. Each one is copyable:

  `find_dependencies` needs a node `id`, such as `api`, `web`, or `db`. If you
  only know the display name, ask the agent to call `list_services` first and
  choose the matching id.


---

# Source: https://canopy.8starlabs.com/docs/operate/currencies

export const metadata = {
  title: "Supported currencies | Canopy Docs",
  description:
    "Set a workspace base currency in Canopy and keep service costs, group totals, and system spend consistent.",
  alternates: { canonical: "/docs/operate/currencies" },
};

# Supported currencies

Every workspace has a single **base currency**. Every service cost you enter is
**stored** in it, and per-node, per-group, and system totals all roll up in it.
Pick it during onboarding or change it under **Workspace settings → Cost
currency**.

See [Intel](/docs/operate/intel) for how costs roll up across your architecture.

## Viewing in another currency

On the **Intel** page you can switch the _display_ currency to any supported one

Canopy converts the totals on the fly using exchange rates that refresh
**daily**. This is a **view only**: it never rewrites your stored amounts, and it
doesn't change what other members see.

## Available currencies

Canopy supports the 10 major currencies below.

  Switching the base currency **relabels** existing amounts - it does **not**
  recalculate them. A node entered as `2,100` becomes `€2,100`, not the
  converted value, so re-enter costs if you switch base. Only the live
  Intel-page _view_ does FX conversion (rates refresh daily). Need a currency we
  don't list yet? [Send feedback](/support) and we'll add it.


---

# Source: https://canopy.8starlabs.com/docs/operate/intel

export const metadata = {
  title: "Intel | Canopy Docs",
  description:
    "Use Canopy Intel to roll up service spend, billing models, ownership, and operational signals across your system map.",
  alternates: { canonical: "/docs/operate/intel" },
};

# Intel

Intel is where Canopy turns your map metadata into workspace signals. Attach a
monthly cost to any [node](/docs/maps/nodes) and it rolls up across groups,
billing models, maps, and the whole system, so your architecture map also shows
where the money and tool choices are concentrated.

## Attaching cost

Select a [node](/docs/maps/nodes), open the inspector, and set its **Monthly cost**.
Costs slide into the map immediately and the system total updates live. You can
enter a flat figure or connect a billing source to keep it current.

## Roll-ups

Intel aggregates cost two ways:

- **By group** - frontend, backend, AI, platform. See [Groups](/docs/maps/groups)
  for how to assign nodes to groups.
- **By system** - a single number for the whole [map](/docs/maps), such as `$12.4k/mo`.

The group view sorts domains by cost, so your most expensive area is always obvious.

## Display currency

Every workspace shows cost in a single currency, set under
**Workspace settings → Cost currency**. Canopy supports 10 major currencies, see
[Supported currencies](/docs/operate/currencies) for the full list.

This is purely about how your tracked cost is _displayed_ and is separate from
how Canopy itself is billed. For plans and seats, see
[Subscriptions & seats](/docs/team-billing/subscriptions).

## Budgets

Set a monthly budget per group or for the whole system. When projected cost crosses
a threshold, Canopy flags it on the [dashboard](https://canopy.8starlabs.com/dashboard)
and can post to Slack.

## The Intel page

See your current monthly cost, service count, and per-service breakdown from the
[Intel page](https://canopy.8starlabs.com/dashboard/intel), rolled up live from
the cost on each node and broken down by billing model and by map.

## Optimization tips


---

# Source: https://canopy.8starlabs.com/docs/quickstart

export const metadata = {
  title: "Quickstart | Canopy Docs",
  description:
    "Build your first Canopy system map in about two minutes, add services and workflows, attach cost, and export an AI-ready architecture brief.",
  alternates: { canonical: "/docs/quickstart" },
};

# Quickstart

Get from zero to a living map in about two minutes - add your services,
workflows, or AI agents, map the connections, attach cost, and export a
`CLAUDE.md` your agents can read.

  A Canopy account - the Free plan is enough to follow this guide.

  

From the dashboard click **New map**, give it a name (e.g. `acme-platform`), then pick how you want to populate it. There's no wrong choice - all paths land on the same editable canvas.

- **Import from GitHub** - install the Canopy GitHub App, pick a repo, and Canopy detects services automatically from your `package.json`, lockfile, and infra files.
- **Browse the library** - search tools, workflows, AI platforms, services, and hand-pick the pieces that make up your system.
- **Browse templates** - start from a curated starter or community template in the [templates gallery](/templates). Canopy copies it straight into your workspace, ready to edit.
- **Ask Canopy AI**  - describe your system in plain English and Canopy generates the map. 
- **Start blank** - drop [nodes](/docs/maps/nodes) onto the canvas one at a time.

Canopy will prompt you to install the **Canopy GitHub App** on your account or organisation. Once installed, select the repo and Canopy parses it and places a node for each detected service.

No GitHub? Skip straight to step 3 and add [nodes](/docs/maps/nodes) manually or from the library.

Every node has an **edge handle** on its right side. Drag it to another node to draw a directed arrow - `web → api`, `api → db`, and so on. Hit **Auto-layout** in the toolbar to re-flow the graph at any time, or drag nodes into position manually.

Labels are optional - double-click an edge to add one (`gRPC`, `webhook`, `async`).

Click any node to open the **Inspector** panel on the right. Fill in the monthly cost, billing model (usage-based, flat, per-seat), owner, SLA, and status. Costs roll up per group - see [Intel](/docs/operate/intel) for how roll-ups and budgets work.

Open the map's **Export** menu and choose **CLAUDE.md** or **AGENTS.md**. Drop the file in your repo root so Claude Code, Cursor, or any agent that reads project files sees your full architecture - services, cost, owners, and topology - before it touches a line of code. See all [export formats](/docs/maps/exports).

Here's what a finished export looks like - the 8starlabs/ui repo scanned into Canopy, with every format previewed:

## Next steps

- [Nodes](/docs/maps/nodes) - the building blocks and their metadata fields.
- [Intel](/docs/operate/intel) - roll-ups, budgets, and display currency.
- [Optimisations](/docs/maps/suggestions) - spot overlapping tools and gaps.
- [MCP server](/docs/mcp) - let agents query your live map over MCP instead of a static file.
- [Roles & permissions](/docs/team-billing) - who can see and edit what.
- [Templates](/docs/maps/templates) - clone a community map or sell your own.


---

# Source: https://canopy.8starlabs.com/docs/security

export const metadata = {
  title: "Security | Canopy Docs",
  description:
    "Learn how Canopy protects maps and workspace data with isolation, encryption, secure sharing, authentication, and token controls.",
  alternates: { canonical: "/docs/security" },
};

# Security

Canopy holds a map of how your systems fit together - so we treat that picture
as sensitive by default. These pages cover how Canopy protects your data, what we
store, the controls you have, and how to use them well.

## Overview

### At a glance

- **Private by default** - maps, workspaces, and cost are scoped to your
  workspace and visible only to its members. Nothing is public unless you
  explicitly publish it.
- **Workspace isolation** - every row in the database is guarded by Postgres
  **Row-Level Security (RLS)**, so one workspace can never read another's data,
  even through the API.
- **Encrypted in transit and at rest** - all traffic is served over HTTPS/TLS,
  and data is encrypted at rest by our managed Postgres provider.
- **No payment data on our servers** - billing runs through **Stripe**; Canopy
  never sees or stores your card details.
- **Role-based access** - Owner, Admin, Editor, and Viewer roles are assigned
  per workspace, so people only get the access they need.

### How access works

Authentication is handled by **Supabase Auth** - you sign in with GitHub OAuth
or email and password. Canopy hashes passwords and never stores them in plain text.

Once signed in, every read and write is checked twice:

1. **Application layer** - routes verify your session and the active workspace.
2. **Database layer** - RLS policies re-check ownership on every query, so a bug
   in the app can't expose another workspace's rows.

Public profiles and public maps are the one exception - they're served through a
narrow, read-only path that exposes **only** the maps you've explicitly marked
public. See [Data & privacy](/docs/security/data-privacy) for the details.

  SSO/SAML, audit logs, and SOC 2 attestation are not part of the current
  product. If your org requires them today, [talk to support](/support) and
  we'll share our timeline.

## Where to go next

- [Data & privacy](/docs/security/data-privacy) - what Canopy stores, RLS isolation, encryption, and public vs private.
- [Two-factor authentication](/docs/security/two-factor) - authenticator-app codes, recovery codes, and how they are enforced.
- [Best practices](/docs/security/best-practices) - secrets, least access, sharing, sign-in, and API tokens.
- [Report a vulnerability](/docs/security/report-a-vulnerability) - how to reach us, and what we commit to.


---

# Source: https://canopy.8starlabs.com/docs/security/best-practices

export const metadata = {
  title: "Security best practices | Canopy Docs",
  description:
    "Keep secrets out of maps, grant the least access needed, share deliberately, strengthen sign-in, and protect API and MCP tokens.",
  alternates: { canonical: "/docs/security/best-practices" },
};

# Best practices

A few habits keep your architecture maps safe to share and safe to keep.

## Never put secrets in a map

Canopy maps describe **architecture**, not credentials. Treat them like a
diagram you might screenshot into a doc.

- Reference a secret by **name** (`STRIPE_SECRET_KEY`), never its value.
- Don't paste API keys, tokens, connection strings, or passwords into node
  labels, descriptions, or purpose fields.
- The same applies to exports - a `CLAUDE.md` or `AGENTS.md` lands in your repo,
  so keep it free of secrets too.

  A `CLAUDE.md` committed to a public repo is public. Keep architecture exports
  in private repos unless the map is meant to be public.

## Grant the least access needed

Roles are assigned **per workspace**. Default to the smallest role that works:

- **Viewer** - read-only. Use this for stakeholders who just need to see the map.
- **Editor** - can edit maps but not invite or manage billing.
- **Admin** - full edit, and can invite Editors/Viewers into open (already-paid)
  seats. Can't change roles, remove members, or manage billing.
- **Owner** - full workspace control: member management, billing, Stripe payouts,
  paid-template listing, and verification requests.

Editors and Admins occupy a paid seat (Viewers are free), so invite people deliberately and
**remove members when they leave** - removing a member frees their seat and revokes
their access immediately. See [Roles & permissions](/docs/team-billing).

## Share deliberately

- Keep maps **workspace-only** (private) unless they're genuinely meant to be public.
- Publish a map only when you intend it to be visible to anyone with the link;
  for internal review, keep it inside the workspace instead.
- Review your **public profile** before publishing - only maps you mark public
  appear there.
- Unpublish or rotate share links you no longer need.

## Strengthen sign-in

- Turn on [two-factor authentication](/docs/security/two-factor) - it is the
  single biggest upgrade to your account's security.
- Sign in with **GitHub** so your Canopy access inherits GitHub's protections,
  including its MFA.
- Keep the email tied to your account secure - password reset links are sent there.

- The [API](/docs/api) and [MCP server](/docs/mcp) act with your workspace's
  permissions - treat any access token like a password.
- Store tokens in environment variables or a secrets manager, never in code or a
  map.
- Rotate tokens periodically and revoke any you suspect are exposed.

## Where to go next

- [Two-factor authentication](/docs/security/two-factor) - set it up in a couple of minutes.
- [Data & privacy](/docs/security/data-privacy) - what is stored and who can read it.
- [Report a vulnerability](/docs/security/report-a-vulnerability) - if you find a hole, tell us.


---

# Source: https://canopy.8starlabs.com/docs/security/data-privacy

export const metadata = {
  title: "Data & privacy | Canopy Docs",
  description:
    "What Canopy stores, how workspaces are isolated with Row-Level Security, encryption in transit and at rest, and what public vs private means.",
  alternates: { canonical: "/docs/security/data-privacy" },
};

# Data & privacy

What Canopy keeps, who can see it, and what changes the moment you publish.

## What Canopy stores

| Data      | What it includes                         | Visibility                           |
| --------- | ---------------------------------------- | ------------------------------------ |
| Account   | Name, email, avatar, auth provider       | You only                             |
| Workspace | Name, org, plan, currency, public handle | Workspace members                    |
| Maps      | Nodes, edges, metadata, monthly cost     | Workspace members (unless published) |
| Topology  | Your saved workspace layout              | Workspace members                    |
| Billing   | Plan & seat counts - **not** card data   | You · Stripe                         |

Canopy stores the **architecture** you put on the canvas. It is not a secrets
manager - see [Best practices](/docs/security/best-practices) for what _not_ to put in a map.

## Isolation with Row-Level Security

Every table is protected by Postgres **RLS**. Policies tie each row to its owner
or workspace, so queries can only ever return rows you're entitled to - the
database enforces this independently of the application code.

Public reads (public profiles and published maps) go through dedicated,
read-only database functions that return **only** explicitly-public data and
never expose private maps, cost, or member details.

## Encryption

- **In transit** - HTTPS/TLS on every request, including the API and MCP server.
- **At rest** - data is encrypted at rest by our managed Postgres provider.

## Public vs private

- **Maps are private by default.** A map is only ever visible outside your
  workspace if you publish it (private share links are coming soon).
- **Public maps** are clearly marked and appear on your public profile at
  `canopy.8starlabs.com/w/<handle>`. Free public maps carry a small "Made with Canopy" badge.

## Private share links  

Private share links (coming soon) will let you share a single map with people
outside the workspace without making it public. Until then, sharing is either
**workspace-only** (members) or **public**.

  Before you publish a map or profile, double-check it contains nothing internal
  - once a map is public, anyone with the link can view it until you unpublish
  it.

## Data ownership & deletion

Your data is yours. You can edit or delete maps at any time, and removing a
workspace removes its maps. Exports (PNG, Markdown, CLAUDE.md, AGENTS.md) are
plain files you own and control once downloaded.

## Where to go next

- [Best practices](/docs/security/best-practices) - habits that keep a map safe to share.
- [Two-factor authentication](/docs/security/two-factor) - the strongest control on your own account.


---

# Source: https://canopy.8starlabs.com/docs/security/report-a-vulnerability

export const metadata = {
  title: "Report a vulnerability | Canopy Docs",
  description:
    "How to report a security issue in Canopy, what we commit to in return, and the testing we ask you to avoid.",
  alternates: { canonical: "/docs/security/report-a-vulnerability" },
};

# Report a vulnerability

We take security seriously and welcome reports from the community. If you've
found a vulnerability in Canopy, please tell us before disclosing it publicly.

## How to report

Email **security@8starlabs.com** with:

- A description of the issue and its potential impact.
- Steps to reproduce (a proof-of-concept helps).
- The affected URL, endpoint, or area of the product.

If you'd rather not email, use [support](/support) and mark it as a security
report.

  Give us a reasonable window to investigate and ship a fix before sharing
  details publicly. We'll keep you updated on our progress.

## Our commitment

- We'll acknowledge your report promptly and keep you informed as we work.
- We won't pursue legal action against good-faith research that respects user
  privacy, avoids data destruction, and doesn't degrade the service.
- We're happy to credit you once a fix has shipped, if you'd like.

## Please avoid

- Accessing, modifying, or deleting data that isn't yours.
- Running automated scans that degrade the service for others.
- Social engineering, phishing, or physical attacks against our team or users.

Thank you for helping keep Canopy and its users safe.


---

# Source: https://canopy.8starlabs.com/docs/security/two-factor

export const metadata = {
  title: "Two-factor authentication | Canopy Docs",
  description:
    "Add a second step at sign-in with an authenticator app: setup, recovery codes, what to do if you lose your phone, and how Canopy enforces it in the database.",
  alternates: { canonical: "/docs/security/two-factor" },
};

# Two-factor authentication

Two-factor authentication (2FA) adds a second step at sign-in: a 6-digit code
from an **authenticator app** on your phone. Even if someone learns your
password, they cannot get in without your device.

Turn it on in **Account settings → Sign-in methods → Two-factor
authentication**.

## Setting it up

1. Click **Set up**. Canopy shows a QR code.
2. Scan it with an authenticator app - **Google Authenticator**, 1Password,
   Authy, or Apple Passwords all work. (Can't scan? Type the setup key shown
   underneath into your app by hand.)
3. Enter the 6-digit code your app displays to confirm it works.
4. **Save your recovery codes.** They are shown once - see below.

From then on, every sign-in asks for the current code, **including sign-ins with
GitHub**. Otherwise "Sign in with GitHub" would be a way around your own setting.

  Your app is not receiving a message - it is computing the same number Canopy
  computes, from a shared key and the current time. Nothing to intercept, and it
  works offline. Codes change every 30 seconds; if yours keeps being rejected,
  check that your phone's clock is set automatically.

## Recovery codes

When you turn 2FA on, Canopy issues **10 single-use recovery codes**. They are
your way back in if you lose your phone, so save them somewhere safe and offline

- a password manager, or printed and put away.

- Each code works **once**.
- Using one **signs you in and turns 2FA off**, so you can set up a new
  authenticator app. It is a reset, not a bypass - there is no way to produce a
  valid app code without the device.
- **Redeeming a code retires the whole sheet.** When you set two-factor up again
  you get ten brand-new codes, and the old page stops working. So the ten are
  redundancy against a code that is lost, damaged or mistyped - not ten separate
  recoveries.
- Anyone holding a code can do the same, so treat them like passwords.
- You can issue a fresh set at any time from the same card ("Regenerate"). The
  old ones stop working immediately.

Canopy stores only one-way hashes of your codes, so we cannot show them to you
again - only replace them. If you lose both your phone and your codes,
[contact support](/support).

## Lost your phone?

On the code screen, choose **Lost your device?** and enter a recovery code. That
signs you in, removes the old authenticator, and prompts you to enroll a new one.

## Turning it off

Turn 2FA off from the same settings card. It asks for your password first, and
we email you either way - a change this significant should never happen silently,
and that email is your signal if it wasn't you.

  Canopy deliberately does **not** offer emailed sign-in codes. Password resets
  already run through your inbox, so a mailbox is already enough to get into an
  account - an emailed second factor would be a second lock keyed to the same
  door. An authenticator app is a genuinely separate factor, and it is the one
  security standards (NIST SP 800-63B) actually recognise.

## How it's enforced

The second factor is not just a screen you have to get past. Once you enroll,
your session carries a signed assurance level, and Canopy's **database policies**
require it: a session that skipped the code can read nothing, even if someone
bypassed the app entirely and queried the API directly.

## Where to go next

- [Best practices](/docs/security/best-practices) - including how to protect API and MCP tokens.
- [Accounts](/docs/accounts) - sign-in methods, handles, and account deletion.


---

# Source: https://canopy.8starlabs.com/docs/team-billing

export const metadata = {
  title: "Roles & permissions | Canopy Docs",
  description:
    "Understand Canopy workspace roles, paid seats, viewers, invitations, permissions, and access management for teams.",
  alternates: { canonical: "/docs/team-billing" },
};

# Roles & permissions

Access in Canopy is granted **per workspace**. The same person can be an Admin
in one workspace and a Viewer in another. Manage everyone under **Workspace
settings → Members**. See [Accounts & workspaces](/docs/accounts) for how
workspaces relate to accounts.

## Permissions by role

| Permission                            | Owner | Admin | Editor | Viewer |
| ------------------------------------- | :---: | :---: | :----: | :----: |
| View maps, nodes & cost               |   ✓   |   ✓   |   ✓    |   ✓    |
| Export (CLAUDE.md, PNG, Markdown…)    |   ✓   |   ✓   |   ✓    |   ✓    |
| Edit maps, nodes & edges              |   ✓   |   ✓   |   ✓    |   -    |
| Share maps (public or private link)   |   ✓   |   ✓   |   ✓    |   -    |
| Invite Editors & Viewers (open seats) |   ✓   |   ✓   |   -    |   -    |
| Change roles, remove, invite Admins   |   ✓   |   -   |   -    |   -    |
| Grow the seat pool                    |   ✓   |   -   |   -    |   -    |
| Manage billing & plan                 |   ✓   |   -   |   -    |   -    |
| Manage API tokens                     |   ✓   |   -   |   -    |   -    |
| Stripe Connect / payout setup         |   ✓   |   -   |   -    |   -    |
| Sell paid templates                   |   ✓   |   -   |   -    |   -    |
| Request workspace verification        |   ✓   |   -   |   -    |   -    |
| Transfer ownership                    |   ✓   |   -   |   -    |   -    |
| **Seat cost**                         | Paid  | Paid  |  Paid  |  Paid  |

## Role notes

- **Owner** - one per workspace. Manages members (invite any role, role changes,
  removal), the seat pool, billing, Stripe Connect payouts, API tokens, paid
  [template listings](/docs/marketplace/selling), and [workspace verification](/docs/marketplace/trust).
  Can [transfer ownership](/docs/team-billing/subscriptions#transferring-ownership)
  to any member (Members → ⋯ → **Transfer ownership**); the previous owner
  becomes an Admin.
- **Admin** - full edit access (create, edit, and delete maps). **Can invite
  Editors and Viewers into open (already-paid) seats** and cancel pending
  invites. Cannot invite other Admins, change roles, remove members, transfer
  ownership, grow the paid seat pool, or manage billing, Stripe, API tokens, or
  selling - those stay with the Owner. (Inviting into a full pool would add a
  paid seat, so that's the Owner's call: ask them to add a seat first.)
- **Editor** - create and edit [maps](/docs/maps); can share publicly. No member
  management or billing access.
- **Viewer** - read-only. Can open maps, browse [nodes](/docs/maps/nodes) and
  [Intel](/docs/operate/intel), and use [exports](/docs/maps/exports), but cannot
  edit anything.

## Viewers are free - editors are a seat

Only the roles that can **change** something cost money. Owner, Admin and Editor
each occupy one paid seat. **Viewers are free and unlimited on every plan**,
including Free.

That means you can invite as many read-only teammates, clients or stakeholders as
you like without touching your bill. See
[Subscriptions & seats](/docs/team-billing/subscriptions) for the full billing model.

  On the **Free** plan you are the only editor, but you can still invite
  **unlimited viewers**. Upgrade to Pro to add Editors and Admins.

## Changing a role

Open **Workspace settings → Members**, pick a member from the ⋯ menu, and
choose a new role. Promoting a Viewer to Editor or Admin **takes a paid seat**
(and grows the pool if none is open); demoting someone to Viewer frees one.


---

# Source: https://canopy.8starlabs.com/docs/team-billing/free-trial

export const metadata = {
  title: "Free trial | Canopy Docs",
  description:
    "Learn how the Canopy Pro free trial works, when billing starts, how to cancel, and what happens when the trial ends.",
  alternates: { canonical: "/docs/team-billing/free-trial" },
};

# Free trial

Canopy Pro comes with a **7-day free trial**. You add a card up front, get full
Pro for the week, and aren't charged until the trial ends. Manage it under
**Workspace settings → Plans & billing**.

  The trial is **one per person, ever** - tracked by a private, peppered hash of
  your email that survives account deletion. Deleting and re-signing up does not
  grant a second trial.

## What you get

During the trial your workspace is on **Pro**, so every Pro limit applies:

- **150 nodes per map** (vs 25 on Free) and **up to 50 maps**.
- **Invite members** (each is a paid seat once billing starts).
- **Figma / Notion export**, the **MCP server**, and **API tokens**.

Everything in Pro works during the trial - **with one exception** below.

## Selling is paid-only

**You cannot sell paid templates during the trial.** Listing a map for sale -
and setting up Stripe Connect payouts - unlocks only once your trial **converts
to a paid plan**. This is because Stripe charges a monthly fee per active
connected (payout) account, so we enable selling for paying customers only.

You can still do everything else: build maps, share them publicly, and even buy
other people's templates. Only _listing your own maps for sale_
waits until the first payment clears. See [Selling templates](/docs/marketplace/selling).

## Start selling sooner - end the trial early

Don't want to wait until day 7? Start your paid plan **now** from **Workspace
settings → Plans & billing → "Start paying now"** (the button also appears on
the **Sales** page while you're on a trial). This:

- charges the card on file **today** (not on day 7),
- converts the subscription from trial to **paid** (`active`), and
- **unlocks selling** the moment that charge clears.

  "Start paying now" keeps the same subscription - you just begin paying early
  (the remaining trial days are forgone). If that first charge **fails**, you
  aren't charged and your workspace **drops to Free** - there's no grace period
  for a first payment that never cleared. Update your card and try again.

## After the trial ends

On day 7, Stripe charges the card you added:

- **Payment succeeds → you're on paid Pro.** The subscription becomes active,
  billing begins on the normal cycle, and selling unlocks. Nothing else changes.
- **Payment fails → your workspace goes to Free right away.** Because the trial
  never converted, no payment ever cleared - so there's **no grace period**. The
  workspace drops to Free (your data is preserved; maps over the Free limits go
  read-only until you re-upgrade or trim them). Fix your card and upgrade again
  anytime.

  The 7-day grace period (keep Pro while you fix a card) is only for **renewal**
  failures on a plan you've **already paid for** - not a first charge. A trial
  that doesn't convert simply ends and you're on Free. See [Failed
  payments](/docs/team-billing/subscriptions#failed-payments).

## Promo codes: a longer, cardless trial

A campaign code (say "3 months free") is a **different kind of trial** from the
7-day one above, and the differences matter:

|               | 7-day trial           | Promo code                              |
| ------------- | --------------------- | --------------------------------------- |
| Card required | **Yes**, up front     | **No**                                  |
| Length        | 7 days                | Whatever the code grants                |
| Eligibility   | Once per person, ever | Per workspace, while the campaign lasts |
| At the end    | Card is charged       | Ends and returns to Free                |

Redeem one under **Plans & billing → Promo code**. That in-app box is the **only**
place a code works - Stripe's own checkout page deliberately has no promo field,
because a Canopy code carries its value in the trial length rather than as a
discount, and typing it into Stripe would apply the wrong thing.

**One code per trial.** A code can extend the standard 7-day trial (it lengthens
the trial in place rather than starting a second subscription, and can only ever
lengthen, never shorten it). It cannot stack on a trial that already came from a
code - once that's the case the redeem box disappears rather than accepting a code
it would refuse.

### When a promo trial ends

Because there's **no card on file**, nothing is charged and nothing is attempted:

- The subscription **ends** and the workspace returns to **Free**.
- **Nothing is deleted.** Maps over the Free limits go read-only until you
  re-upgrade or trim them, and members above Viewer are demoted to Viewer -
  re-upgrading restores their previous roles.
- We email you a few days before, so the end isn't a surprise.

Add a card any time during the trial to keep Pro running - that converts it into
an ordinary paid subscription, and is also what unlocks
[selling templates](/docs/marketplace/selling).

Once a promo trial has ended and the workspace is back on Free, you can redeem a
code again if the campaign is still live. Each code has its own expiry and
redemption cap, so a code may simply be used up.

## Cancelling during the trial

You can cancel anytime before day 7 from **Plans & billing** (the Stripe
portal) and you **won't be charged**. The workspace drops to **Free**
immediately - data preserved, over-limit maps read-only.

  Cancelling does **not** put your account at risk. The dormant-account clock
  starts from the **downgrade date**, not your last sign-in - so a fresh Free
  workspace always gets the full inactivity window before any dormancy notice.
  See the [Dormant Account Policy](/dormant-account-policy).


---

# Source: https://canopy.8starlabs.com/docs/team-billing/notifications

export const metadata = {
  title: "Email notifications | Canopy Docs",
  description:
    "Manage Canopy email notifications for billing, marketplace activity, workspace changes, security, and product updates.",
  alternates: { canonical: "/docs/team-billing/notifications" },
};

# Email notifications

Canopy emails you about things that matter: billing, your template sales and
purchases, workspace changes, and the occasional product update. You control
which of these you receive from **Account settings → Email notifications**.

## Emails we send

Grouped by the category you can toggle:

### Billing & plan

- **Payment failed** - a charge on your workspace's card didn't go through; you
  have a 7-day grace window to update it before the workspace drops to Free.
- **Workspace downgraded to Free** - sent when a subscription is cancelled or the
  grace window lapses. Your data is preserved; over-limit maps and extra members
  become read-only / lose access until you re-upgrade.

### Template sales (sellers)

- **You made a sale** - someone bought one of your paid templates.
- **A sale was refunded** - a buyer was refunded; the amount (including
  Canopy's platform fee) is reversed from your payout and the buyer's license is
  revoked.
- **A buyer opened a dispute** - a chargeback was filed; the buyer's access is
  paused while it's open. Contest it in your Stripe dashboard.

### Your purchases (buyers)

- **Your purchase was refunded** - a template you bought was refunded (or a
  dispute resolved). Your access to it is removed.

### Workspace & members

- **You've been invited to a workspace**, added to one, removed from one, or had
  your role changed.

### Product updates

- Occasional, low-volume news about new Canopy features.

## Managing your preferences

Go to **Account settings → Email notifications**, toggle any category on or off,
then choose **Save changes**. Preferences apply to every workspace you belong to
(email preferences are tied to **your account**, not a single workspace).

  Preferences are per person. If you own several workspaces, turning off
  "Billing & plan" silences billing emails for all of them.

## Always-on emails

A few emails are essential and are **always sent**, regardless of your
preferences:

- **Security & sign-in** - magic links, password resets, email-change
  confirmations, and any change to two-factor authentication (turned on, turned
  off, or a recovery code used).
- **Account closure & dormancy** - closure confirmations, the pre-deletion
  warning, and dormant-account notices.
- **Verification decisions** - the outcome of a workspace verification request.
- **Ownership transfers** - sent to both the outgoing and incoming owner. Being
  handed, or stripped of, a workspace decides who is billed for it and who can
  delete its data, so this one is not covered by the **Workspace & members**
  toggle above.

These can't be turned off because missing them could cost you access to your
account or data.


---

# Source: https://canopy.8starlabs.com/docs/team-billing/subscriptions

export const metadata = {
  title: "Subscriptions & seats | Canopy Docs",
  description:
    "Learn how Canopy subscriptions work per workspace, how Owner, Admin, and Editor seats are billed, and how Free viewers work.",
  alternates: { canonical: "/docs/team-billing/subscriptions" },
};

# Subscriptions & seats

Canopy is billed **per workspace**, by seat - each workspace is its own
subscription. **Owner, Admin and Editor occupy a paid seat; Viewers are free and unlimited.** Manage
everything under **Workspace settings → Plans & billing**. For what each role can
do, see [Roles & permissions](/docs/team-billing).

  You can keep **up to two free workspaces** at a time. To create more, upgrade
  one to Pro - that frees a slot. Paid workspaces are unlimited, since each is
  billed on its own.

## Plans

|                                   | Free           | Pro                                     |
| --------------------------------- | -------------- | --------------------------------------- |
| Price                             | $0             | **$10 / seat / mo** (~$8 billed yearly) |
| Workspaces                        | 2 free         | Unlimited (each paid)                   |
| Maps                              | 2              | 50                                      |
| Nodes per map                     | 25             | 150                                     |
| Paid seats (Owner/Admin/Editor)   | 1 (the owner)  | Unlimited · $10 each                    |
| Viewers (read-only)               | Unlimited free | Unlimited free                          |
| Invite Viewers                    | ✓              | ✓                                       |
| Invite Editors / Admins           | -              | ✓                                       |
| Private share links               | -              | Coming soon                             |
| Intel: cost and billing           | ✓              | ✓                                       |
| Export PNG / Markdown / CLAUDE.md | ✓ (with badge) | ✓ (with badge, always)                  |
| Export Figma / Notion             | -              | ✓                                       |
| MCP server                        | -              | ✓                                       |
| Buy templates                     | ✓              | ✓                                       |
| Sell paid templates               | -              | ✓                                       |
| Canopy AI generation              | -              | Coming soon                             |
| Support                           | Community      | Priority email                          |

## How seats are charged 

Pro is a **per-seat subscription**. You pay for a **seat pool** that you control
from **Plans & billing** - a slider sets how many seats to pay for, with a minimum
of one (the Owner) and a maximum of 50 (beyond that, [talk to support](/support)).
Members _occupy_ seats; the pool can sit **above** your head-count, so you can
pre-buy empty seats and invite into them later.

- **Adding an Editor or Admin** - invite them; existing Canopy accounts accept
  from their dashboard invite inbox, new accounts join from the magic-link invite
  after they sign up or sign in. If you have an open seat, the accepted member
  fills it at no extra charge. If you're already full, the pool **grows by one,
  prorated** for the rest of the current period.
- **Adding a Viewer** - free, on every plan, with no limit. A Viewer invite never
  takes a seat and is never blocked, even on Free.
- **Changing a role** - moving between paid roles (Admin ↔ Editor) never changes
  your bill. Promoting a **Viewer** to Editor or Admin **does take a paid seat**
  (growing the pool if none is open); demoting someone to Viewer frees one.

  **Who can invite:** the Owner can invite any role and grow the pool.
  **Admins** can invite Editors and Viewers too, but only into an **open seat
  you've already paid for** - they can't grow the pool (that's a charge, so it
  stays the Owner's call). Pre-buy a seat with the slider and an Admin can fill
  it. See [Roles & permissions](/docs/team-billing).

  Pricing is in USD. Switching to annual shows the exact yearly discount per
  seat. Change the cadence anytime from Plans & billing.

## Removing members & reducing seats 

Removing a member and reducing your bill are **two separate steps** - by design,
so you can free a seat for someone else without a billing change.

- **Remove a member** - they lose access immediately (their own account and
  personal data are untouched). The seat becomes an **open paid seat** - your bill
  **doesn't drop yet**; you keep that seat ready to invite someone new.
- **Reduce the pool** - to actually pay less, lower the seat slider in Plans &
  billing. Stripe applies the unused time as a **credit on your next invoice** (no
  immediate refund), and the smaller charge starts next cycle.
- **The floor** - you can't pay for fewer seats than people who currently have
  access. To go below your member count, **remove members first**, then slide the
  pool down.
- You can't remove the **Owner** - [transfer ownership](/docs/team-billing#the-four-roles)
  first.

  Adding seats is **charged immediately** (a prorated invoice is issued and paid
  on the spot); removing seats **credits** your next invoice. Nothing is ever
  deleted when seats change.

### What "credited to your next invoice" means

A **credit** is money held on your account that is automatically subtracted from
your **next** invoice. It is **not** a refund to your card, and **nothing is
charged on the day you remove the seat**.

When you lower the pool, Stripe works out how much of that seat you'd already
paid for but won't use, and stores it as a credit. Your next renewal invoice is
reduced by that amount.

**Worked example - yearly plan, 3 seats → 2 seats**

You pay ~$8/seat/mo billed yearly, so 3 seats = **$288/yr**. Six months in, you
drop to 2 seats:

|                                 |                                    |
| ------------------------------- | ---------------------------------- |
| New yearly price                | 2 × $96 = **$192.00**              |
| Unused time on the removed seat | 6 months × $8 = **−$48.00** credit |
| Charged today                   | **$0.00**                          |
| Your next renewal invoice       | $192.00 − $48.00 = **$144.00**     |

After that first reduced invoice the credit is used up, and you simply pay
**$192.00/yr** going forward.

**Monthly works the same way:** on 3 seats at $10/seat/mo ($30/mo), dropping to 2
seats halfway through the month leaves roughly **$5** unused on that seat, so your
next invoice is about **$20 − $5 = $15**, then $20/mo after that.

  A credit can only be spent on **future invoices for that workspace**. If you
  cancel before the next invoice, unused credit isn't paid out as cash - so if
  you're planning to cancel anyway, cancelling at period end (and keeping the
  time you already paid for) is usually the better move.

### Adding seats back in the same period

If you lowered the pool and then raise it again before the next invoice, the two
prorations **net against each other** - so putting a seat back usually costs
little or nothing.

**Worked example - monthly, 3 → 1 → 2 seats mid-period**

|                                          |                              |
| ---------------------------------------- | ---------------------------- |
| Dropping 3 → 1 halfway through the month | ~**$10** credit held         |
| Raising 1 → 2 the same week              | ~**$5** proration owed       |
| Charged today                            | **$0.00** (credit covers it) |

The confirm dialog shows the **actual** amount, asked of Stripe for your exact
subscription at that moment, rather than assuming a charge - so if credit covers
the change it will say "No charge today" and mean it.

## Downgrading to Free 

Cancel Pro from **Plans & billing → Manage** (the secure Stripe portal). Cancellation
takes effect **at the end of the current period** - you stay on Pro until then (billing
shows the date it will cancel), with no further charges. You keep the time you already
paid for; there's no immediate-forfeit option (and transferring a workspace no longer
requires cancelling first - see [transferring ownership](#transferring-ownership)).

**Changed your mind?** **Resume** before the period ends (**Manage → Renew**) and
nothing is lost - same billing cycle, same prepaid time, the next charge is just your
normal renewal. If you instead let it lapse to Free and re-subscribe later, you simply
used the time you'd paid for and start a fresh period - no double charge either way.

When the period ends, the workspace moves to **Free** and these limits apply again:
solo (1 seat, no invites), 2 maps, 25 nodes per map, no Figma / Notion export,
and no MCP.

  **Your data is always preserved - a downgrade never deletes anything.**
  Because Free is solo, teammates beyond the Owner lose access until you
  re-upgrade (their accounts and the maps stay intact). Maps and workspaces over
  the Free limits become **read-only** until you trim them or upgrade again.

If dropping to Free would leave you over the cap of **two free workspaces** per
person, the surplus one stays **read-only** until you re-upgrade it, delete it, or
free a slot. Your two oldest free workspaces stay editable; any newer surplus ones
become read-only (still readable). Re-upgrading or deleting a free workspace
unlocks the rest automatically.

## What counts as a seat

| Role   | Seat                 |
| ------ | -------------------- |
| Owner  | Paid (always 1)      |
| Admin  | Paid                 |
| Editor | Paid                 |
| Viewer | **Free** (unlimited) |

See [Roles & permissions](/docs/team-billing) for what each role can do.

## Managing payments 

Billing is **owner-only**. Open **Plans & billing → Manage** to launch the secure
Stripe Customer Portal, where you can:

- **Update the card** on file (or add one).
- **Change the billing email** and contact details.
- **Download invoices and receipts** for the subscription.
- **Cancel or change** the plan and seat count.

### Removing a card

Stripe requires a payment method on a **renewing** subscription (otherwise the next
charge would just fail), and the Stripe portal won't let you remove your only card
while the plan looks active. So Canopy adds a **Remove** action next to the card in
Plans & billing that works once there's **no upcoming charge**:

- It's **enabled** when the plan is set to **cancel at period end** (or there's no
  active subscription) - you keep Pro through the time you already paid for, but
  nothing renews, so the card is safe to remove.
- It's **disabled while the plan is still renewing** - cancel first (**Manage →
  Cancel**, which keeps your prepaid time), then the Remove action lights up.

So to take your card off entirely: **cancel at period end, then Remove.** You stay
on Pro until the period ends, then drop to Free. (Adding a card again anytime
restores paid renewals.) This is also what lets a departing owner clear their card
before [transferring ownership](#transferring-ownership) - which works on Pro
without cancelling, since the new owner can replace the card themselves.

There are two separate ledgers, and we keep them apart on purpose:

- **Subscription invoices** (the per-seat Pro plan) live in **Stripe** - we link
  you to the hosted portal rather than mirroring them.
- **Template marketplace purchases** are recorded in Canopy and shown **in-app**
  under Plans & billing (buyers) and on the **Sales** page (sellers).

## Transferring ownership 

A workspace has exactly one Owner, who can hand it to another **existing member**.
The previous Owner stays on as an Admin.

  Transfer works on **Free or Pro** - no need to cancel first. On a paid
  workspace the **subscription stays active** and **your card stays on file**
  until the new owner adds **their own** payment method (they get billing access
  the moment they become Owner). Until they do, **your card is charged at the
  next renewal**, so ask them to update billing soon after the hand-off. Stripe
  Connect (payouts) is per-person and never transfers either way.

### Eligibility

The transfer is validated **before anything changes**, and is rejected with a
message if a rule isn't met:

- The recipient is already a **member** of the workspace.
- **For a Free transfer only:** the recipient owns **fewer than two free
  workspaces** - taking this one over must not push them past the
  [free-workspace cap](/docs/accounts#free-workspaces). Paid workspaces have no
  such limit.

### How to transfer

1. Make sure the person is already a **member** - invite them first if not.
2. Go to **Dashboard → Settings → Workspace → Members**.
3. Open the member's **⋯** menu, choose **Transfer ownership**, type the
   workspace name to confirm, and transfer.
4. **If on Pro:** the new owner opens **Plans & billing** and adds their own
   card to take over billing from yours.

Only the current Owner sees this action. Ownership moves immediately - they
become Owner, you become an Admin (you keep edit access; billing, members, and
payouts move to them). Both of you get a confirmation email.

### What changes

| Area                     | What happens on transfer                                                                                   |
| ------------------------ | ---------------------------------------------------------------------------------------------------------- |
| Roles                    | New owner → **Owner**; you → **Admin**.                                                                    |
| Control                  | Members, API tokens, payout setup, listings, and verification move to the new owner.                       |
| Billing / card           | None carries over - the workspace is on **Free** at transfer, so there's no subscription or card to move.  |
| Stripe Connect (payouts) | Per-person and KYC-bound; **never transfers**. If the new owner will sell, they onboard their own Connect. |
| Maps & data              | Unchanged. Nothing is deleted or moved.                                                                    |
| Existing buyers          | Unaffected - past purchases keep working.                                                                  |

**Trade-off:** between canceling and the new owner re-upgrading, the workspace
sits on **Free limits** (over-limit maps read-only, extra members lose access,
paid listings pause) until they re-subscribe. This is the deliberate cost of
guaranteeing a clean billing hand-off. For payout details see [Selling
templates](/docs/marketplace/selling).

## Canopy AI usage  

Canopy AI isn't available yet. We'll share the details closer to launch.

## Failed payments

If a **renewal** charge fails on a plan you've already paid for, the workspace
enters a **7-day grace period** (you keep Pro, and Plans & billing shows the grace
deadline). Update your card from **Manage** and it returns to active. If the payment
is never resolved, Stripe stops retrying and cancels the subscription, which then
follows the **same path as a downgrade to Free** above - data preserved, maps locked
read-only, nothing deleted.

The grace period only applies once you've successfully paid at least once. A
**first** charge that never clears - a [free trial](/docs/team-billing/free-trial)
that doesn't convert, or "Start paying now" with a bad card - has no grace: the
workspace simply drops to Free immediately (data still preserved).

## Deleting a workspace or account

Deletion is the only action that removes data, and it's always explicit (the
Danger zone in settings). Removing a _member_ never deletes their account.

**Deleting a workspace** is owner-only. Cancel its subscription first (a paid
workspace must be back on **Free** to delete), and you **can't delete your only
owned workspace** - keep or create another first. If the workspace has maps other
people have **bought**, it's **archived** (not destroyed) so those buyers keep
their copies; with no buyers it's permanently deleted along with its maps.

**Closing your account** cancels every subscription you own automatically, then
**suspends** the account for a **30-day reversible** window before permanent
deletion. Workspaces that own bought templates are **orphaned and archived**
(buyers keep access); everything else is erased. See [closing your
account](/support/account-deletion-timeline).

## Currency

All cost and billing figures are shown in your **workspace currency**, indicated on
the [Intel](/docs/operate/intel) page.
