{"openapi":"3.1.0","info":{"title":"Bland marketing site API","version":"1.0.0","summary":"Public endpoints on www.bland.ai for content retrieval, platform stats and lead capture.","description":"The public HTTP API of the Bland marketing site (www.bland.ai).\n\nSCOPE. Read this first. This document describes ONLY the marketing/web endpoints hosted on www.bland.ai: blog content retrieval, a public platform counter, and the contact/lead capture forms. It is NOT the Bland voice platform API.\n\nThe Bland PRODUCT API (placing calls, conversational pathways, voices and TTS, knowledge bases, phone numbers, tools, contacts, batches, analysis and the rest) lives at https://api.bland.ai/v1 and is documented at https://docs.bland.ai. It authenticates with an API key in the `Authorization` header. As of this revision Bland does not publish a machine-readable OpenAPI document for that surface, so it is deliberately NOT described here: inventing operations from prose would be worse than a dead end. Agents that need the product API should read https://docs.bland.ai (which also serves https://docs.bland.ai/llms.txt) or connect to the production MCP server at https://api.bland.ai/v1/mcp (Streamable HTTP transport, Bearer auth, 120 requests per minute per organization), which exposes the platform as callable tools.\n\nInternal machinery on this domain (cron jobs, vendor webhooks, analytics drains, cache revalidation, draft mode, internal auth, admin and privacy back-ends, and the metered voice/chat/speech demo endpoints) is intentionally not described here and carries no compatibility promise.\n\nErrors: every endpoint answers 4xx and 5xx with the JSON `ApiError` envelope: a stable `error.code`, a human `error.message`, and an actionable `error.hint`. Branch on `error.code`; it is the only field guaranteed stable across releases. Some endpoints carry extra legacy top-level keys alongside the envelope for backward compatibility with existing browser clients.\n\nConventions: all request and response bodies are JSON (UTF-8) except getBlogPostMarkdown, which returns `text/markdown` on success. All timestamps are ISO 8601 in UTC.","contact":{"name":"Bland","url":"https://www.bland.ai/contact","email":"support@bland.ai"},"termsOfService":"https://www.bland.ai/terms"},"servers":[{"url":"https://www.bland.ai","description":"Production"}],"security":[],"tags":[{"name":"Content","description":"Published blog content, for indexing and retrieval."},{"name":"Stats","description":"Public platform metrics rendered on the marketing site."},{"name":"Forms","description":"Lead and contact capture. Write endpoints; protected by Cloudflare Turnstile and per-IP rate limiting rather than API keys."}],"paths":{"/api/blog":{"get":{"operationId":"listBlogPosts","summary":"List published blog posts","description":"Returns a page of published editorial blog posts ordered newest first, with the total count and a `hasMore` cursor flag. Body content is excluded. Call getBlogPostMarkdown with a slug from `posts[].slug.current` to read a post. No authentication and no rate limit. Responses are cached for one hour and invalidated when content is published.","tags":["Content"],"parameters":[{"name":"page","in":"query","required":false,"description":"1-based page number. Values below 1 are clamped to 1.","schema":{"type":"integer","format":"int32","minimum":1,"default":1},"example":1},{"name":"limit","in":"query","required":false,"description":"Posts per page. Clamped to the range 1-100.","schema":{"type":"integer","format":"int32","minimum":1,"maximum":100,"default":50},"example":50}],"responses":{"200":{"description":"A page of blog posts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogListResponse"},"example":{"posts":[{"_id":"a1b2c3d4","title":"How voice agents handle FNOL","slug":{"current":"how-voice-agents-handle-fnol"},"excerpt":"What changes when the first notice of loss is a phone call an agent answers.","publishedAt":"2026-07-14T09:00:00.000Z","categories":["Insurance"],"contentSource":"editorial","author":{"name":"Kal Sanjaya"}}],"total":214,"page":1,"limit":50,"hasMore":true}}}},"500":{"description":"The CMS query failed. Retry; the endpoint is cached and transient failures are rare.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlogListErrorResponse"},"example":{"posts":[],"total":0,"page":1,"limit":50,"hasMore":false,"ok":false,"error":{"code":"BLOG_FETCH_FAILED","message":"Could not load blog posts right now.","hint":"Retry in a few seconds. If it persists the CMS is unavailable; the rendered pages under /blog remain readable.","status":500,"docs":"https://www.bland.ai/openapi.json"}}}}}}}},"/api/blog/{slug}/markdown":{"get":{"operationId":"getBlogPostMarkdown","summary":"Get one blog post as markdown","description":"Returns the full body of a published blog post rendered as plain markdown, with a title heading, byline and excerpt blockquote. Intended for AI crawlers and retrieval pipelines that want the text without HTML. Success responses are `text/markdown`, not JSON; errors are JSON. No authentication and no rate limit; cached for one hour.","tags":["Content"],"parameters":[{"name":"slug","in":"path","required":true,"description":"Post slug, as returned in `posts[].slug.current` by listBlogPosts.","schema":{"type":"string","minLength":1},"example":"how-voice-agents-handle-fnol"}],"responses":{"200":{"description":"The post body as markdown.","headers":{"Cache-Control":{"description":"public, s-maxage=3600, stale-while-revalidate=86400","schema":{"type":"string"}}},"content":{"text/markdown":{"schema":{"type":"string","description":"UTF-8 markdown document."},"example":"# How voice agents handle FNOL\n\nBy Kal Sanjaya | Published 2026-07-14\n\n> What changes when the first notice of loss is a phone call an agent answers.\n\n## The intake problem\n\n..."}}},"404":{"description":"No published post matches that slug.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"ok":false,"error":{"code":"BLOG_POST_NOT_FOUND","message":"No published blog post exists with that slug.","hint":"List valid slugs with GET /api/blog and retry using a value from posts[].slug.current.","status":404,"docs":"https://www.bland.ai/openapi.json","field":"slug"}}}}},"500":{"description":"The CMS query failed. Retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"ok":false,"error":{"code":"BLOG_FETCH_FAILED","message":"Could not load this blog post right now.","hint":"Retry in a few seconds. Use /blog to browse published articles.","status":500,"docs":"https://www.bland.ai/openapi.json"}}}}}}}},"/api/stats/calls":{"get":{"operationId":"getCallStats","summary":"Get modeled calls-resolved counter","description":"Returns the modeled calls-resolved estimate used by the marketing homepage as two anchors five minutes apart. This is a mathematical model, not a live platform or warehouse query, so a caller can derive the apparent rate without the endpoint exposing a derivative. Values are bucketed to 5-minute boundaries and are identical across regions for the same bucket. No authentication and no rate limit; edge-cached for 60 seconds with a 120 second stale-while-revalidate window.","tags":["Stats"],"responses":{"200":{"description":"Current and previous cumulative anchors.","headers":{"Cache-Control":{"description":"public, max-age=60, stale-while-revalidate=120","schema":{"type":"string"}},"X-Data-Freshness":{"description":"ISO 8601 bucket timestamp of the served sample. Mirrors `asOf` in the body.","schema":{"type":"string","format":"date-time"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallStatsResponse"},"example":{"count":531204778,"asOf":"2026-08-22T14:35:00.000Z","previous":{"count":531203903,"asOf":"2026-08-22T14:30:00.000Z"}}}}},"500":{"description":"The counter could not be computed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"ok":false,"error":{"code":"STATS_UNAVAILABLE","message":"Call statistics could not be computed right now.","hint":"Retry in a few seconds. This endpoint is stateless, so a repeat request normally succeeds.","status":500,"docs":"https://www.bland.ai/openapi.json"}}}}}}}},"/api/contact":{"post":{"operationId":"submitContactForm","summary":"Submit the contact form","description":"Submits a general contact enquiry. The submission is forwarded to HubSpot; a HubSpot outage is logged server-side and still returns 200, so a success response means \"accepted\", not \"delivered to CRM\". No authentication. Cloudflare Turnstile is enforced only when the site is configured with a Turnstile secret. There is no per-IP rate limit on this endpoint. Use submitLead for programmatic lead capture.","tags":["Forms"],"requestBody":{"required":true,"description":"Contact form fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactRequest"},"example":{"name":"Jane Doe","email":"jane@acme.com","company":"Acme Inc","message":"We are evaluating voice AI for claims intake."}}}},"responses":{"200":{"description":"Submission accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSuccessResponse"},"example":{"success":true}}}},"400":{"description":"A required field is missing, or Turnstile is enforced and no token was supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactErrorResponse"},"example":{"success":false,"message":"Missing required fields","ok":false,"error":{"code":"MISSING_REQUIRED_FIELDS","message":"Missing required fields","hint":"Include non-empty `name`, `email` and `message` in the JSON body.","status":400,"docs":"https://www.bland.ai/openapi.json"}}}}},"403":{"description":"A Turnstile token was supplied and Cloudflare rejected it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactErrorResponse"},"example":{"success":false,"message":"Verification failed.","code":"TURNSTILE_INVALID","ok":false,"error":{"code":"TURNSTILE_INVALID","message":"Verification failed.","hint":"Re-render the Cloudflare Turnstile widget, obtain a fresh token, and submit again. Tokens are single-use.","status":403,"docs":"https://www.bland.ai/openapi.json"}}}}},"500":{"description":"The request body was not valid JSON, or the handler threw.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactErrorResponse"},"example":{"success":false,"message":"Internal server error","ok":false,"error":{"code":"CONTACT_INTERNAL_ERROR","message":"Internal server error","hint":"Verify the request body is valid JSON with Content-Type: application/json, then retry.","status":500,"docs":"https://www.bland.ai/openapi.json"}}}}}}}},"/api/leads":{"post":{"operationId":"submitLead","summary":"Submit a lead to a named form","description":"Universal lead capture. The `formSlug` selects a form definition in the CMS, which determines the accepted fields, their validation rules, the post-submit behaviour and the downstream destinations. The submission is stored synchronously; HubSpot and other destinations are pushed after the response is sent, so a 200 means \"stored\", not \"synced\". No authentication. Rate limited per IP per form to the form's `rateLimitPerMinute` (default 5 requests per 60 seconds), answered with 429 RATE_LIMITED. Submissions are idempotent only when a valid UUID v4 `event_id` is supplied.","tags":["Forms"],"requestBody":{"required":true,"description":"Lead payload. Field names inside `values` are defined by the form.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadSubmissionRequest"},"example":{"formSlug":"newsletter","values":{"email":"jane@acme.com"},"event_id":"b7f4c1a2-6f0e-4a1b-9c3d-7e2f5a8b1c40"}}}},"responses":{"200":{"description":"Lead accepted and stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadSuccessResponse"},"example":{"ok":true,"postSubmit":{"kind":"message","message":"Thanks. We will be in touch."},"gtmEventName":"newsletter_signup","submissionId":"1f0a9c7e-2b31-4b64-9f0a-2c9b7e51d3aa"}}}},"400":{"description":"Malformed body (INVALID_BODY) or a field that failed the form's validation rules: REQUIRED, INVALID_EMAIL, INVALID_PHONE, INVALID_URL, INVALID_OPTION, TOO_LONG, PATTERN_MISMATCH, DOMAIN_NOT_ALLOWED, DOMAIN_BLOCKED, FREE_EMAIL_BLOCKED or DISPOSABLE_EMAIL_BLOCKED. Field-scoped failures set `error.field`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadErrorResponse"},"example":{"ok":false,"error":{"code":"INVALID_EMAIL","message":"Enter a valid email address.","hint":"Correct the value named in `error.field` and resubmit.","status":400,"docs":"https://www.bland.ai/openapi.json","field":"email"}}}}},"403":{"description":"A Turnstile token was supplied and Cloudflare explicitly rejected it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadErrorResponse"},"example":{"ok":false,"error":{"code":"TURNSTILE_INVALID","message":"Verification failed.","hint":"Re-render the Cloudflare Turnstile widget, obtain a fresh token, and submit again. Tokens are single-use.","status":403,"docs":"https://www.bland.ai/openapi.json"}}}}},"404":{"description":"No form definition exists for the supplied `formSlug`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadErrorResponse"},"example":{"ok":false,"error":{"code":"FORM_NOT_FOUND","message":"No form found with slug \"book-a-demo\".","hint":"Use the formSlug rendered by the page that hosts the form. Slugs are managed in the CMS and are not enumerable through this API.","status":404,"docs":"https://www.bland.ai/openapi.json"}}}}},"429":{"description":"Per-IP, per-form rate limit exceeded. The window is 60 seconds and the default allowance is 5 submissions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadErrorResponse"},"example":{"ok":false,"error":{"code":"RATE_LIMITED","message":"Too many submissions. Try again in a minute.","hint":"Wait 60 seconds before retrying. The limit is per IP address per form.","status":429,"docs":"https://www.bland.ai/openapi.json"}}}}},"500":{"description":"The submission could not be persisted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadErrorResponse"},"example":{"ok":false,"error":{"code":"SUBMISSION_STORE_FAILED","message":"Submission could not be saved. Please try again shortly.","hint":"Retry with the SAME UUID v4 `event_id` and the same form and values to avoid a duplicate submission.","status":500,"docs":"https://www.bland.ai/openapi.json"}}}}},"503":{"description":"The form definition could not be loaded from the CMS. No submission was stored.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"ok":false,"error":{"code":"FORM_LOOKUP_FAILED","message":"The form could not be loaded. Please try again shortly.","hint":"The form service is temporarily unavailable. Wait a few seconds, then retry the same request.","status":503,"docs":"https://www.bland.ai/openapi.json"}}}}}}}}},"components":{"schemas":{"ApiErrorDetail":{"type":"object","title":"ApiErrorDetail","description":"Machine-readable description of a single failure: what happened, and what to do about it.","required":["code","message","hint","status","docs"],"additionalProperties":false,"properties":{"code":{"type":"string","description":"Stable, machine-readable error identifier in SCREAMING_SNAKE_CASE. Branch on this, never on `message`.","examples":["FORM_NOT_FOUND","RATE_LIMITED","INVALID_BODY"]},"message":{"type":"string","description":"Human-readable explanation of the failure. Safe to display to an end user.","examples":["No form found with slug \"book-a-demo\"."]},"hint":{"type":"string","description":"Resolution hint: the concrete next action a caller should take to succeed on retry.","examples":["Fetch the list of available forms from the page that renders them, then retry."]},"status":{"type":"integer","format":"int32","minimum":400,"maximum":599,"description":"Mirrors the HTTP status code so the body is self-describing in isolation.","examples":[404]},"docs":{"type":"string","format":"uri","description":"Absolute URL of this OpenAPI document.","examples":["https://www.bland.ai/openapi.json"]},"field":{"type":"string","description":"Present only for field-scoped validation failures. Names the offending request field.","examples":["email"]}}},"ApiError":{"type":"object","title":"ApiError","description":"Standard JSON error envelope returned by every public endpoint for 4xx and 5xx responses. Individual endpoints may carry additional legacy top-level keys for backward compatibility; `ok` and `error` are always present.","required":["ok","error"],"properties":{"ok":{"type":"boolean","const":false,"description":"Always false on an error response."},"error":{"$ref":"#/components/schemas/ApiErrorDetail"}}},"SanitySlug":{"type":"object","title":"SanitySlug","description":"Sanity slug object. `current` is the URL path segment.","required":["current"],"properties":{"current":{"type":"string","description":"URL path segment for the document.","examples":["how-voice-agents-handle-fnol"]},"_type":{"type":"string","description":"Sanity type discriminator.","examples":["slug"]}}},"SanityImage":{"type":"object","title":"SanityImage","description":"Raw Sanity image reference as stored in the CMS. Resolve it to a CDN URL with the Sanity image pipeline; the asset id is in `asset._ref`.","properties":{"_type":{"type":"string","examples":["image"]},"asset":{"type":"object","properties":{"_ref":{"type":"string","description":"Sanity asset id.","examples":["image-abc123-1200x630-jpg"]},"_type":{"type":"string","examples":["reference"]}}},"alt":{"type":"string","description":"Alt text, when the editor supplied one."}}},"BlogAuthorSummary":{"type":"object","title":"BlogAuthorSummary","description":"Dereferenced author, trimmed to what the index needs.","properties":{"name":{"type":"string","examples":["Kal Sanjaya"]},"avatar":{"description":"Author portrait, or null when unset.","anyOf":[{"$ref":"#/components/schemas/SanityImage"},{"type":"null"}]}}},"BlogPostSummary":{"type":"object","title":"BlogPostSummary","description":"One blog post as returned by the index endpoint. Body content is NOT included. Fetch GET /api/blog/{slug}/markdown for the full text.","required":["_id","title","slug"],"properties":{"_id":{"type":"string","description":"Sanity document id.","examples":["a1b2c3d4"]},"title":{"type":"string","examples":["How voice agents handle FNOL"]},"slug":{"$ref":"#/components/schemas/SanitySlug"},"excerpt":{"type":["string","null"],"description":"Short summary. Null or absent when the editor left it blank."},"featuredImage":{"description":"Hero image, or null when the editor left it blank.","anyOf":[{"$ref":"#/components/schemas/SanityImage"},{"type":"null"}]},"publishedAt":{"type":["string","null"],"format":"date-time","description":"ISO 8601 publication timestamp. Posts are ordered by this, newest first.","examples":["2026-07-14T09:00:00.000Z"]},"categories":{"type":["array","null"],"description":"Free-form category labels. Null when the post has none.","items":{"type":"string"},"examples":[["Product","Engineering"]]},"contentSource":{"type":["string","null"],"description":"Editorial provenance of the post. The index endpoint returns editorial posts only.","examples":["editorial"]},"author":{"description":"Dereferenced author, or null when the post has no author set.","anyOf":[{"$ref":"#/components/schemas/BlogAuthorSummary"},{"type":"null"}]}}},"BlogListResponse":{"type":"object","title":"BlogListResponse","description":"A page of blog posts plus the cursor state needed to request the next page.","required":["posts","total","page","limit","hasMore"],"properties":{"posts":{"type":"array","description":"Posts for the requested page, newest first.","items":{"$ref":"#/components/schemas/BlogPostSummary"}},"total":{"type":"integer","format":"int32","minimum":0,"description":"Total number of published editorial posts across all pages.","examples":[214]},"page":{"type":"integer","format":"int32","minimum":1,"description":"Echo of the resolved 1-based page number.","examples":[2]},"limit":{"type":"integer","format":"int32","minimum":1,"maximum":100,"description":"Echo of the resolved page size.","examples":[50]},"hasMore":{"type":"boolean","description":"True when a further page exists. Request `page + 1` to continue."}}},"BlogListErrorResponse":{"title":"BlogListErrorResponse","description":"Error envelope for the blog index. For backward compatibility the handler ALSO returns an empty result set with the same keys as a success response, so existing clients that read `posts` keep working. Check `ok` to distinguish.","allOf":[{"$ref":"#/components/schemas/ApiError"},{"type":"object","required":["posts","total","page","limit","hasMore"],"properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/BlogPostSummary"},"maxItems":0},"total":{"type":"integer","format":"int32","const":0},"page":{"type":"integer","format":"int32","minimum":1},"limit":{"type":"integer","format":"int32","minimum":1,"maximum":100},"hasMore":{"type":"boolean","const":false}}}]},"CallStatsAnchor":{"type":"object","title":"CallStatsAnchor","description":"A single modeled cumulative estimate, bucketed to a 5-minute boundary.","required":["count","asOf"],"additionalProperties":false,"properties":{"count":{"type":"integer","format":"int64","minimum":0,"description":"Modeled cumulative calls resolved as of `asOf`, not a measured warehouse total.","examples":[531204778]},"asOf":{"type":"string","format":"date-time","description":"ISO 8601 UTC timestamp of the 5-minute bucket this sample anchors to.","examples":["2026-08-22T14:35:00.000Z"]}}},"CallStatsResponse":{"type":"object","title":"CallStatsResponse","description":"Two cumulative anchors five minutes apart. Derive an apparent rate from `(count - previous.count) / (asOf - previous.asOf)`; the endpoint intentionally does not return a derivative.","required":["count","asOf","previous"],"additionalProperties":false,"properties":{"count":{"type":"integer","format":"int64","minimum":0,"description":"Modeled cumulative calls resolved as of the current bucket.","examples":[531204778]},"asOf":{"type":"string","format":"date-time","description":"ISO 8601 UTC timestamp of the current 5-minute bucket.","examples":["2026-08-22T14:35:00.000Z"]},"previous":{"$ref":"#/components/schemas/CallStatsAnchor"}}},"ContactRequest":{"type":"object","title":"ContactRequest","description":"Contact form payload.","required":["name","email","message"],"properties":{"name":{"type":"string","minLength":1,"description":"Full name of the person getting in touch.","examples":["Jane Doe"]},"email":{"type":"string","format":"email","minLength":1,"description":"Reply-to email address.","examples":["jane@acme.com"]},"company":{"type":"string","description":"Optional company name. Forwarded to HubSpot as an empty string when omitted.","examples":["Acme Inc"]},"message":{"type":"string","minLength":1,"description":"Free-text message.","examples":["We are evaluating voice AI for claims intake."]},"turnstileToken":{"type":"string","description":"Cloudflare Turnstile token. Only enforced when the site is configured with a Turnstile secret; a missing token then yields TURNSTILE_MISSING (400) and an invalid one TURNSTILE_INVALID (403)."}}},"ContactSuccessResponse":{"type":"object","title":"ContactSuccessResponse","description":"Accepted. The submission is forwarded to HubSpot; a downstream HubSpot failure is logged server-side and does NOT change this response.","required":["success"],"additionalProperties":false,"properties":{"success":{"type":"boolean","const":true}}},"ContactErrorResponse":{"title":"ContactErrorResponse","description":"Error envelope for /api/contact. Retains the pre-existing `success: false` and `message` top-level keys for backward compatibility; `code` is present on Turnstile rejections.","allOf":[{"$ref":"#/components/schemas/ApiError"},{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean","const":false},"message":{"type":"string","description":"Legacy mirror of `error.message`."},"code":{"type":"string","description":"Legacy mirror of `error.code`, Turnstile paths only."}}}]},"AttributionTouch":{"type":"object","title":"AttributionTouch","description":"One marketing touch. Unknown keys are dropped server-side; each value is truncated to 1024 characters.","additionalProperties":false,"properties":{"utm_source":{"type":"string","maxLength":1024},"utm_medium":{"type":"string","maxLength":1024},"utm_campaign":{"type":"string","maxLength":1024},"utm_term":{"type":"string","maxLength":1024},"utm_content":{"type":"string","maxLength":1024},"gclid":{"type":"string","maxLength":1024,"description":"Google Ads click id."},"fbclid":{"type":"string","maxLength":1024,"description":"Meta click id."},"li_fat_id":{"type":"string","maxLength":1024,"description":"LinkedIn click id."},"msclkid":{"type":"string","maxLength":1024,"description":"Microsoft Ads click id."},"ttclid":{"type":"string","maxLength":1024,"description":"TikTok click id."},"twclid":{"type":"string","maxLength":1024,"description":"X/Twitter click id."},"referrer":{"type":"string","maxLength":1024},"landing_path":{"type":"string","maxLength":1024},"seen_at":{"type":"string","format":"date-time","maxLength":1024,"description":"ISO 8601 timestamp of the touch."}}},"LeadAttribution":{"type":"object","title":"LeadAttribution","description":"First-touch / last-touch attribution plus page context. Every field is optional.","properties":{"first":{"$ref":"#/components/schemas/AttributionTouch"},"last":{"$ref":"#/components/schemas/AttributionTouch"},"pageUri":{"type":"string","maxLength":2048,"description":"URL of the page the form was submitted from. Falls back to the Referer header."},"referrer":{"type":"string","maxLength":2048,"description":"Document referrer."},"hubspotutk":{"type":"string","maxLength":256,"description":"HubSpot user token cookie value. Read from the `hubspotutk` cookie when omitted."}}},"LeadFieldValues":{"type":"object","title":"LeadFieldValues","description":"Field values keyed by the field names defined on the form in the CMS. Validation (required, email/phone/URL format, allowed options, max length, domain policy) is driven entirely by that form definition, so the accepted keys vary per `formSlug`.","additionalProperties":{"type":["string","number","boolean","array","null"],"items":{"type":"string"}},"examples":[{"email":"jane@acme.com","first_name":"Jane","company":"Acme Inc"}]},"LeadSubmissionRequest":{"type":"object","title":"LeadSubmissionRequest","description":"Universal lead submission payload.","required":["formSlug","values"],"properties":{"formSlug":{"type":"string","minLength":1,"description":"Slug of the form definition in the CMS. Determines validation and routing.","examples":["newsletter","book-a-demo"]},"values":{"$ref":"#/components/schemas/LeadFieldValues"},"turnstileToken":{"type":"string","description":"Cloudflare Turnstile token. Required by default per form (`requireTurnstile`). A missing token or an unreachable Cloudflare fails OPEN; a supplied token that Cloudflare explicitly rejects fails CLOSED with 403 TURNSTILE_INVALID."},"attribution":{"$ref":"#/components/schemas/LeadAttribution"},"event_id":{"type":"string","format":"uuid","pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89aAbB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$","description":"Client-minted UUID v4 conversion id. Replaying the same valid UUID v4 `event_id` returns the original success envelope without re-firing HubSpot, downstream destinations, or ad conversions. Omitted or invalid IDs disable idempotency for compatibility with older clients; automatic retries can then create duplicate leads. Use crypto.randomUUID() and retain the same ID for retries.","examples":["b7f4c1a2-6f0e-4a1b-9c3d-7e2f5a8b1c40"]}}},"LeadPostSubmit":{"title":"LeadPostSubmit","description":"What the client should render after a successful submission, as configured on the form.","oneOf":[{"type":"object","title":"Message","required":["kind","message"],"properties":{"kind":{"type":"string","const":"message"},"message":{"type":"string"},"heading":{"type":"string"}}},{"type":"object","title":"Redirect","required":["kind","redirectUrl"],"properties":{"kind":{"type":"string","const":"redirect"},"redirectUrl":{"type":"string","format":"uri"},"redirectDelaySeconds":{"type":"number","minimum":0}}},{"type":"object","title":"DefaultScheduler","required":["kind"],"properties":{"kind":{"type":"string","const":"default-scheduler"},"schedulerUrl":{"type":"string","description":"Default.com scheduler iframe URL. Blank renders a \"scheduler not configured\" fallback."},"heading":{"type":"string"},"subheading":{"type":"string"},"minHeight":{"type":"number","description":"Minimum iframe height in pixels. Defaults to 720."},"prefillFields":{"type":"array","items":{"type":"string"},"description":"Submitted field names passed to the iframe URL as query params. Defaults to ['email', 'firstname', 'company']."}}}]},"LeadSuccessResponse":{"type":"object","title":"LeadSuccessResponse","description":"Accepted. Storage is synchronous; HubSpot and other destinations run after the response is sent. `submissionId` is null when the submission was intentionally accepted but not stored (bot timing trap or blocklist hit), and the whole object reduces to `{ \"ok\": true }` when a standing deletion request suppresses capture.","required":["ok"],"properties":{"ok":{"type":"boolean","const":true},"postSubmit":{"$ref":"#/components/schemas/LeadPostSubmit"},"gtmEventName":{"type":"string","description":"Analytics event name the client should push, when the form defines one."},"submissionId":{"type":["string","null"],"description":"Stored submission id, or null when nothing was stored. Never treat null as a failure.","examples":["1f0a9c7e-2b31-4b64-9f0a-2c9b7e51d3aa"]}}},"LeadErrorResponse":{"title":"LeadErrorResponse","description":"Error envelope for /api/leads. `error.field` names the offending form field on validation failures.","allOf":[{"$ref":"#/components/schemas/ApiError"}]}}},"externalDocs":{"description":"Bland product API and platform documentation (api.bland.ai/v1), the surface most agents actually want","url":"https://docs.bland.ai"},"x-related-apis":[{"name":"Bland product API","description":"Voice platform API: calls, conversational pathways, voices and TTS, knowledge bases, numbers, tools, contacts, batches, analysis. Documented in prose; no OpenAPI document is published for it at this revision, so no operations are described here.","baseUrl":"https://api.bland.ai/v1","documentationUrl":"https://docs.bland.ai","llmsTxtUrl":"https://docs.bland.ai/llms.txt","authentication":"API key in the Authorization header.","openapiUrl":null},{"name":"Bland MCP server","description":"Production Model Context Protocol server exposing the Bland product API as callable tools. The recommended machine interface for agents.","url":"https://api.bland.ai/v1/mcp","transport":"Streamable HTTP","authentication":"Bland API key or OAuth token in the Authorization Bearer header. OAuth tokens are scoped to this MCP endpoint.","oauthResourceMetadataUrl":"https://api.bland.ai/.well-known/oauth-protected-resource/v1/mcp","oauthAuthorizationServerMetadataUrl":"https://api.bland.ai/.well-known/oauth-authorization-server/authorization","scopes":["mcp:read","mcp:write"],"rateLimit":"120 requests per minute per organization.","documentationUrl":"https://docs.bland.ai/integrations/mcp/overview"}]}