{"openapi":"3.1.0","info":{"title":"Hedge Submission API","version":"1.0","description":"Submit commercial insurance risks to Hedge programmatically and track\nthem to quote. One rail, two doors:\n\n- **Connected apps / broker agents**, per-user OAuth 2.1 (PKCE, dynamic\n  client registration). The broker consents once; tokens are scoped to\n  their brokerage. This is also how the Hedge MCP connector works.\n- **Systems (machine) credentials**, brokerage API clients created\n  self-serve in the broker portal (Settings → API keys) or issued by\n  Hedge ops, exchanged via the `client_credentials` grant. Submissions\n  made with a machine credential must name a `producer_email`.\n\nThe contract is **gaps-driven**: create a submission with minimal data,\nupload what you have, and read `GET /broker/submissions/{id}/requirements`\nfor exactly what each matched market still needs. Answer carrier\nquestions for instant-quote markets, then `finalize` to start marketing.\n\nAll endpoints are scoped to YOUR brokerage by the token, there is no\ncross-tenant access. Base URL: `https://api.hedgespecialty.com/api/v1`.\n\n**Webhooks (push).** Instead of polling the events feed, register a\nwebhook endpoint and Hedge POSTs new submission events to you as they\nbecome visible; same event objects, same visibility rules, minutes\ninstead of poll intervals. Endpoints are managed in the broker portal\n(Settings → Webhooks, brokerage admins only; the management API is\nportal-session-only by design; a leaked machine key must never be\nable to point your event stream somewhere new). Deliveries are signed\n(svix-compatible) and documented under **Webhooks** below. Endpoints\ncreated in the staging portal receive staging deliveries, with their\nown secrets.\n\nChanges are additive-only on this version. Breaking changes ship as a\nnew version with notice to integrated partners.\n"},"externalDocs":{"description":"Hedge developer documentation","url":"https://docs.hedgespecialty.com"},"servers":[{"url":"https://api.hedgespecialty.com/api/v1","description":"Production"},{"url":"https://staging-api.hedgespecialty.com/api/v1","description":"Staging sandbox"}],"tags":[{"name":"Auth"},{"name":"Account"},{"name":"Submissions"},{"name":"Requirements"},{"name":"Carrier quotes"},{"name":"Documents"},{"name":"Policies"},{"name":"Appetite"},{"name":"Webhooks"}],"components":{"securitySchemes":{"brokerOAuth":{"type":"oauth2","description":"Broker access token. Scopes: `broker_mcp` (read), `broker_submit`\n(write). Per-user tokens come from the OAuth authorization-code +\nPKCE flow (`/oauth/authorize` → `/oauth/token`); machine tokens\nfrom `client_credentials`, using an API key created self-serve in\nthe broker portal (Settings → API keys) or issued by Hedge ops.\nAccess tokens live 60 minutes.\n","flows":{"authorizationCode":{"authorizationUrl":"https://api.hedgespecialty.com/api/v1/oauth/authorize","tokenUrl":"https://api.hedgespecialty.com/api/v1/oauth/token","scopes":{"broker_mcp":"Read brokerage-scoped submissions, policies, payments, appetite, and requirements.","broker_submit":"Create and manage submissions after broker review and approval."}},"clientCredentials":{"tokenUrl":"https://api.hedgespecialty.com/api/v1/oauth/token","scopes":{"broker_mcp":"Read brokerage-scoped submissions, policies, payments, appetite, and requirements.","broker_submit":"Create and manage submissions after broker review and approval."}}}}},"schemas":{"Applicant":{"type":"object","required":["insured_name"],"properties":{"insured_name":{"type":"string","maxLength":256},"business_phone":{"type":"string"},"business_email":{"type":"string"},"website":{"type":"string"},"contact_first_name":{"type":"string"},"contact_last_name":{"type":"string"},"contact_email":{"type":"string"},"naics":{"type":"string"},"mailing_address":{"type":"object","description":"line1, city, state and zip are required together.","required":["line1","city","state","zip"],"properties":{"line1":{"type":"string"},"line2":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zip":{"type":"string"}}}}},"SubmissionCreate":{"type":"object","required":["applicant","narrative"],"properties":{"applicant":{"$ref":"#/components/schemas/Applicant"},"narrative":{"type":"string","description":"Operations description of the risk, what the business does,\nsize, anything an underwriter should know. Required; appetite\nmatching depends on it.\n"},"lines_of_business":{"type":"array","items":{"type":"string"},"description":"Canonical slugs (e.g. commercial_general_liability) or common aliases (gl, wc)."},"effective_date":{"type":"string","format":"date"},"primary_state":{"type":"string","minLength":2,"maxLength":2,"description":"Primary risk state, 2-letter US code (normalized to uppercase).\nWhen the applicant mailing address also carries a state the two\nmust agree: the request 422s on a conflict, with a message that\nsays exactly what to fix. Omit it to use the mailing address\nstate.\n"},"producer_email":{"type":"string","description":"REQUIRED when authenticating with a machine credential, the active portal user this submission is attributed to."},"insured_id":{"type":"string","format":"uuid","description":"Optional id of an existing insured from GET /broker/insureds;\nlinks the new submission to that client's account (repeat\nbusiness, a new line for an existing client). Must belong to\nyour brokerage; an unknown or foreign id returns 404.\n"}}},"SubmissionCreated":{"type":"object","properties":{"submission_id":{"type":"string","format":"uuid"},"state":{"type":"string"},"status_label":{"type":"string"}}},"Requirements":{"type":"object","description":"The live \"what's still needed\" view.","properties":{"submission_id":{"type":"string","format":"uuid"},"insured_name":{"type":"string"},"lines":{"type":"array","items":{"type":"string"}},"effective_date":{"type":"string","format":"date"},"state":{"type":"string"},"forms":{"type":"array","items":{"type":"string"},"description":"Application forms Hedge prepares for this risk (ACORD baseline + recommended carrier supplements)."},"marketing_status":{"type":"string","enum":["not_started","matching","matched","no_markets_matched"],"description":"Where the asynchronous marketing pipeline is. Matching runs for\na few minutes after `finalize`, so an empty `markets` list means\n\"still matching\" when this is `matching`; not \"no appetite\".\n"},"marketing_hint":{"type":"string","nullable":true,"description":"Human-readable next step for the current marketing_status."},"markets":{"type":"array","items":{"type":"object","properties":{"market_name":{"type":"string"},"ready":{"type":"boolean"},"needs_from_you":{"type":"array","items":{"type":"string"}},"hedge_obtains":{"type":"array","items":{"type":"string"}}}}},"carrier_api_sessions":{"type":"array","items":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"carrier_slug":{"type":"string"},"program":{"type":"string"},"status":{"type":"string"},"completion_pct":{"type":"number"},"missing_questions":{"type":"array","items":{"type":"object"}},"has_quote":{"type":"boolean"}}}},"documents_on_file":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"kind_label":{"type":"string"},"display_name":{"type":"string"}}}}}}}},"security":[{"brokerOAuth":[]}],"paths":{"/oauth/register":{"post":{"tags":["Auth"],"operationId":"registerOAuthClient","summary":"Register an OAuth client (Dynamic Client Registration)","security":[],"description":"RFC 7591. Public PKCE clients only; no client_secret is ever issued\nhere. Returns a stateless signed client_id you use on /oauth/authorize\nand /oauth/device_authorization. Machine (client_credentials)\ncredentials are not issued by this endpoint: create them self-serve\nin the broker portal (Settings → API keys) or ask Hedge ops.\n","responses":{"201":{"description":"Client registration ({client_id, client_name, redirect_uris})"},"400":{"description":"Invalid registration request"}}}},"/oauth/authorize":{"get":{"tags":["Auth"],"operationId":"startOAuthAuthorization","summary":"Start the browser authorization flow","security":[],"description":"RFC 6749 authorization endpoint, PKCE (S256) required. Redirects the\nbroker to the portal consent screen; on approval the browser returns\nto your redirect_uri with a single-use code for /oauth/token. Use the\ndevice flow instead for headless or CLI contexts.\n","responses":{"302":{"description":"Redirect to the consent screen"},"400":{"description":"Unknown client or invalid redirect_uri"}}}},"/oauth/device_authorization":{"post":{"tags":["Auth"],"operationId":"startOAuthDeviceAuthorization","summary":"Start the device authorization flow","security":[],"description":"RFC 8628. Form-encoded client_id (+ optional scope). Returns a\ndevice_code to poll /oauth/token with\n(grant_type=urn:ietf:params:oauth:grant-type:device_code), plus a\nuser_code and verification_uri the broker opens to approve. Codes\nexpire after 15 minutes; poll no faster than the returned interval.\n","responses":{"200":{"description":"Device authorization ({device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval})"},"400":{"description":"Unknown client"}}}},"/oauth/token":{"post":{"tags":["Auth"],"operationId":"exchangeOAuthToken","summary":"Exchange credentials for an access token","security":[],"description":"`grant_type=authorization_code` (+ PKCE) and `grant_type=refresh_token`\nfor per-user connections; `grant_type=client_credentials` with your\n`client_id` + `client_secret` for machine credentials. Form-encoded.\n","responses":{"200":{"description":"Token response ({access_token, token_type, expires_in, scope})"},"400":{"description":"Invalid request or grant"},"401":{"description":"Invalid credentials"}}}},"/broker/me":{"get":{"tags":["Account"],"operationId":"getBrokerIdentity","summary":"Who am I (user or machine credential)","description":"Returns the authenticated identity plus the brokerage. Two shapes,\ndiscriminated by `kind`:\n\n- `kind: user` (portal session or per-user OAuth token): `id`,\n  `email`, `full_name`, `role`, `brokerage`, `active_states`.\n- `kind: api_client` (machine credential): `client_id` (the public\n  `bac_...` identifier), `label`, `scope` (the token's granted\n  scopes), `brokerage`, `active_states`.\n\n`active_states` lists the US states Hedge is currently active in;\n`null` means no gating (all states).\n","responses":{"200":{"description":"Identity (user shape or api_client shape)"},"401":{"description":"Account or credential is no longer active"}}}},"/broker/submissions":{"get":{"tags":["Submissions"],"operationId":"listSubmissions","summary":"List your brokerage's submissions","description":"Newest first. All filters are optional; a bare call returns the full\nbook.\n","parameters":[{"in":"query","name":"status","schema":{"type":"string","enum":["intake","preparing","marketing","quoting","binding","bound","issued","lost","cancelled"]},"description":"Exact submission state. Unknown values return 422 with the valid list."},{"in":"query","name":"search","schema":{"type":"string","maxLength":256},"description":"Case-insensitive match on the insured name (literal, wildcards are escaped)."},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":500},"description":"Max rows to return. Unbounded when omitted."},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip, for paging with limit."},{"in":"query","name":"updated_since","schema":{"type":"string","format":"date-time"},"description":"Only submissions updated at or after this timestamp (delta sync)."}],"responses":{"200":{"description":"Submission list"},"422":{"description":"Invalid filter (e.g. limit over 500)"}}},"post":{"tags":["Submissions"],"operationId":"createSubmission","summary":"Create a submission","description":"Creates the submission and starts intake (enrichment + instant\ncarrier-API quoting). Does NOT start marketing to carriers, call\n`finalize` when documents and answers are in.\n\nSupports an `Idempotency-Key` header: a retried create with the same\nkey within 24h replays the original response instead of creating a\nduplicate. A concurrent duplicate returns 409, retry shortly.\n","parameters":[{"in":"header","name":"Idempotency-Key","schema":{"type":"string"},"required":false}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmissionCreated"}}}},"409":{"description":"Same Idempotency-Key already in flight"},"422":{"description":"Validation error (message says exactly what to fix)"},"429":{"description":"Daily create limit reached"}}}},"/broker/submissions/{submission_id}":{"get":{"tags":["Submissions"],"operationId":"getSubmission","summary":"Get one submission (status, markets, documents)","description":"The submission header (id, insured_name, lines, state, status_label,\npremium, primary_state, effective_date, created_at, updated_at) plus\nthe live marketing roll-up:\n\n- `markets`: one entry per engaged carrier with per-line rows\n  (line, status, status_label, quote premium in cents and dollars,\n  last update, decline/withdrawal notes).\n- `status_summary`: counts by status and per-line quoted/total\n  progress.\n- `documents`: finalized documents on file, each with a\n  `download_path` you can GET with the same bearer token.\n- `bor_actions`: Broker of Record workstreams, empty for most\n  submissions.\n","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Submission detail with marketing roll-up"},"404":{"description":"Not found (or not your brokerage's)"}}}},"/broker/submissions/{submission_id}/events":{"get":{"tags":["Submissions"],"operationId":"listSubmissionEvents","summary":"Poll a submission's market events (quotes, declines, progress)","description":"The change feed for one submission; poll it to surface outcomes\nthat arrive hours after submit on email/portal markets (a carrier\nquoting back, declining, or a market being engaged) without diffing\nthe detail view. Events are ordered oldest-first with a stable\ncursor.\n\n**The end-of-stream signal is the cursor, never page length.** A\nnon-null `next_cursor` means more events may exist: store it and\npoll again with `since=next_cursor` until you receive a null. A\npage may hold fewer than `limit` events; or none at all; with a\nnon-null cursor (internal audit rows are scanned but never\nreturned, and a bounded per-request scan can stop early); never\ntreat a short page as the tail. `next_cursor` is null only when\nthe request proved your position is the tail of the\ncurrently-visible stream; resume later polls from your last\nnon-null cursor. Event ids are stable, so re-reading an older\ncursor is always safe (dedupe by `id`).\n\n`type` is a closed set:\n\n- `market_attached`; a market was engaged (sent to underwriter).\n- `quote_received`; a quote was received and relayed to you (the\n  quote itself is on the submission detail / proposal).\n- `status_changed`; other movement, e.g. quote in progress or a\n  market re-engaged.\n- `declined`, `withdrawn`, `blocked`; terminal outcomes for a\n  market; `detail` carries the recorded rationale when there is one.\n- `bound`; coverage placed with that market.\n\nEach event: `{id, type, occurred_at, market: {name, lines},\nstatus, status_label, detail?}`. `status`/`status_label` use the\nsame vocabulary as the submission detail's `markets` roll-up.\nEvents reflect what is currently visible to your brokerage; the\nsame rules as the detail view (markets Hedge has actually engaged;\nquotes only once relayed).\n\nBecause visibility is evaluated at read time while the cursor is a\nposition over the underlying log, a market that becomes visible\nlater (e.g. its quote gets relayed after your cursor passed its\nearly rows) can enter your stream mid-history; a `quote_received`\nwithout a preceding `market_attached`. Treat each event as\nself-contained rather than assuming lifecycle completeness, and\nread `GET /broker/submissions/{submission_id}` for authoritative\ncurrent state. Re-reading from an older (or empty) cursor replays\nthe full currently-visible history when you need to backfill.\n","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"since","schema":{"type":"string"},"description":"Opaque cursor from a previous page's next_cursor; returns events strictly after it. Omit to read from the beginning."},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Max events per page (internal audit rows are not counted). Values outside [1, 100] are clamped, never rejected."}],"responses":{"200":{"description":"One page of events plus the follow-up cursor","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["market_attached","quote_received","status_changed","declined","withdrawn","blocked","bound"]},"occurred_at":{"type":"string","format":"date-time"},"market":{"type":"object","properties":{"name":{"type":"string"},"lines":{"type":"array","items":{"type":"string"}}}},"status":{"type":"string"},"status_label":{"type":"string"},"detail":{"type":"string","nullable":true}}}},"next_cursor":{"type":"string","nullable":true}}}}}},"404":{"description":"Not found (or not your brokerage's)"},"422":{"description":"Invalid since cursor"}}}},"/broker/submissions/{submission_id}/documents":{"post":{"tags":["Submissions"],"operationId":"uploadSubmissionDocument","summary":"Attach a PDF (ACORD, loss runs, supplement)","description":"Multipart upload, PDF only, 15 MB max per file. Uploaded applications are auto-extracted into the submission brief.","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Stored"},"400":{"description":"Not a valid PDF"},"413":{"description":"PDF is over the 15 MB limit. Split the package or compress the PDF."},"429":{"description":"Daily upload limit reached"}}}},"/broker/forms":{"get":{"tags":["Requirements"],"operationId":"listBlankApplicationForms","summary":"Search the blank application-form catalog","description":"ACORD baseline plus Hedge/carrier supplemental applications; the\nsame form keys the requirements view lists under `forms`. `q`\nmatches key or title (case-insensitive); results cap at 50.\n","parameters":[{"in":"query","name":"q","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Matching forms","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"form_key":{"type":"string","example":"HEDGE_15_LIQUOR_LIABILITY_SUPPLEMENTAL"},"title":{"type":"string"},"is_acord":{"type":"boolean"},"page_count":{"type":"integer"}}}}}}}}}},"/broker/forms/{form_key}/pdf":{"get":{"tags":["Requirements"],"operationId":"downloadBlankApplicationForm","summary":"Download a blank application form","description":"Serves the editable AcroForm blank; fill it in any PDF editor and attach it to the submission.","parameters":[{"in":"path","name":"form_key","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The blank PDF"},"404":{"description":"Unknown form key"}}}},"/broker/submissions/{submission_id}/finalized-documents":{"get":{"tags":["Documents"],"operationId":"listSubmissionDocuments","summary":"List a submission's documents","description":"Finalized documents on file for one of your submissions: rendered\nACORDs, supplements, your own uploads, received documents. Each row\ncarries `id`, `source`, `source_label`, `display_name`, `size_bytes`,\n`content_type`, `created_at` and a `download_path` for the PDF route\nbelow. Proposal-surface documents (carrier quote PDFs, bind-wizard\nuploads) are excluded.\n","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Document list (empty for a submission that is not yours)"}}}},"/broker/finalized-documents/{document_id}/pdf":{"get":{"tags":["Documents"],"operationId":"downloadSubmissionDocument","summary":"Download a submission document PDF","description":"Streams the PDF, brokerage-scoped. Form fields stay editable; widget appearances are baked so filled values render in any viewer.","parameters":[{"in":"path","name":"document_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The PDF bytes","content":{"application/pdf":{}}},"404":{"description":"Not found (or not your brokerage's)"},"502":{"description":"Stored document could not be retrieved"}}}},"/broker/submissions/{submission_id}/requirements":{"get":{"tags":["Requirements"],"operationId":"getSubmissionRequirements","summary":"What this submission still needs","description":"The contract centerpiece, per-market readiness (`needs_from_you` =\nitems only you can supply), live carrier question gaps, the resolved\nform set, and documents already on file. Deterministic and fast; poll\nit between uploads/answers and finalize.\n","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Requirements view","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Requirements"}}}}}}},"/broker/submissions/{submission_id}/api-quotes/sessions":{"get":{"tags":["Carrier quotes"],"operationId":"listCarrierQuoteSessions","summary":"List carrier-API quote sessions and their question gaps","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Sessions with missing_required_questions_json"}}}},"/broker/submissions/{submission_id}/api-quotes/sessions/{session_id}/answers":{"post":{"tags":["Carrier quotes"],"operationId":"saveCarrierAnswers","summary":"Answer a carrier session's questions (merge-write)","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"session_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Refreshed session with remaining gaps"}}}},"/broker/submissions/{submission_id}/api-quotes/sessions/{session_id}/close":{"post":{"tags":["Carrier quotes"],"operationId":"requestCarrierQuote","summary":"Request indication then quote","description":"Soft-fails, if the carrier still needs answers, the returned session's outcome is awaiting_answers with the gaps listed.","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"session_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Session with quote (quote_json","quote_pdf_url) or remaining gaps":null}}}},"/broker/submissions/{submission_id}/finalize":{"post":{"tags":["Submissions"],"operationId":"finalizeSubmission","summary":"Start marketing (the explicit \"go shop it\")","description":"Hedge matches carrier markets and prepares submissions; each still\npasses your brokerage's approval gates. Matching is asynchronous;\nit usually completes within about 5 minutes (`typical_wait_seconds`).\nPoll the requirements endpoint: its `marketing_status` flips to\n`matched` when lanes attach and distinguishes still-matching from\nno-appetite. (The submission detail's `markets` list only shows\nmarkets Hedge has actually contacted, which can be later; after\nyour brokerage's approval gates.)\n","parameters":[{"in":"path","name":"submission_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Queued; matching runs asynchronously.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"queued"},"marketing_status":{"type":"string","example":"matching"},"typical_wait_seconds":{"type":"integer","example":300},"detail":{"type":"string"}}}}}}}}},"/broker/insureds":{"get":{"tags":["Submissions"],"operationId":"listInsureds","summary":"List or search your book of insureds","description":"Your brokerage's insureds on file with Hedge, most recently active\nfirst. With `q` it is the \"start from existing insured\" picker:\nreturns enough identity to prefill a new submission (name, entity\ntype, contact details, NAICS, mailing address). Pass a matching\nrow's `id` as `insured_id` on submission create to link the account.\nWith `include_stats=true` each row also carries submissions_count,\npolicies_count and last_activity_at for a book overview.\n","parameters":[{"in":"query","name":"q","schema":{"type":"string"},"description":"Case-insensitive match on the insured name."},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":200,"default":10}},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0,"default":0},"description":"Rows to skip, for paging large books."},{"in":"query","name":"include_stats","schema":{"type":"boolean","default":false},"description":"Adds submissions_count, policies_count and last_activity_at per\nrow (extra aggregate queries; leave off for typeahead use).\n"}],"responses":{"200":{"description":"Insured summaries","most recently updated first":null}}}},"/broker/insureds/{insured_id}":{"get":{"tags":["Submissions"],"operationId":"getInsured","summary":"Get one insured from your book","description":"One insured's canonical identity (name, DBA, entity type, contact\ndetails, NAICS/SIC, mailing address), active contacts, plus YOUR\nbrokerage's submissions and bound policies for it; the portal's\ninsured detail view in a single call.\n","parameters":[{"in":"path","name":"insured_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Insured detail"},"404":{"description":"Not found (or not your brokerage's)"}}}},"/broker/policies":{"get":{"tags":["Policies"],"operationId":"listPolicies","summary":"List your brokerage's bound policies","description":"One row per policy: policy_number, carrier_name, status +\nstatus_label, premium, effective and expiration dates, and the\nsource submission_id.\n","responses":{"200":{"description":"Policy list","newest effective date first":null}}}},"/broker/policies/{policy_id}":{"get":{"tags":["Policies"],"operationId":"getPolicy","summary":"Get one policy","description":"Full policy detail: identifiers, coverage lines, term, carrier and\nunderwriting paper (with NAIC code and admitted status), the premium\nbreakdown (net premium, fees, taxes, total billed), YOUR commission\n(rate and amount), payment terms, and the downloadable documents\n(binder, policy, declarations) with their download paths.\n","parameters":[{"in":"path","name":"policy_id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Policy detail"},"404":{"description":"Not found (or not your brokerage's)"}}}},"/broker/policies/{policy_id}/document/{kind}":{"get":{"tags":["Policies"],"operationId":"downloadPolicyDocument","summary":"Download a policy document PDF","description":"Streams the binder, policy, or declarations PDF, brokerage-scoped.","parameters":[{"in":"path","name":"policy_id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"kind","required":true,"schema":{"type":"string","enum":["binder","policy","declarations"]}}],"responses":{"200":{"description":"The PDF bytes (or a 307 redirect to hosted storage)","content":{"application/pdf":{}}},"404":{"description":"Policy or document not found"},"502":{"description":"Stored document could not be retrieved"}}}},"/broker/payments":{"get":{"tags":["Payments"],"operationId":"listPayments","summary":"List invoice and payment status","description":"One row per bound submission with billing: submission_id,\ninsured_name, status + status_label, premium, invoice_url (the\nhosted invoice), updated_at. Read scope is enough.\n","responses":{"200":{"description":"Payment list","most recently updated first":null}}}},"/broker/appetite":{"get":{"tags":["Appetite"],"operationId":"checkAppetite","summary":"Which markets have appetite for a class of business","parameters":[{"in":"query","name":"q","required":true,"schema":{"type":"string"},"description":"Risk description, e.g. roofing contractor"},{"in":"query","name":"state","schema":{"type":"string"}},{"in":"query","name":"lob","schema":{"type":"string"}}],"responses":{"200":{"description":"Ranked markets with lines and matched programs"},"429":{"description":"Daily appetite-check limit reached"}}}},"/broker/markets/{market_id}/requirements":{"get":{"tags":["Requirements"],"operationId":"getMarketRequirements","summary":"Requirements forecast for a market + line (pre-submission)","parameters":[{"in":"path","name":"market_id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"lob","required":true,"schema":{"type":"string"}},{"in":"query","name":"state","schema":{"type":"string"}}],"responses":{"200":{"description":"Baseline forms + authored program requirements (broker-safe)"}}}},"/broker/market-access":{"get":{"tags":["Market access"],"operationId":"getMarketAccess","summary":"The markets this brokerage receives quotes from, with per-program and per-line toggles","description":"One row per market Hedge has enabled for your brokerage, each with its per-program breakdown and each program's line switches. A toggle that is off stops NEW submissions from being marketed there; in-flight submissions are unaffected. Readable by any signed-in broker user.","responses":{"200":{"description":"Rows of {market_id, name, enabled, programs: [{program_id, name, description, enabled, lines: [{slug, label, enabled}]}]}"}}},"put":{"tags":["Market access"],"operationId":"setMarketAccess","summary":"Turn a whole market on or off (admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["market_id","enabled"],"properties":{"market_id":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The refreshed market-access table"},"403":{"description":"Admin role required"},"404":{"description":"Market not visible to this brokerage"}}}},"/broker/market-access/program":{"put":{"tags":["Market access"],"operationId":"setProgramAccess","summary":"Turn one program of a market on or off (admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["program_id","enabled"],"properties":{"program_id":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The refreshed market-access table"},"403":{"description":"Admin role required"},"404":{"description":"Program not visible to this brokerage"}}}},"/broker/market-access/program-line":{"put":{"tags":["Market access"],"operationId":"setProgramLineAccess","summary":"Turn a single line of business within a program on or off (admin)","description":"Toggles one line the program lists (see the program's lines in GET /broker/market-access). Turning off every line a program lists is equivalent to turning the program off.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["program_id","lob_slug","enabled"],"properties":{"program_id":{"type":"string","format":"uuid"},"lob_slug":{"type":"string","description":"Canonical line-of-business slug","e.g. commercial_general_liability":null},"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"The refreshed market-access table"},"403":{"description":"Admin role required"},"404":{"description":"Line not listed by this program","or program not visible":null}}}}},"webhooks":{"submission.events":{"post":{"tags":["Webhooks"],"operationId":"receiveSubmissionEvents","summary":"New submission events, pushed to your registered endpoint","description":"Hedge POSTs this message to each webhook endpoint your brokerage\nhas registered (broker portal → Settings → Webhooks, admins only)\nwhenever a submission gains newly-visible events; the SAME event\nobjects, vocabulary and visibility rules as\n`GET /broker/submissions/{submission_id}/events`, delivered\nwithin about a minute of becoming visible. An endpoint receives\nevents from its creation onward; history is not replayed.\n\n**Verifying.** Every delivery is signed with your endpoint's\n`whsec_` secret (shown exactly once at creation; rotate by\nrevoking and re-creating). Headers are the standard-webhooks /\nsvix scheme, verifiable with any svix SDK:\n\n- `svix-id`; the delivery id, STABLE across every retry and\n  manual redelivery: your idempotency key.\n- `svix-timestamp`; unix seconds; reject stale timestamps\n  (±5 minutes is the conventional tolerance).\n- `svix-signature`; `v1,` + base64(HMAC-SHA256(secret,\n  `{id}.{timestamp}.{body}`)) over the exact bytes received.\n\n**Delivery semantics.** At-least-once: dedupe on `svix-id`.\nWithin one submission, deliveries arrive in order; across\nsubmissions there is no ordering guarantee. An event's `(id,\ntype)` pair can arrive AGAIN with an upgraded `type`; e.g. a\n`status_changed` later re-delivered as `quote_received` once the\nquote is relayed to you; so treat `(id, type)` as the event\nidentity; deduping webhook events by `id` alone will drop quote\narrivals. (The polling feed has no such re-emission; this is\npush-only behavior, and it is how a relay that happens hours\nafter the underlying status row is still pushed to you.)\n\n**Acknowledging.** Respond 2xx within 10 seconds; the body is\nignored. Redirects are treated as failures. Anything else is\nretried with backoff for up to 8 attempts over 24 hours; the\ndelivery is then parked (re-send it from the portal's delivery\nlog). Repeated exhausted deliveries auto-disable the endpoint;\na successful test-send from the portal re-enables it and queued\nevents resume.\n\nA `ping` message (`{\"type\": \"ping\", ...}`, same signature scheme)\nis sent by the portal's test button; acknowledge it like any\nother delivery.\n","requestBody":{"content":{"application/json":{"schema":{"type":"object","required":["type","submission_id","events"],"properties":{"type":{"type":"string","const":"submission.events"},"submission_id":{"type":"string","format":"uuid"},"events":{"type":"array","description":"Newly-visible events, oldest first; the exact object shape of the polling feed's `events` array.","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["market_attached","quote_received","status_changed","declined","withdrawn","blocked","bound"]},"occurred_at":{"type":"string","format":"date-time"},"market":{"type":"object","properties":{"name":{"type":"string"},"lines":{"type":"array","items":{"type":"string"}}}},"status":{"type":"string"},"status_label":{"type":"string"},"detail":{"type":"string","nullable":true}}}}}}}}},"responses":{"200":{"description":"Acknowledge with any 2xx within 10 seconds; response body is ignored."}}}}}}