{"openapi":"3.1.0","info":{"title":"Dealboard API","version":"1.2.0","description":"The public REST API for Dealboard — programmatically read and\nwrite your workspace data. Built for humans and AI agents.\n\n**Authentication.** Send your API key as `Authorization: Bearer dlb_live_…`.\nCreate one in Workspace Settings → API Keys.\n\n**Scopes.** OAuth tokens carry exactly the scopes the user consented to;\nAPI keys hold the full set (the creator's workspace role is the gate).\nEffective permission is always `granted-scopes ∩ role`. Available scopes:\n\n- `deals:read` — List, search, and read deals plus their notes and activity.\n- `deals:write` — Create deals, update fields, move stages, and add notes.\n- `leads:read` — Read inbound leads and their enrichment payloads.\n- `leads:write` — Triage leads, promote them to deals, and write enrichment back.\n- `pipeline:read` — Read pipeline summaries, weighted pipeline, report cards, and export PNGs.\n\n**Conventions.** Money is in cents + currency. Timestamps are ISO 8601 UTC.\nIDs are raw UUIDs. Lists are cursor-paginated.\n\n**Idempotency.** Mutating requests accept an `Idempotency-Key` header — replays\nreturn the original response without re-executing.\n\n**Rate limit.** 600 requests/minute/key AND 2000 requests/minute/workspace\n(the aggregate across all of a workspace's keys). Whichever trips first\nreturns 429 with a `Retry-After` header. Back off and retry after the\nindicated delay.\n\n**Payload limit.** Request bodies over 1 MB are rejected with 413.\n\n**Plan limits.** Create endpoints honor the workspace's plan — e.g. a Free\nworkspace that has hit its active-deal or board cap gets 403\n`plan_limit_exceeded` (with the limit name in `error.meta.limit`), exactly\nlike the in-app UI. Upgrade or free up room, then retry.\n\n**Stages (May 2026 release):** the `stage` field on deal responses is an object\n`{id, name, region, isAnchor}`, not a bare enum string. Write paths take\n`stageId` (UUID). List a board's stages via `GET /v1/boards/{id}/stages`.\n\n## Leads vs Deals\n\nTwo top-level objects with different jobs:\n\n- **Lead** — raw inbound interest from your website form, hosted contact page,\n  or inbound email address. Holds the submitter's contact info, message, and\n  any enrichment your qualifier writes. Lives in the Leads inbox until a\n  human (or AI agent) triages it.\n- **Deal** — an opportunity in your sales pipeline. Has a stage, owner, amount,\n  notes, proposals/contracts, etc.\n\nA Lead becomes a Deal via `POST /v1/leads/{id}/promote`. The Lead row stays\naround with `triageStatus=\"promoted\"` and `dealId` pointing at the new Deal,\nso you keep the full inbound provenance forever.\n\nTriage states: `new`, `contacted`, `promoted`, `junk`, `bad_fit`, `stale`. Only\n`promoted` creates a Deal row; the rest are lifecycle markers on the Lead.\n\n## Lead enrichment\n\nExternal qualifiers (Clearbit / Apollo / your own model) write to\n`PATCH /v1/leads/{id}` with an `enrichment` payload. Each PATCH **merges** the\ntop-level keys you send into the existing enrichment object and appends a\nhistory snapshot — so the audit trail is intact. Dealboard renders whatever's\nthere but imposes no schema; common fields are `score`, `verdict`, `body`,\n`attachments`.\n\nInclude a `contact` object whenever you resolve the person — `name`, `title`,\n`email`, `phone`, and especially **`linkedin`** (a full profile URL). When the\nlead is promoted to a deal, these gap-fill the deal's primary contact, so the\nLinkedIn you found lands right on the deal — the same way you'd expect phone or\nemail to. (If you only mention LinkedIn in `body`, Dealboard still picks up the\nfirst linkedin.com/in/ URL it finds.)","contact":{"name":"Dealboard","email":"support@getdealboard.com","url":"https://getdealboard.com/developers"}},"servers":[{"url":"https://app.getdealboard.com","description":"Production"}],"security":[{"BearerApiKey":[]}],"components":{"securitySchemes":{"BearerApiKey":{"type":"http","scheme":"bearer","bearerFormat":"dlb_live_<43-char-base62>","description":"API keys are workspace-scoped and inherit the creator's role. Generate in Workspace Settings → API Keys."},"BearerOAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"OAuth 2.1 access token (audience-bound to the /mcp resource). Obtained via the authorization-code + PKCE flow — discovery at /.well-known/oauth-authorization-server. Used by the remote MCP endpoint (/mcp) and accepted on /v1/* as well; effective permission is the granted scopes intersected with the user's workspace role."}},"schemas":{"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","code","message","request_id"],"properties":{"type":{"type":"string","description":"Coarse machine-readable category.","enum":["authentication_error","permission_error","invalid_request_error","not_found","rate_limit_error","conflict_error","unsupported_media_type","method_not_allowed","api_error"]},"code":{"type":"string","description":"Finer-grained error code."},"message":{"type":"string","description":"Human-readable English explanation."},"request_id":{"type":"string","description":"The Dealboard-Request-Id assigned to this request. Quote this when filing a bug."}}}}},"Stage":{"type":"object","required":["id","name","region","isAnchor"],"properties":{"id":{"type":"string","description":"Stage id (per-board). Use this in `stageId` / `toStageId` / `stageFilter` write paths."},"name":{"type":"string","description":"User-facing stage name. Editable per-board."},"region":{"type":"string","enum":["early","anchor","late","won","dead"],"description":"Semantic bucket: early/late are 1..6 user-customizable stages each; anchor is the single per-board \"things got serious\" milestone; won/dead are immutable terminals."},"isAnchor":{"type":"boolean","description":"Convenience flag — `region === \"anchor\"`."}}},"Deal":{"type":"object","required":["id","boardId","name","company","amountCents","currency","stage","ownerId","ownerName","labels","primaryContact","keyTerms","createdAt","updatedAt","stageEnteredAt","commitmentAt","wonAt","deadAt"],"properties":{"id":{"type":"string","format":"uuid"},"boardId":{"type":"string","format":"uuid"},"name":{"type":"string"},"company":{"type":["string","null"]},"amountCents":{"type":["integer","null"],"description":"Deal value in minor currency units (cents). No floats."},"currency":{"type":"string","enum":["USD","EUR","GBP","CAD","AUD","JPY","INR"]},"stage":{"$ref":"#/components/schemas/Stage"},"ownerId":{"type":"string","format":"uuid"},"ownerName":{"type":["string","null"]},"labels":{"type":"array","items":{"type":"string"}},"primaryContact":{"type":"object","required":["name","title","email","phone","linkedin"],"properties":{"name":{"type":["string","null"]},"title":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"linkedin":{"type":["string","null"]}}},"keyTerms":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"stageEnteredAt":{"type":"string","format":"date-time"},"commitmentAt":{"type":["string","null"],"format":"date-time"},"wonAt":{"type":["string","null"],"format":"date-time"},"deadAt":{"type":["string","null"],"format":"date-time"}}},"Board":{"type":"object","required":["id","teamId","name","currency","defaultDealAmountCents","createdAt"],"properties":{"id":{"type":"string","format":"uuid"},"teamId":{"type":"string","format":"uuid"},"name":{"type":"string"},"currency":{"type":"string","enum":["USD","EUR","GBP","CAD","AUD","JPY","INR"]},"defaultDealAmountCents":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}},"Note":{"type":"object","required":["id","dealId","authorId","kind","contentText","contentJson","systemEventType","systemEventData","createdAt","editedAt"],"properties":{"id":{"type":"string","format":"uuid"},"dealId":{"type":"string","format":"uuid"},"authorId":{"type":"string","format":"uuid"},"kind":{"type":"string","enum":["manual","system_event"]},"contentText":{"type":["string","null"]},"contentJson":{"description":"Tiptap document JSON for manual notes; null for system events."},"systemEventType":{"type":["string","null"]},"systemEventData":{},"createdAt":{"type":"string","format":"date-time"},"editedAt":{"type":["string","null"],"format":"date-time"}}},"Webhook":{"type":"object","required":["id","boardId","label","urlFingerprint","provider","events","stageFilter","active","createdAt","lastDeliveredAt","lastError"],"properties":{"id":{"type":"string","format":"uuid"},"boardId":{"type":"string","format":"uuid"},"label":{"type":["string","null"]},"urlFingerprint":{"type":"string","description":"Last 4 characters of the URL. The URL itself is a secret and is never returned."},"provider":{"type":"string","enum":["slack","teams","discord","generic"]},"events":{"type":"array","items":{"type":"string","enum":["deal.created","deal.stage_changed","deal.won","lead.created"]}},"stageFilter":{"type":"array","items":{"type":"string"},"description":"Stage IDs (per-board) to filter `deal.stage_changed` events to. Empty = all stages."},"active":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"lastDeliveredAt":{"type":["string","null"],"format":"date-time"},"lastError":{"type":["string","null"]}}},"Lead":{"type":"object","required":["id","boardId","triageStatus","source","dealId","contact","enrichment","createdAt","contactedAt","promotedAt","junkedAt"],"properties":{"id":{"type":"string","format":"uuid"},"boardId":{"type":"string","format":"uuid"},"triageStatus":{"type":"string","enum":["new","contacted","promoted","junk","bad_fit","stale"],"description":"Lifecycle marker for the Lead inbox. `new` → `contacted` is recorded when a reply goes out; `promoted`/`junk`/`bad_fit` are terminal triage outcomes; `stale` is set by a daily cron if a Lead sits in `new` too long."},"source":{"type":"string","enum":["form","email"],"description":"How the Lead arrived: `form` (website/hosted form) or `email` (inbound email)."},"dealId":{"type":["string","null"],"format":"uuid","description":"Set once the Lead has been promoted to a Deal via POST /v1/leads/{id}/promote. Null otherwise."},"contact":{"type":"object","required":["firstName","lastName","email","phone","companyName","companyWebsite","message"],"description":"Submitted form values verbatim. Same shape as the form fields the workspace admin configured.","properties":{"firstName":{"type":["string","null"]},"lastName":{"type":["string","null"]},"email":{"type":["string","null"],"format":"email"},"phone":{"type":["string","null"]},"companyName":{"type":["string","null"]},"companyWebsite":{"type":["string","null"]},"message":{"type":["string","null"]},"howHeard":{"type":["string","null"],"description":"\"How did you hear about us?\" answer, when collected."},"consent":{"type":["boolean","null"],"description":"Marketing/SMS consent given on the form (null = not asked)."}}},"enrichment":{"$ref":"#/components/schemas/LeadEnrichment"},"createdAt":{"type":"string","format":"date-time"},"contactedAt":{"type":["string","null"],"format":"date-time"},"promotedAt":{"type":["string","null"],"format":"date-time"},"junkedAt":{"type":["string","null"],"format":"date-time"}}},"LeadEnrichment":{"type":"object","additionalProperties":true,"description":"Free-form qualifier payload. Common fields shown below — additional keys are preserved verbatim. `history` is server-managed; PATCH bodies cannot overwrite it.","properties":{"status":{"type":"string","enum":["queued","in_progress","complete","skipped"],"description":"Async lifecycle. Send `in_progress` when your tool claims the lead (scoring can take minutes); the inbox shows \"Scoring…\". Then `complete` (or `skipped`) with the result."},"score":{"type":"number","minimum":0,"maximum":100,"description":"Normalized 0–100 fit/ICP score. Drives the inbox badge color + sort. Send the human-readable original (e.g. \"9/10\") in `scoreDisplay`."},"scoreDisplay":{"type":"string","description":"Human-readable score as your tool renders it, e.g. \"9/10\" or \"B+\". Display-only."},"verdict":{"type":"string","description":"Short free-form recommendation/label, e.g. \"Reach out and chat\", \"Ignore\". Shown next to the score."},"body":{"type":"string","description":"Rich writeup (markdown — bold/bullets/emoji), shown on the lead detail."},"provider":{"type":"string","description":"Which tool produced this (e.g. \"hazel\", \"clearbit\")."},"contact":{"type":"object","additionalProperties":false,"description":"Enriched CONTACT — the person behind the lead. Generic across every qualifier (mirrors a deal’s own contact fields). When the lead is promoted to a deal, these GAP-FILL the deal’s primary contact (the lead’s own values win). Recommended to populate — especially `linkedin`, which lands in the deal’s LinkedIn field just like phone/email/title.","properties":{"name":{"type":"string","description":"Full name of the contact."},"title":{"type":"string","description":"Job title, e.g. \"VP of Product\"."},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"linkedin":{"type":"string","format":"uri","description":"Full https URL to the contact’s LinkedIn profile, e.g. \"https://www.linkedin.com/in/jane-doe\". Strongly recommended — it populates the promoted deal’s LinkedIn field. (If omitted, Dealboard also picks up the first linkedin.com/in/ URL it finds in `body`.)"},"website":{"type":"string","format":"uri"}}},"screenshot":{"type":"string","description":"A website/homepage screenshot for the enrichment card. PATCH a public https URL; Dealboard downloads the image and stores it permanently, so it survives even if your original URL later expires. On read, this is a durable Dealboard-hosted URL."},"screenshotSource":{"type":"string","format":"uri","description":"Read-only. The original provider URL the stored `screenshot` was ingested from."},"summary":{"type":"string"},"notes":{"type":"string"},"attachments":{"type":"array","items":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"Image URL. PATCH a public https URL; image attachments are downloaded and stored permanently (on read, `url` is a durable Dealboard-hosted URL)."},"filename":{"type":"string"},"kind":{"type":"string"},"sourceUrl":{"type":"string","format":"uri","description":"Read-only. The original provider URL this stored image was ingested from."}}}},"history":{"type":"array","items":{"$ref":"#/components/schemas/LeadEnrichmentHistoryEntry"},"description":"Append-only audit trail of every PATCH /v1/leads/{id} call. Server-managed."}}},"LeadEnrichmentHistoryEntry":{"type":"object","required":["at","by","payload"],"properties":{"at":{"type":"string","format":"date-time"},"by":{"type":"string","description":"API key id that wrote the enrichment patch."},"payload":{"type":"object","additionalProperties":true,"description":"The keys that were merged at this point in time."}}},"DealList":{"type":"object","required":["data","hasMore","nextCursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Deal"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"],"description":"Pass as `?cursor=` on the next request to fetch the next page."}}},"BoardList":{"type":"object","required":["data","hasMore","nextCursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Board"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"],"description":"Pass as `?cursor=` on the next request to fetch the next page."}}},"NoteList":{"type":"object","required":["data","hasMore","nextCursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Note"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"],"description":"Pass as `?cursor=` on the next request to fetch the next page."}}},"WebhookList":{"type":"object","required":["data","hasMore","nextCursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"],"description":"Pass as `?cursor=` on the next request to fetch the next page."}}},"LeadList":{"type":"object","required":["data","hasMore","nextCursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Lead"}},"hasMore":{"type":"boolean"},"nextCursor":{"type":["string","null"],"description":"Pass as `?cursor=` on the next request to fetch the next page."}}},"StageList":{"type":"array","items":{"$ref":"#/components/schemas/Stage"}},"Me":{"type":"object","required":["user","workspace","apiKey","auth"],"properties":{"user":{"type":"object","required":["id","email","name"],"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"name":{"type":["string","null"]}}},"workspace":{"type":"object","required":["id","name","plan"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"plan":{"type":"string","enum":["free","standard"]}}},"apiKey":{"type":["object","null"],"description":"The API key in use, or null when authenticated via OAuth.","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"}}},"auth":{"type":"object","description":"How the caller authenticated and what it may do.","required":["kind","scopes","client"],"properties":{"kind":{"type":"string","enum":["api_key","oauth"]},"scopes":{"type":"array","items":{"type":"string","enum":["deals:read","deals:write","leads:read","leads:write","pipeline:read"]},"description":"Granted scopes. API keys hold the full set (role is the gate); OAuth tokens hold exactly what the user consented to.\n\n- `deals:read` — List, search, and read deals plus their notes and activity.\n- `deals:write` — Create deals, update fields, move stages, and add notes.\n- `leads:read` — Read inbound leads and their enrichment payloads.\n- `leads:write` — Triage leads, promote them to deals, and write enrichment back.\n- `pipeline:read` — Read pipeline summaries, weighted pipeline, report cards, and export PNGs."},"client":{"type":["object","null"],"description":"The OAuth client, when kind = oauth.","properties":{"id":{"type":"string"}}}}}}}}},"paths":{"/v1/me":{"get":{"summary":"Verify your API key","description":"Returns the calling user, their workspace, and the key in use. The fastest way to confirm a key works.","tags":["Auth"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/reports/weighted-pipeline":{"get":{"summary":"Weighted pipeline report","description":"Probability-weighted pipeline value for a board: for each open deal, amount × (stage probability ÷ 100), summed, plus the raw (unweighted) total, open-deal count, and a per-stage breakdown. All money is integer cents.","tags":["Reports"],"parameters":[{"name":"board_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"The board to report on."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["board_id","board_name","currency","generated_at","weighted_pipeline_cents","raw_pipeline_cents","open_deal_count","lanes"],"properties":{"board_id":{"type":"string","format":"uuid"},"board_name":{"type":"string"},"currency":{"type":"string"},"generated_at":{"type":"string","format":"date-time"},"weighted_pipeline_cents":{"type":"integer"},"raw_pipeline_cents":{"type":"integer"},"open_deal_count":{"type":"integer"},"lanes":{"type":"array","items":{"type":"object","properties":{"stage_id":{"type":"string"},"name":{"type":"string"},"region":{"type":"string"},"probability_pct":{"type":"integer"},"deal_count":{"type":"integer"},"raw_cents":{"type":"integer"},"weighted_cents":{"type":"integer"}}}}}}}}},"400":{"description":"`board_id` is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Board not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/reports/this-month":{"get":{"summary":"This-month summary report","description":"The \"This Month So Far\" snapshot for a board: three metrics — proposals (deals that reached the anchor stage this month), deals won, and deal value (cents) — each against its monthly goal with a pace verdict (`ahead` | `on-track` | `behind`, or null if no goal is set), plus day-of-month and the active-deal pipeline. Powers the iPhone home-screen widget.","tags":["Reports"],"parameters":[{"name":"board_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"},"description":"The board to report on."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["board_id","board_name","currency","generated_at","day_of_month","days_in_month","metrics","pipeline"],"properties":{"board_id":{"type":"string","format":"uuid"},"board_name":{"type":"string"},"board_color":{"type":"string","description":"The board color (gradient string); pull the accent from it."},"currency":{"type":"string"},"generated_at":{"type":"string","format":"date-time"},"day_of_month":{"type":"integer"},"days_in_month":{"type":"integer"},"metrics":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","enum":["proposals","deals_won","deal_value"]},"label":{"type":"string"},"current":{"type":"integer","description":"Count for proposals/deals_won; cents for deal_value."},"goal":{"type":"integer","nullable":true},"pace":{"type":"string","enum":["ahead","on-track","behind"],"nullable":true}}}},"pipeline":{"type":"array","items":{"type":"object","properties":{"stage_id":{"type":"string"},"name":{"type":"string"},"region":{"type":"string"},"deal_count":{"type":"integer"}}}}}}}}},"400":{"description":"`board_id` is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Board not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/templates":{"get":{"summary":"List email templates","description":"The workspace's email templates, most-used first. Bodies use the merge tokens {{first_name}} / {{company}} / {{sender_first_name}}, filled by the client. Powers the in-app picker and the Gmail extension's compose picker.","tags":["Templates"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"subject":{"type":"string"},"body":{"type":"string"},"icon":{"type":"string"},"color":{"type":"string"},"use_count":{"type":"integer"}}}}}}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/templates/{id}/use":{"post":{"summary":"Record a template use","description":"Log one use of an email template — bumps `use_count` (the picker's most-used ordering) and appends a use record, exactly like picking it inside Dealboard. Fired by the Gmail extension after inserting a template into a draft. Optional body links the use to a deal or lead.","tags":["Templates"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"The template id."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"deal_id":{"type":"string","format":"uuid","nullable":true},"lead_id":{"type":"string","format":"uuid","nullable":true}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"}}}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Template not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/embed-token":{"post":{"summary":"Mint a Gmail-embed token","description":"Mints a short-lived (10-minute) single-purpose token that the Dealboard for Gmail extension redeems at /api/embed/start to bootstrap an app session inside its sidebar iframe. The session belongs to the API key's creator, is re-checked against team membership at redemption, and is set as a partitioned cookie scoped to the Gmail embed only.","tags":["Templates"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["token","expires_at"],"properties":{"token":{"type":"string"},"expires_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/match":{"get":{"summary":"Match a contact email to deals & leads","description":"Finds deals and leads whose primary-contact / work email matches the address (exact, case-insensitive), with a domain fallback when nothing exact hits. Powers the Gmail extension sidebar. Each result `url` is the shareable /deals/<id> · /leads/<id> deep-link.","tags":["Match"],"parameters":[{"name":"email","in":"query","required":true,"schema":{"type":"string","format":"email"},"description":"The contact email to match."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["email","match","deals","leads"],"properties":{"email":{"type":"string"},"domain":{"type":"string","nullable":true},"match":{"type":"string","enum":["email","domain","none"]},"deals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"company":{"type":"string","nullable":true},"stage":{"type":"string","nullable":true},"amount_cents":{"type":"integer","nullable":true},"url":{"type":"string"}}}},"leads":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","nullable":true},"company":{"type":"string","nullable":true},"status":{"type":"string"},"url":{"type":"string"}}}}}}}}},"400":{"description":"`email` is required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid credential.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/deals":{"get":{"summary":"List deals","tags":["Deals"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Max number of items per page (1-100)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a previous response's `nextCursor`."},{"name":"boardId","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter to one board."},{"name":"stageId","in":"query","schema":{"type":"string"},"description":"Filter to one stage (list a board's stages with `GET /v1/boards/{id}/stages`)."},{"name":"status","in":"query","schema":{"type":"string","enum":["open","won","dead","all"],"default":"open"},"description":"Which deals to include. `open` (default) is the active pipeline only; `won` and `dead` are closed deals; `all` returns every non-deleted deal — use it for history lookups."},{"name":"q","in":"query","schema":{"type":"string","maxLength":120},"description":"Case-insensitive substring search over deal name and company. Combine with `status=all` to find closed deals. This is the lookup to use when you have a name but no id (e.g. a Zapier \"Find Deal\" step)."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DealList"}}}},"400":{"description":"Invalid query parameters.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"summary":"Create a deal","tags":["Deals"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["boardId","name"],"properties":{"boardId":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":200},"company":{"type":["string","null"]},"amountCents":{"type":["integer","null"],"minimum":0},"stageId":{"type":"string","description":"Stage id from the target board. If omitted, defaults to the earliest open stage (region=early, lowest position)."},"ownerId":{"type":"string","format":"uuid","description":"Defaults to the API key's creator."},"labels":{"type":"array","items":{"type":"string"}},"primaryContactName":{"type":["string","null"]},"primaryContactTitle":{"type":["string","null"]},"primaryContactEmail":{"type":["string","null"]},"primaryContactPhone":{"type":["string","null"]},"primaryContactLinkedin":{"type":["string","null"]},"keyTerms":{"type":["string","null"]}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deal"}}}},"400":{"description":"Invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Board not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Idempotency-Key reused with a different body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/deals/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get one deal","tags":["Deals"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deal"}}}},"404":{"description":"Deal not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"summary":"Update a deal","tags":["Deals"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"All fields are optional; only the ones you send are updated.","properties":{"name":{"type":"string","maxLength":200},"company":{"type":["string","null"]},"amountCents":{"type":["integer","null"],"minimum":0},"stageId":{"type":"string","description":"For side-effect-aware stage changes (auto-stamp won/dead/commitment dates), prefer POST /v1/deals/{id}/move."},"ownerId":{"type":"string","format":"uuid"},"labels":{"type":"array","items":{"type":"string"}},"primaryContactName":{"type":["string","null"]},"primaryContactTitle":{"type":["string","null"]},"primaryContactEmail":{"type":["string","null"]},"primaryContactPhone":{"type":["string","null"]},"primaryContactLinkedin":{"type":["string","null"]},"keyTerms":{"type":["string","null"]}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deal"}}}},"400":{"description":"Invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Deal not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"summary":"Delete a deal","description":"Soft delete. Deals are purged after a 30-day grace period.","tags":["Deals"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["id","deleted"],"properties":{"id":{"type":"string"},"deleted":{"type":"boolean"}}}}}},"404":{"description":"Deal not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/deals/{id}/move":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Move a deal to a new stage","description":"Stamps wonAt/deadAt/commitmentAt automatically as appropriate (keyed off the target stage's region). Fires `deal.stage_changed` (and `deal.won` when applicable) on subscribed webhooks.","tags":["Deals"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["toStageId"],"properties":{"toStageId":{"type":"string","description":"Stage id from the deal's board."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deal"}}}},"404":{"description":"Deal not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/deals/{id}/notes":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"List manual notes on a deal","tags":["Notes"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Max number of items per page (1-100)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a previous response's `nextCursor`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteList"}}}}}},"post":{"summary":"Add a manual note","tags":["Notes"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contentText"],"properties":{"contentText":{"type":"string","maxLength":20000}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}}}}},"/v1/deals/{id}/activity":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"List notes + system events (full activity feed)","tags":["Notes"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Max number of items per page (1-100)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a previous response's `nextCursor`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteList"}}}}}}},"/v1/leads":{"get":{"summary":"List leads","description":"Returns inbound leads from your website forms, hosted contact pages, and\ninbound email address. Filter by `boardId` to narrow to one board, and by\n`triageStatus` to filter the inbox (e.g. only `new` to mimic the unread tray).\n\nLeads and Deals are separate objects — see the API description for the\nlifecycle. Promote a Lead to a Deal via POST /v1/leads/{id}/promote.","tags":["Leads"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Max number of items per page (1-100)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a previous response's `nextCursor`."},{"name":"boardId","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter to leads on this board."},{"name":"triageStatus","in":"query","schema":{"type":"string","enum":["new","contacted","promoted","junk","bad_fit","stale"]},"description":"Filter to one triage status. Unknown values return an empty list (forward-compatible — clients can pre-filter without coupling to the enum)."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadList"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"createLead","summary":"Create a lead","description":"Create a lead via the API — the server-to-server intake used by a website-form relay (e.g. a WordPress/Gravity Forms feed). Unlike the public browser form endpoint, this is authenticated by your API key, so submissions are trusted (not bot-checked). Fires the `lead.created` webhook so a Slack alert and any lead-qualification tool can pick it up. Lands on the workspace's first board unless `boardId` is given. Scope: leads:write.","tags":["Leads"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"At least one of workEmail, firstName, lastName, companyName, or message is required.","properties":{"boardId":{"type":"string","format":"uuid","description":"Destination board. Defaults to the workspace's first board."},"firstName":{"type":"string"},"lastName":{"type":"string"},"workEmail":{"type":"string","format":"email"},"phone":{"type":"string"},"companyName":{"type":"string"},"companyWebsite":{"type":"string"},"message":{"type":"string"},"howHeard":{"type":"string"},"consent":{"type":"boolean","description":"Accepts a real boolean or a string (\"true\"/\"1\"/\"yes\") — a server-to-server form relay sends strings."},"consentText":{"type":"string"},"source":{"type":"string","enum":["form","email","import"]},"sourceUrl":{"type":"string","description":"Page the form was submitted from (website-form relay)."},"pageTitle":{"type":"string","description":"Title of the page the form was on."},"referrer":{"type":"string","description":"document.referrer at submission, if known."},"utmSource":{"type":"string"},"utmMedium":{"type":"string"},"utmCampaign":{"type":"string"},"utmTerm":{"type":"string"},"utmContent":{"type":"string"},"gclid":{"type":"string"},"fbclid":{"type":"string"},"msclkid":{"type":"string"}}},"example":{"firstName":"AJ","workEmail":"aj@arcs.health","companyWebsite":"covenant.clinic","message":"Exploring telehealth infrastructure.","howHeard":"Online"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lead"}}}},"400":{"description":"Invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Board not found in this workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Rate limit exceeded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/leads/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get one lead","tags":["Leads"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lead"}}}},"404":{"description":"Lead not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"summary":"Update triage status and/or enrichment","description":"Two independent updates can ride in the same call:\n\n- `triageStatus` — flip lifecycle state (`new` → `contacted`, `junk`,\n  `bad_fit`, or `stale`). Setting `contacted` stamps `contactedAt`;\n  setting `junk` stamps `junkedAt` + the calling user. **`promoted` is NOT\n  settable here** — call `POST /v1/leads/{id}/promote` instead, which\n  creates the Deal and links it.\n\n- `enrichment` — merge a free-form enrichment payload from your qualifier.\n  Top-level keys you send overwrite existing keys; keys you omit are\n  preserved. Each PATCH appends a snapshot to `enrichment.history` (which\n  you cannot overwrite — it's server-managed audit). Use this to push AI\n  scoring, summaries, or attachment URLs onto a Lead.\n\nEmpty body is allowed — returns the Lead unchanged.","tags":["Leads"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"All fields are optional; only the ones you send are applied.","properties":{"triageStatus":{"type":"string","enum":["new","contacted","junk","bad_fit","stale"],"description":"`promoted` is intentionally excluded — promoting a Lead requires creating a Deal, which is the dedicated `POST /v1/leads/{id}/promote` endpoint."},"enrichment":{"$ref":"#/components/schemas/LeadEnrichment","description":"Merged into existing enrichment. Keys you send overwrite; keys you omit are preserved. `history` cannot be overwritten."}}},"examples":{"flipToContacted":{"summary":"Mark as contacted","value":{"triageStatus":"contacted"}},"pushScore":{"summary":"Push an AI score + summary","value":{"enrichment":{"score":0.82,"summary":"High intent. Mentions budget, decision-maker, timeline.","notes":"Asked about pricing. Looks like 20-50 user range."}}},"bothInOneCall":{"summary":"Score + transition in one call","value":{"triageStatus":"contacted","enrichment":{"score":0.82,"summary":"High intent."}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Lead"}}}},"400":{"description":"Invalid input (e.g. attempted PATCH to triageStatus=\"promoted\").","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Lead not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/leads/{id}/promote":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"summary":"Promote a Lead to a Deal (temporarily disabled)","description":"⚠️ **Temporarily disabled.** This endpoint currently returns `403`\n(`code: \"promote_disabled\"`). Enrich leads via `PATCH /v1/leads/{id}`; a\nteammate promotes them to deals from the Dealboard inbox. The behavior\nbelow applies when promotion is re-enabled.\n\nCreates a new Deal in the same board's pipeline using the Lead's submitter\nas the primary contact and the form fields as the Deal name + company. The\nLead row is updated with `triageStatus=\"promoted\"` and `dealId` pointing at\nthe new Deal — provenance is preserved.\n\nIdempotent on `leadId`: replays return the existing Deal id without\ncreating a second one.","tags":["Leads"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","description":"All fields optional. Defaults come from the Lead form + the board's configured defaults.","properties":{"dealName":{"type":"string","maxLength":200,"description":"Override the Deal name. Default: companyName, or full name, or workEmail, or \"New deal\"."},"ownerUserId":{"type":"string","format":"uuid","description":"Override the Deal owner. Default: the lead form's configured default owner."}}}}}},"responses":{"200":{"description":"OK — the Lead was already promoted. Returns the existing Deal id.","content":{"application/json":{"schema":{"type":"object","required":["dealId","leadId"],"properties":{"dealId":{"type":"string","format":"uuid"},"leadId":{"type":"string","format":"uuid"}}}}}},"201":{"description":"Created — the Lead was promoted; the Deal is new.","content":{"application/json":{"schema":{"type":"object","required":["dealId","leadId"],"properties":{"dealId":{"type":"string","format":"uuid"},"leadId":{"type":"string","format":"uuid"}}}}}},"400":{"description":"Invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Lead not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"Board has no stages — add stages before promoting leads.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/notes/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"patch":{"summary":"Edit a manual note","tags":["Notes"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contentText"],"properties":{"contentText":{"type":"string","maxLength":20000}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Note"}}}},"404":{"description":"Note not found in your workspace, or is a system event (not editable).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"summary":"Delete a manual note","tags":["Notes"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["id","deleted"],"properties":{"id":{"type":"string"},"deleted":{"type":"boolean"}}}}}},"404":{"description":"Note not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/boards":{"get":{"summary":"List boards","tags":["Boards"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Max number of items per page (1-100)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a previous response's `nextCursor`."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BoardList"}}}}}},"post":{"summary":"Create a board (admin only)","tags":["Boards"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":80},"currency":{"type":"string","enum":["USD","EUR","GBP","CAD","AUD","JPY","INR"]}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Board"}}}},"403":{"description":"Admin role required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/boards/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Get one board","tags":["Boards"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Board"}}}},"404":{"description":"Board not found.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"summary":"Update a board (admin only)","tags":["Boards"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":80},"currency":{"type":"string","enum":["USD","EUR","GBP","CAD","AUD","JPY","INR"]},"defaultDealAmountCents":{"type":"integer","minimum":0}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Board"}}}},"403":{"description":"Admin role required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/boards/{id}/stages":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"List a board's stages","description":"Returns the stages on a board in pipeline order (early → anchor → late → won → dead). Use the returned `id` values for `stageId`/`toStageId` write paths.","tags":["Boards"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StageList"}}}},"404":{"description":"Board not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhooks":{"get":{"summary":"List webhook subscriptions","tags":["Webhooks"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"description":"Max number of items per page (1-100)."},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor from a previous response's `nextCursor`."},{"name":"boardId","in":"query","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookList"}}}}}},"post":{"summary":"Create a webhook subscription (admin only)","description":"For `generic` provider webhooks, the response includes a one-time `signingSecret` — capture it; the secret cannot be retrieved later.","tags":["Webhooks"],"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client-chosen string. If the same key is sent twice within 24h with the same body, the original response is replayed."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["boardId","url","events"],"properties":{"boardId":{"type":"string","format":"uuid"},"url":{"type":"string","maxLength":2000},"label":{"type":["string","null"],"maxLength":80},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["deal.created","deal.stage_changed","deal.won","lead.created"]}},"stageFilter":{"type":"array","items":{"type":"string"},"description":"Stage IDs (from this board) to filter stage_changed events. Empty = all stages."}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"signingSecret":{"type":["string","null"],"description":"Non-null only for `generic` provider. Shown exactly once."}}}]}}}},"400":{"description":"Invalid URL or input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Admin role required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/webhooks/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"summary":"Delete a webhook subscription (admin only)","tags":["Webhooks"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["id","deleted"],"properties":{"id":{"type":"string"},"deleted":{"type":"boolean"}}}}}},"403":{"description":"Admin role required.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Webhook not found in your workspace.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}}}