# Account AI copilot Source: https://docs.flowyte.com/api-reference/agents/account-ai-copilot /openapi.yaml post /assist Sends a natural-language request to an AI copilot that can configure your entire account and review call history by calling your own API operations on your behalf. It streams progress and returns its reply with any changes applied. Actions that could be destructive are paused and listed for you to approve; resend them marked as confirmed to carry them out. # Compare a version with the draft Source: https://docs.flowyte.com/api-reference/agents/compare-a-version-with-the-draft /openapi.yaml get /agents/{id}/versions/{versionId}/diff Returns the differences between a published version and the agent's current draft, so you can review exactly what changed before publishing. # Configure an agent with AI Source: https://docs.flowyte.com/api-reference/agents/configure-an-agent-with-ai /openapi.yaml post /agents/{id}/assist Sends a natural-language request to an AI assistant that edits this agent for you. It streams progress and returns the assistant's reply along with the list of changes it applied to the agent. # Create an agent Source: https://docs.flowyte.com/api-reference/agents/create-an-agent /openapi.yaml post /agents Creates a new agent. The agent starts as a draft; publish it before it can take live calls or chats. # Delete an agent Source: https://docs.flowyte.com/api-reference/agents/delete-an-agent /openapi.yaml delete /agents/{id} Deletes an agent so it no longer appears in your list or handles calls and chats. # Get an agent Source: https://docs.flowyte.com/api-reference/agents/get-an-agent /openapi.yaml get /agents/{id} Returns a single agent by its ID, including its current draft configuration. # Get an AI copilot thread Source: https://docs.flowyte.com/api-reference/agents/get-an-ai-copilot-thread /openapi.yaml get /assist/threads/{threadId} Returns a single AI copilot conversation and all of its messages so you can review or resume it. # Get the pre-publish report Source: https://docs.flowyte.com/api-reference/agents/get-the-pre-publish-report /openapi.yaml get /agents/{id}/prepublish-report Returns a plain-language summary of how the agent will behave once published, including what it can answer, what it will decline, and what information it discloses. Use it to review the agent before going live. # List agent versions Source: https://docs.flowyte.com/api-reference/agents/list-agent-versions /openapi.yaml get /agents/{id}/versions Returns a paginated history of the agent's published versions. # List agents Source: https://docs.flowyte.com/api-reference/agents/list-agents /openapi.yaml get /agents Returns a paginated list of your agents. You can filter by status (draft or published) or search by name. # List AI copilot threads Source: https://docs.flowyte.com/api-reference/agents/list-ai-copilot-threads /openapi.yaml get /assist/threads Returns your saved AI copilot conversations, most recent first. Optionally filter to a single agent with the agentId query parameter. # Publish an agent Source: https://docs.flowyte.com/api-reference/agents/publish-an-agent /openapi.yaml post /agents/{id}/publish Publishes the agent's current draft as a new fixed version that live calls and chats will use. You can include an optional note describing the change. # Roll back to a previous version Source: https://docs.flowyte.com/api-reference/agents/roll-back-to-a-previous-version /openapi.yaml post /agents/{id}/rollback Makes a previously published version the live one again. Pass the versionId of the version you want to restore. # Scaffold a draft agent Source: https://docs.flowyte.com/api-reference/agents/scaffold-a-draft-agent /openapi.yaml post /agents/scaffold Generates a draft agent automatically from a website URL, a written procedure, or a plain-language description. Streams progress while it builds and returns the finished draft agent at the end of the stream. Review and publish the draft before it goes live. # Set the agent's voice Source: https://docs.flowyte.com/api-reference/agents/set-the-agents-voice /openapi.yaml put /agents/{id}/voice Assigns a voice to the agent for a given language, defaulting to the agent's primary language. This is a shortcut for changing the voice without sending a full agent update. Look up a voice ID with the voice catalog or voice search endpoints first. # Update an agent Source: https://docs.flowyte.com/api-reference/agents/update-an-agent /openapi.yaml patch /agents/{id} Updates one or more fields on an agent's draft. To avoid overwriting concurrent edits, send the version you last read in the If-Match header; if it is out of date the request returns 409 with the current version and agent state so you can refetch and retry. Changes apply to the draft only and take effect once you publish the agent. # List activity logs Source: https://docs.flowyte.com/api-reference/auditlogs/list-activity-logs /openapi.yaml get /audit-logs Returns a log of actions taken in your account, showing who performed each request, the endpoint and method, the response status, and when it happened. Filter by date range and by who performed the action (a user, API key, visitor, or the system). Results are paginated. # Add credit to the wallet Source: https://docs.flowyte.com/api-reference/billing/add-credit-to-the-wallet /openapi.yaml post /billing/wallet/credit Adds funds directly to the prepaid wallet without a card payment, and returns the updated balance. Useful for manual adjustments or onboarding. # Change subscription plan Source: https://docs.flowyte.com/api-reference/billing/change-subscription-plan /openapi.yaml post /billing/subscription Switches your organization to the plan you specify. Returns the updated subscription. # Configure wallet auto-reload Source: https://docs.flowyte.com/api-reference/billing/configure-wallet-auto-reload /openapi.yaml put /billing/wallet/auto-reload Sets whether the wallet automatically tops up by a fixed amount when its balance falls below a threshold. Returns the updated wallet. # Get current subscription Source: https://docs.flowyte.com/api-reference/billing/get-current-subscription /openapi.yaml get /billing/subscription Returns your organization's current plan and the date it next renews. # Get wallet balance Source: https://docs.flowyte.com/api-reference/billing/get-wallet-balance /openapi.yaml get /billing/wallet Returns your prepaid wallet's current balance along with its auto-reload settings. # List billing plans Source: https://docs.flowyte.com/api-reference/billing/list-billing-plans /openapi.yaml get /billing/plans Returns the subscription plans available to your organization, including each plan's monthly price, included minutes, and overage rate. # List invoices Source: https://docs.flowyte.com/api-reference/billing/list-invoices /openapi.yaml get /billing/invoices Returns your organization's invoices, newest first, each with a link to download the invoice PDF. # List itemized usage charges Source: https://docs.flowyte.com/api-reference/billing/list-itemized-usage-charges /openapi.yaml get /billing/usage/items Returns individual usage charges (one per call, chat, or transfer) within a date range, newest first. # List usage records Source: https://docs.flowyte.com/api-reference/billing/list-usage-records /openapi.yaml get /billing/usage Returns a paginated list of daily usage records, each with the minutes used and the resulting cost. Filter by date range or by agent. # Top up the wallet by card Source: https://docs.flowyte.com/api-reference/billing/top-up-the-wallet-by-card /openapi.yaml post /billing/wallet/topup Starts a card payment to add funds to the prepaid wallet and returns a payment client secret you use to complete the charge. Pass an idempotency key to safely retry without double-charging. # Create a chat session Source: https://docs.flowyte.com/api-reference/chat/create-a-chat-session /openapi.yaml post /chat/sessions Starts a new chat session with an agent and returns its ID. Send messages to the session to converse with the agent. # End a chat session Source: https://docs.flowyte.com/api-reference/chat/end-a-chat-session /openapi.yaml post /chat/sessions/{id}/end Closes a chat session. No further messages can be sent once it is ended. # Get a chat session Source: https://docs.flowyte.com/api-reference/chat/get-a-chat-session /openapi.yaml get /chat/sessions/{id} Returns a chat session and its current state. # Get widget bootstrap config Source: https://docs.flowyte.com/api-reference/chat/get-widget-bootstrap-config /openapi.yaml get /widget/bootstrap Returns the public configuration for the browser widget (theme, copy, and behavior) for a given publishable key. It contains no secrets and is safe to call from the browser, but the request's origin must be allowed for the key. # List chat messages Source: https://docs.flowyte.com/api-reference/chat/list-chat-messages /openapi.yaml get /chat/sessions/{id}/messages Returns the paginated message history for a chat session. # OpenAI-compatible chat completions Source: https://docs.flowyte.com/api-reference/chat/openai-compatible-chat-completions /openapi.yaml post /chat/completions An OpenAI-compatible chat completions endpoint where the model field is your agent ID. Set stream to true for streamed chunks. Existing OpenAI SDK clients work by pointing the base URL at this API. # Send a chat message Source: https://docs.flowyte.com/api-reference/chat/send-a-chat-message /openapi.yaml post /chat/sessions/{id}/messages Sends a message to a chat session and returns the agent's reply. Set stream to true to receive the reply as a server-sent event stream; otherwise the complete reply is returned in a single response. # Start a widget chat session Source: https://docs.flowyte.com/api-reference/chat/start-a-widget-chat-session /openapi.yaml post /chat/widget/sessions Starts a chat session for the embeddable browser widget, authenticated with a publishable key from an allowed origin. Creates an anonymous visitor and returns the connection details; a bot-check token may be required. # Get caller verification settings Source: https://docs.flowyte.com/api-reference/guardrails/get-caller-verification-settings /openapi.yaml get /agents/{agentId}/caller-verification Returns the agent's caller-identity verification configurations, including the verification method (such as date of birth plus postcode, an SMS one-time code, an account PIN, or knowledge-based questions) and the fields required to confirm a caller. # Get guardrail policies Source: https://docs.flowyte.com/api-reference/guardrails/get-guardrail-policies /openapi.yaml get /agents/{agentId}/guardrails Returns the agent's guardrail policies: rules that constrain what the agent may do, such as verifying a caller before disclosing information, limiting which fields can be shared, gating actions that write data, rate limits, business hours, and content rules. # Replace caller verification settings Source: https://docs.flowyte.com/api-reference/guardrails/replace-caller-verification-settings /openapi.yaml put /agents/{agentId}/caller-verification Replaces the agent's full set of caller-verification configurations with the list you send. This is a full overwrite, so include every configuration you want to keep; any one you omit is removed. # Replace guardrail policies Source: https://docs.flowyte.com/api-reference/guardrails/replace-guardrail-policies /openapi.yaml put /agents/{agentId}/guardrails Replaces the agent's full set of guardrail policies with the list you send. This is a full overwrite, so include every policy you want to keep; any policy you omit is removed. # Add an integration's actions as skills Source: https://docs.flowyte.com/api-reference/integrations/add-an-integrations-actions-as-skills /openapi.yaml post /agents/{agentId}/integrations/{kind}/provision Turns a connected integration into ready-to-use skills on an agent: one skill per action, or every key action when none are specified. Re-running is safe, as actions that already exist as skills are skipped. Actions that still need configuration are created as disabled drafts until you complete their settings. # Auto-map a binding from a goal Source: https://docs.flowyte.com/api-reference/integrations/auto-map-a-binding-from-a-goal /openapi.yaml post /agents/{agentId}/integrations/{kind}/bindings/auto Describe what you want in plain language ("look up a caller by phone and return their open tickets") and the AI assistant proposes a binding over the provider's discovered schema: which operation to call, how to map your agent's inputs to its arguments, and which fields to return. The proposal is validated against the schema and saved as a disabled draft for you to review before you enable it. Returns 422 if it can't produce a valid binding, 404 if the provider hasn't been discovered yet. # Browse a provider's objects Source: https://docs.flowyte.com/api-reference/integrations/browse-a-providers-objects /openapi.yaml get /integrations/{kind}/objects Returns the searchable, paginated list of objects in the provider's discovered schema, each with its capabilities and field, relationship, and operation counts, so you can pick what to map without loading the whole schema. Returns 404 if the provider hasn't been discovered yet. # Browse a provider's operations Source: https://docs.flowyte.com/api-reference/integrations/browse-a-providers-operations /openapi.yaml get /integrations/{kind}/operations Returns the operations a binding can call. By default only read operations; pass readOnly=false to include writes. Filter by object (operations that return that object) or q (a name substring). # Claim a Shopify App Store install Source: https://docs.flowyte.com/api-reference/integrations/claim-a-shopify-app-store-install /openapi.yaml post /integrations/{kind}/claim Completes a Shopify install that began on the Shopify App Store before the merchant had a Flowyte account. After installing from the App Store, the merchant signs in and POSTs `{ shop, claimSecret }` (the one-time secret carried in the post-install redirect); the stored token is then attached to their organization as a normal connection. Returns 403 if the secret doesn't match, or 404 if there's no pending install for that shop. Shopify only. # Connect an integration Source: https://docs.flowyte.com/api-reference/integrations/connect-an-integration /openapi.yaml post /integrations/{kind}/connect Starts a connection to an integration. For OAuth providers it returns a URL to send the user to for authorization. For API-key providers, send the credentials in the request body; they are stored securely and never returned. Returns an error if the provider is not yet available to connect. # Disconnect an integration Source: https://docs.flowyte.com/api-reference/integrations/disconnect-an-integration /openapi.yaml delete /integrations/{kind} Disconnects an integration from your organization and removes its stored credentials. You can reconnect it again later. # Discover an integration's schema Source: https://docs.flowyte.com/api-reference/integrations/discover-an-integrations-schema /openapi.yaml post /integrations/{kind}/discover Scans a connected integration and builds a normalized map of all its objects, fields, relationships, and operations. Running it again replaces the previous snapshot. It returns a summary of what was found; read the full schema back with the get-schema endpoint. The integration must be connected first. # Generate database setup scripts Source: https://docs.flowyte.com/api-reference/integrations/generate-database-setup-scripts /openapi.yaml get /integrations/{kind}/sql/scripts Generates the copy-paste SQL to create a scoped database user for the connection: a least-privilege read-only user (SELECT on current and future tables) and a scoped read-write script (INSERT/UPDATE on only the tables your write skills target — never blanket write). If you don't supply a password, a strong one is generated and returned once. Pure generation — it never touches your database. Pass database (required), and optionally ro_user, rw_user, tables (comma-separated write-skill tables), and password. # Get a connection's data scoping Source: https://docs.flowyte.com/api-reference/integrations/get-a-connections-data-scoping /openapi.yaml get /integrations/{kind}/scoping Returns the connection's data scoping — the tables and columns the agent may NOT see or bind. Empty lists mean everything is allowed. This is a privacy control you set on a connected SQL database, and it survives re-discovery. # Get a provider's preset pack Source: https://docs.flowyte.com/api-reference/integrations/get-a-providers-preset-pack /openapi.yaml get /integrations/{kind}/pack Returns the provider's Connector Pack: its curated preset skills (ready to install) plus guidance on how its operations behave, how to identify a caller, and what isn't possible. Use it to build skills and playbooks for the provider, or install the presets directly. Returns 404 if the provider ships no pack. # Get an integration's discovered schema Source: https://docs.flowyte.com/api-reference/integrations/get-an-integrations-discovered-schema /openapi.yaml get /integrations/{kind}/schema Returns the most recently discovered schema for a connected integration, including all of its objects, fields, relationships, and operations. Run the discover endpoint first; this returns an error if the integration has never been scanned. # Get one object's detail Source: https://docs.flowyte.com/api-reference/integrations/get-one-objects-detail /openapi.yaml get /integrations/{kind}/objects/{object} Returns one object's full detail: its fields (type, whether it's required, allowed values, relationship target, and whether it's sensitive), its operations, and its relationships to other objects. Returns 404 for an unknown object. # Install a provider's preset skills Source: https://docs.flowyte.com/api-reference/integrations/install-a-providers-preset-skills /openapi.yaml post /agents/{agentId}/integrations/{kind}/pack/install Installs a provider's curated preset skills (its Connector Pack) onto the agent in one call. Idempotent: a preset whose skill already exists is skipped and returned with created:false. Returns 404 if the agent doesn't exist or the provider ships no pack, 409 if the provider isn't connected for your organization. # List an integration's available actions Source: https://docs.flowyte.com/api-reference/integrations/list-an-integrations-available-actions /openapi.yaml get /integrations/{kind}/actions Returns the actions an integration offers, each of which can be turned into a skill. For every action you get its name, description, input parameters, and which configuration values you still need to provide before it can run. # List connected integrations Source: https://docs.flowyte.com/api-reference/integrations/list-connected-integrations /openapi.yaml get /integrations Returns your organization's integrations and their connection status. Stored credentials are never included in the response. # List the integration catalog Source: https://docs.flowyte.com/api-reference/integrations/list-the-integration-catalog /openapi.yaml get /integrations/catalog Returns every integration you can connect, including its name, category, description, logo, and how many actions it offers, combined with whether your organization has already connected it and the connected account label. # Look up a connected spreadsheet Source: https://docs.flowyte.com/api-reference/integrations/look-up-a-connected-spreadsheet /openapi.yaml get /integrations/{kind}/spreadsheet Helps you build a spreadsheet skill: given a spreadsheet link or id, it confirms your connected account can read it and returns the spreadsheet's title, its tabs, and each tab's column headers so you can pick a tab and map fields to the real columns. It is read-only and returns an error if the spreadsheet isn't connected or can't be accessed. # Map an integration's fields to a skill Source: https://docs.flowyte.com/api-reference/integrations/map-an-integrations-fields-to-a-skill /openapi.yaml post /agents/{agentId}/integrations/{kind}/bindings Creates a skill on an agent by mapping a discovered integration operation: pick the operation, map your agent's inputs to its arguments, and choose which provider fields to return. The mapping is validated against the integration's discovered schema and compiles to a runnable skill — created as a draft unless you enable it. Connect and discover the integration first. # Remove an integration's skills from an agent Source: https://docs.flowyte.com/api-reference/integrations/remove-an-integrations-skills-from-an-agent /openapi.yaml delete /agents/{agentId}/integrations/{kind}/provision Removes the skills that were created from a given integration on this agent. Skills you built by hand, and skills from other integrations, are left in place. # Search a provider's schema Source: https://docs.flowyte.com/api-reference/integrations/search-a-providers-schema /openapi.yaml get /integrations/{kind}/search Ranked keyword search across a connected provider's discovered schema (objects and fields), most-relevant first. When a schema is large, search for what your goal needs instead of browsing everything: each hit carries the object, field, type, relationship target, and badges (core, PII, payment-card). Use it to find the right field or object before authoring a binding, then expand the object for detail. Pass `q` (what to find) and an optional `limit`. # Set a connection's data scoping Source: https://docs.flowyte.com/api-reference/integrations/set-a-connections-data-scoping /openapi.yaml patch /integrations/{kind}/scoping Sets the tables and columns the agent may NOT see or bind for this connection. Blocked tables and columns disappear from the schema browser and are refused at bind time. Empty lists clear the blocks (allow all). # Test a database connection Source: https://docs.flowyte.com/api-reference/integrations/test-a-database-connection /openapi.yaml post /integrations/{kind}/sql/test For a SQL database connector (Postgres or MySQL), runs an ordered set of checks against the credentials you supply — reachability and TLS, authentication, reading the schema, a timed read probe, a read-only-session proof, and a live-call latency verdict — without storing anything (this powers the connect wizard's "Test connection" button). Always returns 200 with the ordered check results and an overall ok flag; a failed check is data to render, not an error. A host that resolves to a private, loopback, or metadata address is refused. # Using the API Source: https://docs.flowyte.com/api-reference/introduction Conventions that apply to every endpoint — base URL, auth, envelopes, errors, pagination, and streaming. The endpoint pages in this section are generated from the OpenAPI contract. This page covers the conventions that apply across all of them. ## Base URL ``` https://builder.flowyte.com/api/v1 ``` The API is versioned in the path and additive within `v1`. ## Authentication Send a secret key as a bearer token: `Authorization: Bearer flowyte_sk_…`. See [Authentication](/get-started/authentication) for keys, scopes, and the publishable-key path for the chat widget. ## Response envelope Successful responses are wrapped in an `ApiResponse` envelope: ```json theme={null} { "success": true, "data": { /* the resource */ } } ``` List responses use `PaginatedResponse` with **cursor** pagination — pass the returned cursor to fetch the next page rather than page numbers. ## Errors Errors use [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem+json (`application/problem+json`): ```json theme={null} { "type": "https://docs.flowyte.com/errors/no_published_version", "title": "No published version", "status": 409, "detail": "agent has no published version; publish it or retry with draftMode:true" } ``` Treat the `type` / `status` pair as the recovery contract. ## Streaming (SSE) Streaming endpoints (simulate, assist, probe) return `text/event-stream`. Each frame is `event:\ndata:\n\n`; the stream terminates with `event: done` (or `event: error`). Consume them with `fetch()` streaming so your `Authorization` header attaches. ## Idempotency Money-moving endpoints accept an idempotency key so a retry replays the original result instead of charging twice. ## Rate limits The API is rate-limited per organization. Limits are generous for normal use; if you hit one, back off and retry. # Add a knowledge source Source: https://docs.flowyte.com/api-reference/knowledge/add-a-knowledge-source /openapi.yaml post /agents/{agentId}/knowledge/sources Attaches a new knowledge source to the agent from a web page URL, an uploaded file, raw text, or an FAQ. Ingestion runs in the background, so the source is returned with a 'pending' status; poll the source until its status becomes 'indexed' before relying on it in conversations. # Delete a knowledge source Source: https://docs.flowyte.com/api-reference/knowledge/delete-a-knowledge-source /openapi.yaml delete /agents/{agentId}/knowledge/sources/{id} Permanently removes a knowledge source and all of the indexed content derived from it, so the agent will no longer use it. # Get a knowledge source Source: https://docs.flowyte.com/api-reference/knowledge/get-a-knowledge-source /openapi.yaml get /agents/{agentId}/knowledge/sources/{id} Returns a single knowledge source, including its full text content and current ingestion status. Poll this endpoint after creating a source to track progress until the status is 'indexed' (or 'error'). # List knowledge sources Source: https://docs.flowyte.com/api-reference/knowledge/list-knowledge-sources /openapi.yaml get /agents/{agentId}/knowledge/sources Returns a paginated list of the knowledge sources attached to an agent, each with its current ingestion status. The full text of a source is omitted here; fetch a single source to see its content. # Preview knowledge search Source: https://docs.flowyte.com/api-reference/knowledge/preview-knowledge-search /openapi.yaml post /agents/{agentId}/knowledge/preview Runs a search query against the agent's indexed knowledge and returns the best-matching passages with similarity scores. Use it to test coverage and see what the agent would retrieve for a question. You can optionally limit the search to specific sources. # Refresh a knowledge source now Source: https://docs.flowyte.com/api-reference/knowledge/refresh-a-knowledge-source-now /openapi.yaml post /agents/{agentId}/knowledge/sources/{id}/refresh Triggers an immediate re-crawl of a website knowledge source, whether or not auto-refresh is on. Returns 202 and re-crawls in the background; the source is re-indexed only if its content changed. Poll the source to watch `lastCheckedAt` and `lastChangedAt` update. # Set a knowledge source's auto-refresh Source: https://docs.flowyte.com/api-reference/knowledge/set-a-knowledge-sources-auto-refresh /openapi.yaml patch /agents/{agentId}/knowledge/sources/{id} Opts a website knowledge source into automatic re-crawl and re-index, or changes its cadence. Auto-refresh is opt-in and applies to URL sources only; the cadence has a 24-hour minimum (default weekly). Re-indexing happens only when the re-crawled content actually changed, so an unchanged site costs nothing beyond the crawl. # Get language capability matrix Source: https://docs.flowyte.com/api-reference/meta/get-language-capability-matrix /openapi.yaml get /meta/languages Returns which languages are available for each agent type (single-language or multilingual) and each voice-quality tier (budget, standard, or premium). Any authenticated caller can read this. # Get your account info Source: https://docs.flowyte.com/api-reference/meta/get-your-account-info /openapi.yaml get /meta/account Returns organization-scoped capability flags your app can read on load — for example whether outbound calling is enabled for your organization. Available to any authenticated caller; no special scope is required. # Assign a number to an agent Source: https://docs.flowyte.com/api-reference/numbers/assign-a-number-to-an-agent /openapi.yaml post /numbers/{id}/assign Points a phone number at an agent so its inbound calls are answered by that agent. # Buy a phone number Source: https://docs.flowyte.com/api-reference/numbers/buy-a-phone-number /openapi.yaml post /numbers/purchase Purchases a number and charges your prepaid wallet. Optionally pass a reservation id from the reserve endpoint so the number is already held for you and can't be taken during checkout. Returns 402 if your wallet balance is too low. # Check one available number Source: https://docs.flowyte.com/api-reference/numbers/check-one-available-number /openapi.yaml get /numbers/available/{e164} Re-checks a single available number by its E.164 value to confirm its current price and availability right before purchase. Returns 404 if the number is no longer purchasable. # List your phone numbers Source: https://docs.flowyte.com/api-reference/numbers/list-your-phone-numbers /openapi.yaml get /numbers Returns the phone numbers your account owns. Results are paginated. # Release a number Source: https://docs.flowyte.com/api-reference/numbers/release-a-number /openapi.yaml delete /numbers/{id} Permanently releases a number back to the carrier and stops billing for it. This cannot be undone. To keep the number but detach it from an agent, unassign it instead. # Release a number hold Source: https://docs.flowyte.com/api-reference/numbers/release-a-number-hold /openapi.yaml delete /numbers/reserve/{id} Cancels a reservation you no longer need and frees the held number for others. # Reserve a number before buying Source: https://docs.flowyte.com/api-reference/numbers/reserve-a-number-before-buying /openapi.yaml post /numbers/reserve Holds a number for about 30 minutes so it can't be taken by someone else while you finish checkout. This does not charge your wallet. Pass the returned reservation id to the purchase endpoint to claim the held number. # Search available numbers Source: https://docs.flowyte.com/api-reference/numbers/search-available-numbers /openapi.yaml get /numbers/search Browses purchasable phone numbers. All filters are optional: search by area code, city, state, full number, or a prefix/suffix for vanity and last-four matches, and require capabilities such as voice or SMS. A too-narrow filter is widened to nearby matches by default; set bestEffort to false for strict matching. # Unassign a number Source: https://docs.flowyte.com/api-reference/numbers/unassign-a-number /openapi.yaml delete /numbers/{id}/assign Detaches a number from its agent without releasing it. You keep owning and paying for the number, and it returns to your available pool so you can reassign it any time. This is the safe, reversible alternative to releasing a number. # Get a conversation Source: https://docs.flowyte.com/api-reference/observe/get-a-conversation /openapi.yaml get /conversations/{id} Returns the summary for a single conversation, including its outcome and key details. # Get a conversation receipt Source: https://docs.flowyte.com/api-reference/observe/get-a-conversation-receipt /openapi.yaml get /conversations/{id}/receipt Returns the ordered list of events for a conversation: a step-by-step audit record of what the agent did, including tool calls and handoffs. # Get a conversation transcript Source: https://docs.flowyte.com/api-reference/observe/get-a-conversation-transcript /openapi.yaml get /conversations/{id}/transcript Returns the full transcript of a conversation as a list of turns. Works for both voice and chat. # Get account analytics overview Source: https://docs.flowyte.com/api-reference/observe/get-account-analytics-overview /openapi.yaml get /analytics/overview Returns account-wide totals rolled up across every agent: total, answered, and missed calls, answer/containment/transfer rates, the voice versus chat split, active agent count, and spend for the period. Pass your average ticket value to estimate recovered revenue, and filter by date range and channel. # Get account trend over time Source: https://docs.flowyte.com/api-reference/observe/get-account-trend-over-time /openapi.yaml get /analytics/timeseries Returns per-day call volume and spend across your whole account, ordered oldest to newest. Filter by date range and channel. Only daily buckets are currently supported. # Get agent analytics Source: https://docs.flowyte.com/api-reference/observe/get-agent-analytics /openapi.yaml get /agents/{agentId}/analytics Returns post-call analytics for a single agent over a date range, including how many calls were answered, missed, contained, or transferred. Pass your average ticket value to estimate recovered revenue. Filter by channel (voice or chat). # Get agent trend over time Source: https://docs.flowyte.com/api-reference/observe/get-agent-trend-over-time /openapi.yaml get /agents/{agentId}/analytics/timeseries Returns per-day call volume and spend for a single agent, ordered oldest to newest. Filter by date range and channel. Only daily buckets are currently supported. # Get analytics per agent Source: https://docs.flowyte.com/api-reference/observe/get-analytics-per-agent /openapi.yaml get /analytics/by-agent Returns one rollup row per active agent with total calls, answer rate, containment rate, last call time, and spend. Agents with no calls in the selected window are still included with zeros. Filter by date range and channel. # List conversation topics Source: https://docs.flowyte.com/api-reference/observe/list-conversation-topics /openapi.yaml get /agents/{agentId}/topics Returns the most common topics callers discussed with this agent, with volume, trend, and sample questions. Topics are grouped automatically from conversation text, and results appear only once enough conversations have accumulated. # List conversations Source: https://docs.flowyte.com/api-reference/observe/list-conversations /openapi.yaml get /conversations Returns paginated conversation history, newest first, with each row's outcome, a one-line summary, and topic. Use the q parameter to full-text search across transcripts and summaries, and filter by date range, channel, outcome, and agent. # List conversations for a topic Source: https://docs.flowyte.com/api-reference/observe/list-conversations-for-a-topic /openapi.yaml get /agents/{agentId}/topics/{clusterId}/conversations Returns the paginated list of conversations grouped under a single topic, so you can drill into the calls behind it. # List knowledge gaps Source: https://docs.flowyte.com/api-reference/observe/list-knowledge-gaps /openapi.yaml get /agents/{agentId}/knowledge-gaps Returns questions callers asked that the agent could not answer, grouped by question and ordered by frequency, so you know what to add to its knowledge. Once enough data accumulates the gaps become ranked and can be filtered by status (open, in progress, covered, dismissed). # Update a knowledge gap Source: https://docs.flowyte.com/api-reference/observe/update-a-knowledge-gap /openapi.yaml patch /agents/{agentId}/knowledge-gaps/{gapId} Curate a knowledge gap by setting its status to open, in progress, or dismissed. The covered status is set automatically when the gap is resolved and cannot be set here. # Create a playbook Source: https://docs.flowyte.com/api-reference/playbooks/create-a-playbook /openapi.yaml post /agents/{agentId}/playbooks Creates a new playbook for the agent with a name and description, plus optional input and output schemas. After creating it, define its steps with the playbook graph endpoint. # Delete a playbook Source: https://docs.flowyte.com/api-reference/playbooks/delete-a-playbook /openapi.yaml delete /agents/{agentId}/playbooks/{id} Permanently deletes a playbook and its step graph from the agent. # Get a playbook Source: https://docs.flowyte.com/api-reference/playbooks/get-a-playbook /openapi.yaml get /agents/{agentId}/playbooks/{id} Returns a single playbook's details, including its name, description, and input and output schemas. # Get a playbook's steps Source: https://docs.flowyte.com/api-reference/playbooks/get-a-playbooks-steps /openapi.yaml get /agents/{agentId}/playbooks/{id}/graph Returns the playbook's step graph: the nodes and the connections between them that define the procedure the agent runs. # List playbooks Source: https://docs.flowyte.com/api-reference/playbooks/list-playbooks /openapi.yaml get /agents/{agentId}/playbooks Returns a paginated list of the playbooks defined for an agent. A playbook is a reusable, step-by-step procedure the agent can follow during a conversation. # Replace a playbook's steps Source: https://docs.flowyte.com/api-reference/playbooks/replace-a-playbooks-steps /openapi.yaml put /agents/{agentId}/playbooks/{id}/graph Replaces the playbook's entire step graph with the nodes and connections you provide. This overwrites the existing graph, so send the complete set of steps. Publish the agent for the change to take effect on live conversations. # Update a playbook Source: https://docs.flowyte.com/api-reference/playbooks/update-a-playbook /openapi.yaml patch /agents/{agentId}/playbooks/{id} Updates an existing playbook's name, description, and input or output schemas. # Create a publishable key Source: https://docs.flowyte.com/api-reference/publishablekeys/create-a-publishable-key /openapi.yaml post /agents/{agentId}/publishable-keys Creates a publishable key for an agent and returns the full key. Publishable keys are safe to use in the browser, so the value can be retrieved again later. # List publishable keys Source: https://docs.flowyte.com/api-reference/publishablekeys/list-publishable-keys /openapi.yaml get /agents/{agentId}/publishable-keys Returns the paginated publishable keys for an agent. Publishable keys are safe to embed in the browser and authorize the chat widget. # Revoke a publishable key Source: https://docs.flowyte.com/api-reference/publishablekeys/revoke-a-publishable-key /openapi.yaml delete /agents/{agentId}/publishable-keys/{id} Revokes a publishable key immediately. Any widget still using it will stop working. # Rotate a publishable key Source: https://docs.flowyte.com/api-reference/publishablekeys/rotate-a-publishable-key /openapi.yaml post /agents/{agentId}/publishable-keys/{id}/rotate Issues a new value for a publishable key while keeping its agent and allowed origins. The old value keeps working during a short grace window so you can swap it in without downtime. # Update a publishable key Source: https://docs.flowyte.com/api-reference/publishablekeys/update-a-publishable-key /openapi.yaml patch /agents/{agentId}/publishable-keys/{id} Updates a publishable key, such as its label or its list of allowed origins. # Create a skill Source: https://docs.flowyte.com/api-reference/skills/create-a-skill /openapi.yaml post /agents/{agentId}/skills Adds a new skill to an agent so it can perform an action, such as sending an email or booking an appointment, during conversations. Publish the agent for the new skill to take effect on live calls and chats. # Delete a skill Source: https://docs.flowyte.com/api-reference/skills/delete-a-skill /openapi.yaml delete /agents/{agentId}/skills/{id} Removes a skill from an agent. Publish the agent for the removal to take effect on live calls and chats. # Geocode a location roster Source: https://docs.flowyte.com/api-reference/skills/geocode-a-location-roster /openapi.yaml post /agents/{agentId}/geocode-roster Converts a batch of locations (label, address, phone) into map coordinates for use in a find-nearest-location skill, so the agent can quickly identify the closest location during a call. This endpoint does not save anything itself, so store the returned coordinates in your skill's configuration. Large lists must be sent in batches, and any address that can't be resolved is returned flagged so you can correct it. # Get a skill Source: https://docs.flowyte.com/api-reference/skills/get-a-skill /openapi.yaml get /agents/{agentId}/skills/{id} Returns the full configuration of a single skill on an agent. # List an agent's skills Source: https://docs.flowyte.com/api-reference/skills/list-an-agents-skills /openapi.yaml get /agents/{agentId}/skills Returns a paginated list of the skills configured on an agent. Skills are the actions an agent can take during a conversation. # List available skill types Source: https://docs.flowyte.com/api-reference/skills/list-available-skill-types /openapi.yaml get /skill-types A skill type is a category of capability a skill can use, such as sending an email, looking up an order, or transferring a call. This returns the full catalog of skill types you can choose from when creating a skill. # Update a skill Source: https://docs.flowyte.com/api-reference/skills/update-a-skill /openapi.yaml patch /agents/{agentId}/skills/{id} Changes the configuration of an existing skill on an agent. Publish the agent for the changes to apply to live calls and chats. # Get a probe run result Source: https://docs.flowyte.com/api-reference/test/get-a-probe-run-result /openapi.yaml get /agents/{agentId}/probe/runs/{runId} Returns the current status and result of a probe run by its id. # Simulate a conversation Source: https://docs.flowyte.com/api-reference/test/simulate-a-conversation /openapi.yaml post /agents/{agentId}/simulate Runs a text or voice simulation against the agent and streams the conversation back as server-sent events. Set draftMode to test unpublished changes without billing. Each event is a turn or tool-call frame, and the stream ends with a done or error event. # Start a probe run Source: https://docs.flowyte.com/api-reference/test/start-a-probe-run /openapi.yaml post /agents/{agentId}/probe/run Kicks off a probe run against the agent for a given test scenario, in either loopback or phone mode. Returns a run id you can poll or stream for results. # Start an in-browser voice test Source: https://docs.flowyte.com/api-reference/test/start-an-in-browser-voice-test /openapi.yaml post /agents/{agentId}/talk-token Returns the credentials needed to start a real-time voice test session in the browser. Set draftMode to true to test the current draft without billing; when false (the default) it uses the latest published version and returns 409 if the agent has never been published. # Stream probe run progress Source: https://docs.flowyte.com/api-reference/test/stream-probe-run-progress /openapi.yaml get /agents/{agentId}/probe/runs/{runId}/stream Streams a probe run's progress in real time as server-sent events until the run finishes. # Upload a file Source: https://docs.flowyte.com/api-reference/uploads/upload-a-file /openapi.yaml post /uploads Upload a file (max 25 MB) and get back a file_id you can pass to a knowledge file source, the create-from-document flow, or a skill file input. # Declare or annotate a variable Source: https://docs.flowyte.com/api-reference/variables/declare-or-annotate-a-variable /openapi.yaml post /agents/{agentId}/variables Optionally attach a note, a type hint, or a manual declaration to a variable. This is documentation only — it carries no runtime meaning and never gates a call. Annotating a name that's already derived returns 200 (not a conflict). Names are case-sensitive and must match `^[a-zA-Z_][a-zA-Z0-9_]*$`; the reserved caller variables (`caller_info`, `caller_number`, `caller_name`, `verified`) can't be declared. # Delete a variable annotation Source: https://docs.flowyte.com/api-reference/variables/delete-a-variable-annotation /openapi.yaml delete /agents/{agentId}/variables/{name} Removes only the annotation. If the name is still produced or used by the agent, it reappears as a derived variable on the next read. Deleting a name that has no annotation does nothing. # Edit a variable annotation Source: https://docs.flowyte.com/api-reference/variables/edit-a-variable-annotation /openapi.yaml patch /agents/{agentId}/variables/{name} Edits the note or type hint on an existing variable annotation (only the fields you send change). Returns 404 if the name has no annotation. The annotation carries no runtime meaning. # List interaction variables Source: https://docs.flowyte.com/api-reference/variables/list-interaction-variables /openapi.yaml get /agents/{agentId}/variables Returns the agent's interaction variables — the values that flow through a call (collected slots, skill outputs, and `{placeholder}` references). The list is derived by scanning the agent's enabled playbooks and skills, then merged with any notes or type hints you've added. It's a read-only view of how variables flow; it never validates references or affects publishing or runtime. Sorted by name. # Get voice filter counts Source: https://docs.flowyte.com/api-reference/voices/get-voice-filter-counts /openapi.yaml get /voices/facets Returns grouped counts for each voice filter using the same filters as the voice list, so a picker can show a live count next to each option (for example 'Female: 142') as the user narrows their choice. # List the voice catalog Source: https://docs.flowyte.com/api-reference/voices/list-the-voice-catalog /openapi.yaml get /voices Returns the voice catalog, optionally narrowed by facets such as language, accent, gender, age, tone, and use case (all combined together). The catalog is returned as a flat list rather than paginated. Pair it with the facets endpoint for live counts and the search endpoint for natural-language lookup. # Search voices by description Source: https://docs.flowyte.com/api-reference/voices/search-voices-by-description /openapi.yaml post /voices/search Turns a plain-language phrase like 'calm older british woman for support' into structured filters, runs the search, and returns both the resolved filters and a short ranked shortlist of voices to audition. # Create a webhook endpoint Source: https://docs.flowyte.com/api-reference/webhooks/create-a-webhook-endpoint /openapi.yaml post /webhooks Registers a URL to receive event notifications and returns a signing secret used to verify that deliveries came from us. The secret is shown only once in this response, so store it now; you cannot retrieve it again later. # Delete a webhook endpoint Source: https://docs.flowyte.com/api-reference/webhooks/delete-a-webhook-endpoint /openapi.yaml delete /webhooks/{id} Permanently removes a webhook endpoint so it will no longer receive event deliveries. # Get a webhook endpoint Source: https://docs.flowyte.com/api-reference/webhooks/get-a-webhook-endpoint /openapi.yaml get /webhooks/{id} Returns the details of a single webhook endpoint, including its URL, subscribed events, and status. The signing secret is not included. # List webhook deliveries Source: https://docs.flowyte.com/api-reference/webhooks/list-webhook-deliveries /openapi.yaml get /webhooks/{id}/deliveries Returns the recent delivery attempts for an endpoint, including each event type, status, attempt count, and the next scheduled retry. Use this to debug failures and confirm events are reaching your URL. Results are paginated. # List webhook endpoints Source: https://docs.flowyte.com/api-reference/webhooks/list-webhook-endpoints /openapi.yaml get /webhooks Returns your configured webhook endpoints, with the events each is subscribed to and its current status. Results are paginated. # Send a test delivery Source: https://docs.flowyte.com/api-reference/webhooks/send-a-test-delivery /openapi.yaml post /webhooks/{id}/test Sends a sample event to the endpoint so you can confirm your receiver is reachable and verifies signatures correctly. Returns the resulting delivery record, whose status updates as the attempt completes. # Update a webhook endpoint Source: https://docs.flowyte.com/api-reference/webhooks/update-a-webhook-endpoint /openapi.yaml patch /webhooks/{id} Changes a webhook endpoint's URL, subscribed events, or status. Set the status to disabled to pause deliveries without deleting the endpoint. # Get the widget embed snippet Source: https://docs.flowyte.com/api-reference/widget/get-the-widget-embed-snippet /openapi.yaml get /agents/{agentId}/widget/embed Returns the HTML snippet for embedding the chat widget on your site, along with a publishable key. Optionally specify which publishable key to include. # Get widget config Source: https://docs.flowyte.com/api-reference/widget/get-widget-config /openapi.yaml get /agents/{agentId}/widget Returns the chat widget configuration for an agent, including its appearance, copy, and behavior settings. # Replace widget config Source: https://docs.flowyte.com/api-reference/widget/replace-widget-config /openapi.yaml put /agents/{agentId}/widget Replaces an agent's chat widget configuration with the supplied settings and returns the saved result. # Chat Source: https://docs.flowyte.com/channels/chat Talk to an agent over text — sessions, streaming, and an OpenAI-compatible endpoint. The same published agent that answers the phone also answers over text. There are two ways to drive a chat from your backend: the **session API** (durable conversations you create, message, and end) and an **OpenAI-compatible completions endpoint** (a drop-in for any client that already speaks the OpenAI chat shape). ## Sessions A session is a server-side conversation tied to one agent. Create it, post messages, and end it when done. History is reconstructed for you, so you can fetch the full transcript any time. Posting a message with `stream: true` returns Server-Sent Events (SSE) — token deltas, tool calls, and knowledge retrievals arrive as they happen, terminating with an `event: done` (or `event: error`) frame. With `stream: false` you get the assembled messages in one JSON response. ```bash theme={null} # Create a session, then send a streaming message curl -X POST https://builder.flowyte.com/api/v1/chat/sessions \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{"agentId": "AGENT_ID"}' curl -N -X POST https://builder.flowyte.com/api/v1/chat/sessions/SESSION_ID/messages \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{"content": "Do you deliver to 80202?", "stream": true}' ``` ## OpenAI-compatible completions `POST /chat/completions` accepts the OpenAI request shape — set `model` to the **agent's id**. Point an existing OpenAI SDK at the base URL `https://builder.flowyte.com/api/v1` and it works with only a base-URL swap. ```bash curl theme={null} curl -X POST https://builder.flowyte.com/api/v1/chat/completions \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{ "model": "AGENT_ID", "messages": [{"role": "user", "content": "What are your hours?"}], "stream": true }' ``` ```python Python theme={null} from openai import OpenAI client = OpenAI( base_url="https://builder.flowyte.com/api/v1", api_key="flowyte_sk_…", ) stream = client.chat.completions.create( model="AGENT_ID", # the agent id is the model messages=[{"role": "user", "content": "What are your hours?"}], stream=True, ) for chunk in stream: print(chunk.choices[0].delta.content or "", end="") ``` ```javascript Node theme={null} import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://builder.flowyte.com/api/v1", apiKey: "flowyte_sk_…", }); const stream = await client.chat.completions.create({ model: "AGENT_ID", // the agent id is the model messages: [{ role: "user", content: "What are your hours?" }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); } ``` When `stream: true`, this endpoint emits OpenAI-style SSE chunks (`data: {choices:[{delta:…}]}`) ending with `data: [DONE]`. ## In the API | Action | Endpoint | Scope | | ---------------------------------- | ----------------------------------- | ------------ | | Create a session | `POST /chat/sessions` | `chat:write` | | Get a session | `GET /chat/sessions/{id}` | `chat:read` | | List messages | `GET /chat/sessions/{id}/messages` | `chat:read` | | Post a message (SSE when `stream`) | `POST /chat/sessions/{id}/messages` | `chat:write` | | End a session | `POST /chat/sessions/{id}/end` | `chat:write` | | OpenAI-compatible completion | `POST /chat/completions` | `chat:write` | These endpoints use your **secret key**. To put chat in a browser without exposing a secret, use a publishable key and the [Embeddable Widget](/channels/widget). # DTMF / Keypad Source: https://docs.flowyte.com/channels/dtmf Collect keypad digits from a caller during a voice call. On a voice call, callers can answer with their **keypad** as well as their voice. The agent recognizes the touch-tones (DTMF) a caller presses and treats those digits as input — the same way it treats spoken words. This is the right tool when a value is awkward to say out loud or must be exact: an account or order number, a numeric menu choice, a PIN, or a yes/no confirmation ("press 1 to confirm"). ## When to reach for the keypad * **Exact numbers** — account, order, member, or case IDs where a misheard digit is costly. * **Quick choices** — "press 1 for billing, 2 for support" style menus. * **Confirmations** — "press 1 to confirm, 2 to start over." * **Sensitive entry** — values a caller would rather key in than speak aloud. ## How it works Keypad input is a property of the **voice** channel — there is no separate setup to turn it on. During a call the agent listens for both speech and digits, so a caller can press a number when the agent asks for one. Those digits flow into the conversation just like spoken answers: when a skill needs a value (for example, an order number for a lookup, or a destination choice for a transfer), the caller can speak it *or* key it in, and the agent uses whichever it receives. Because digits become ordinary collected values, you don't build a rigid phone-tree. You describe what the agent should collect — in the agent's prompt and in each skill's parameters — and the agent decides when keypad entry makes sense. Phrase your prompts to invite it explicitly when precision matters: *"Read me your order number, or type it on your keypad."* Callers default to the keypad for long digit strings, which improves accuracy. ## Related configuration | What it controls | Where | | ---------------------------------------------------------------- | ------------------------------------------------ | | Which values a skill collects from the caller (spoken or keypad) | `POST /agents/{agentId}/skills` (`skills:write`) | | Idle / no-response handling while waiting for input | `CallControl` on the agent (`agents:write`) | | Routing a keyed-in choice to a person | Transfer rules — see [Voice](/channels/voice) | | What actually happened on a call | `GET /agents/{agentId}/calls` (`calls:read`) | Keypad entry is only available on the [Voice (PSTN)](/channels/voice) channel — text [Chat](/channels/chat) has no tones to press. Build the same data-collection step into your skill parameters and it works across both: spoken in chat, spoken-or-keyed on a call. # SMS (10DLC) Source: https://docs.flowyte.com/channels/sms Text from your phone numbers — set up in the dashboard once your brand and campaign are registered. SMS lets your business send and receive text messages from numbers you own. In the US, texting from a standard 10-digit number requires **A2P 10DLC registration**: every organization registers its own **brand** and **campaign** with the carrier registry before any message can be sent. SMS registration is **handled in the dashboard**, not the API. Carrier registration (TCR) is compliance-critical — the guided flow validates your details, runs a free compliance review, and walks you through approval so a submission can't be wrong before you pay the activation fee. Sending stays **gated** until your campaign is approved. ## Set it up 1. Open **SMS** in the dashboard. 2. Enter your business details — the platform auto-generates compliant campaign text (sample messages and opt-in language). 3. Run the **free compliance review** (green / yellow / red, with specific fixes). 4. **Submit** once it's green. This pays the one-time activation fee and registers your brand and campaign with the carrier registry; status then updates as the registry approves it. 5. Enable SMS on the numbers you want to text from. ## After approval Once your campaign is **approved**, your agent can send and receive texts on its enabled numbers. Opt-outs (STOP) are honored automatically, and the dashboard keeps a do-not-text suppression list — re-enabling a number requires fresh, documented consent. Plan the registration step before launch — approval is controlled by the carrier registry and can take time, so start it early. # Voice (PSTN) Source: https://docs.flowyte.com/channels/voice Put an agent on a real phone number and answer inbound calls. Voice is the flagship channel: a published agent answers a real phone number over the public telephone network (PSTN), greets the caller, answers from its knowledge, runs its skills, and hands off to a person when needed. There is nothing to host — you buy a number, point it at an agent, and inbound calls just work. ## Get a number, point it at an agent Browse purchasable numbers by area code, city, or vanity suffix. Filter by capability (require `voice`, and `sms` if you plan to text from it later). Hold a number for \~30 minutes so it can't be sniped while you confirm, then purchase it. Purchase debits your prepaid wallet. Already own a number elsewhere? **Import** it instead — no wallet charge. Assigning sets the number's default agent. Inbound calls to that number are routed to this deployment's voice connection and reach your agent. The agent must be **published** to take live calls — phone and chat always serve the last published version, never your draft. See [Draft vs Published](/get-started/draft-vs-published). ## On the call The agent opens with its configured greeting (spoken in the agent's primary language), listens with real-time turn detection, and responds with the platform's voice engine. Knowledge lookups, skills, and playbooks all run live. Per-agent **call control** governs the experience: a maximum call length, "are you still there?" idle reminders, and what to do on silence. ## Transfer and handoff When a caller needs a person, the agent transfers the call. You configure a default handoff destination plus a **transfer-by-context** table — plain-language rules that route specific situations (billing, Spanish line, a region) to specific numbers. A data-lookup skill can also supply a dynamic destination at call time (for example, route to the booked technician). The original caller ID is passed through to the receiving line. ## Keypad input (DTMF) Callers can press keypad digits during a call — useful for menus, account numbers, or confirmations. See [DTMF / Keypad](/channels/dtmf). ## In the API | Action | Endpoint | Scope | | ------------------------ | ----------------------------- | --------------- | | Search available numbers | `GET /numbers/search` | `numbers:read` | | Reserve a number | `POST /numbers/reserve` | `numbers:write` | | Purchase a number | `POST /numbers/purchase` | `numbers:write` | | Import a number you own | `POST /numbers/import` | `numbers:write` | | Assign to an agent | `POST /numbers/{id}/assign` | `numbers:write` | | Unassign (keep, pool it) | `DELETE /numbers/{id}/assign` | `numbers:write` | | Release a number | `DELETE /numbers/{id}` | `numbers:write` | | Review call summaries | `GET /agents/{agentId}/calls` | `calls:read` | ```bash theme={null} # Assign a purchased number to a published agent curl -X POST https://builder.flowyte.com/api/v1/numbers/NUM_ID/assign \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{"agentId": "AGENT_ID"}' ``` `DELETE /numbers/{id}` permanently **releases** the number back to the carrier. To detach a number from an agent while keeping it, use `DELETE /numbers/{id}/assign` to move it to the pool. Configure the greeting, transfer rules, and call control on the agent, then republish. Editing without republishing changes only the draft — the live phone line keeps serving the old version. # Embeddable Widget Source: https://docs.flowyte.com/channels/widget Drop a chat agent onto any website with a browser-safe publishable key. The widget puts a published agent on your own site as a chat bubble. It authenticates in the browser with a **publishable key** (`flowyte_pk_…`) — agent-pinned, origin-allowlisted, and limited to public chat — so it is safe to ship in client-side code. No secret key ever touches the browser. ## How it works Create a key for the agent and list the exact origins (sites) allowed to use it. The full key is returned on create — it is public by design, not a show-once secret. Fetch the loader ` ``` ## Origin allowlisting A publishable key only works from an origin you listed. Browser requests must send an `Origin` that matches the key's `allowedOrigins`; anything else is rejected with `403`. Keep the list tight — one key per site is a good default. You can rotate a key (same agent and origins, with a grace window) or revoke it instantly. ## Customize and harden * **Appearance & copy** live in the agent's widget config (`theme`, `copy`, `behavior`). * **Rate limiting** per key (`rateLimitRpm`) caps abuse from a single origin. * **Identity verification** (`requireIdentityVerification`) can require a signed visitor token before a session opens, for logged-in experiences. ## In the API | Action | Endpoint | Scope | | --------------------------- | ----------------------------------------------------- | -------------------------------- | | List publishable keys | `GET /agents/{agentId}/publishable-keys` | `pubkeys:read` | | Mint a publishable key | `POST /agents/{agentId}/publishable-keys` | `pubkeys:write` | | Update a key | `PATCH /agents/{agentId}/publishable-keys/{id}` | `pubkeys:write` | | Rotate a key (grace window) | `POST /agents/{agentId}/publishable-keys/{id}/rotate` | `pubkeys:write` | | Revoke a key | `DELETE /agents/{agentId}/publishable-keys/{id}` | `pubkeys:write` | | Get / replace widget config | `GET` · `PUT /agents/{agentId}/widget` | `widgets:read` · `widgets:write` | | Get the embed snippet | `GET /agents/{agentId}/widget/embed` | `widgets:read` | ```bash theme={null} # Mint a publishable key scoped to two origins curl -X POST https://builder.flowyte.com/api/v1/agents/AGENT_ID/publishable-keys \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{ "name": "Marketing site", "allowedOrigins": ["https://example.com", "https://www.example.com"], "env": "live" }' ``` The widget talks to the public chat surface (`chat:public` scope) using only the publishable key — visitors get an anonymous session pinned to the one agent. Your secret key stays server-side. The widget serves the agent's **published** version. Republish after editing, or visitors keep seeing the old behavior. # Agents Source: https://docs.flowyte.com/concepts/agents The single entity you configure — persona, language, and voice. An **agent** is the one user-facing entity in Flowyte. Everything else — knowledge, skills, guardrails, playbooks — attaches to an agent. You configure who the agent is; the platform handles turning that into a real-time voice and chat experience. ## What an agent holds * **Persona** — its name, role, goals, and the things it must never do. * **Language & voice** — the primary language (it can mirror a caller's language) and the voice it speaks with. * **Behavior** — greeting, tone, and how it handles handoffs. ## Draft vs published Editing an agent changes its **draft**. The in-browser tester runs the draft, but phone and chat channels serve the last **published** version. Publishing freezes a version; you can roll back to a previous one at any time. ## In the API | Action | Endpoint | | ----------------- | ----------------------------------------- | | Create an agent | `POST /agents` | | Read / update | `GET /agents/{id}` · `PATCH /agents/{id}` | | Publish a version | `POST /agents/{id}/publish` | | Roll back | `POST /agents/{id}/rollback` | | Test (streaming) | `POST /agents/{id}/simulate` | See the [Agents endpoints](/api-reference/introduction) in the reference for the full shape. # Billing & Credits Source: https://docs.flowyte.com/concepts/billing A prepaid credit wallet you top up with Stripe, metered per voice minute and per chat message. Flowyte runs on a **prepaid credit wallet**. You add funds, the platform meters usage as agents work, and credits are drawn down in real time. \*\*1 credit = 1 cent ($0.01)**, so a $10 top-up is 1,000 credits. ## How metering works Usage is metered per channel as it happens: * **Voice** — per minute of connected call time. * **Chat** — per message exchanged. * **Toll-free inbound** — a call received on a toll-free (8XX) number bills your normal voice minute **plus a flat 2 credits/min** (\$0.02), the same on every plan. Toll-free is "called-party-pays," so the number owner covers the higher carrier cost. See the toll-free note below. * **Call transfers** — per minute of the **forwarded** call, billed for the whole bridged duration including hold or queue time. Your AI charges stop at the hand-off, so forwarded minutes are a separate bucket from AI minutes. * **Phone numbers** — a recurring monthly rental. Each charge becomes a usage item with a `channel` (`voice` · `chat` · `transfer_minute` · `number`), a `quantity` (the metered units), and the `credits` debited. Pull the day-by-day breakdown from `GET /billing/usage/items`, or the rolled-up records from `GET /billing/usage`. **Toll-free pass-through.** The flat 2-credit/min toll-free surcharge covers ordinary inbound traffic. In rare cases the underlying carrier charges more than that for a particular call — for example high-access or international originators, or the federal per-call payphone-origination fee — and those above-surcharge carrier costs are **passed through at cost**. Toll-free numbers may also be subject to inbound usage limits to protect against artificial traffic inflation. ## Topping up with Stripe `POST /billing/wallet/topup` starts a Stripe payment and returns a `clientSecret` your checkout UI confirms. Once the payment settles, the credits land in the wallet. Downloadable invoices are available from `GET /billing/invoices`. `topup`, `auto-reload`, and changing your **plan** are completed through the dashboard session (Stripe checkout), not with an API key. Read endpoints — wallet, usage, plans, invoices — accept a key with `billing:read`. ## Auto-reload So an agent never goes silent mid-day, configure **auto-reload**: when the balance drops below `thresholdUsd`, the platform charges `amountUsd` automatically. ```json theme={null} PUT /billing/wallet/auto-reload { "enabled": true, "thresholdUsd": 20, "amountUsd": 50 } ``` ## Plans Plans (`Starter`, `Growth`, `Scale`) bundle included minutes at a lower per-minute rate plus an overage rate beyond them; the wallet covers everything else. List them with `GET /billing/plans` and read your current plan from `GET /billing/subscription`. ## In the API | Action | Endpoint | Scope | | ---------------------------------- | ------------------------------------ | ---------------------------------- | | Get wallet balance | `GET /billing/wallet` | `billing:read` | | Top up via Stripe | `POST /billing/wallet/topup` | dashboard session | | Configure auto-reload | `PUT /billing/wallet/auto-reload` | dashboard session | | Credit the wallet (manual / admin) | `POST /billing/wallet/credit` | `billing:write` | | List usage records | `GET /billing/usage` | `billing:read` | | Itemized charges in a window | `GET /billing/usage/items` | `billing:read` | | List plans | `GET /billing/plans` | `billing:read` | | Get / change subscription | `GET /billing/subscription` · `POST` | `billing:read` · dashboard session | | List invoices (PDF links) | `GET /billing/invoices` | `billing:read` | ```bash theme={null} # Check the balance before kicking off a batch of outbound work curl https://builder.flowyte.com/api/v1/billing/wallet \ -H "Authorization: Bearer flowyte_sk_…" ``` Watch the balance from a server key with `billing:read` and alert your own ops channel — pair it with auto-reload so a spike in call volume never drains the wallet to zero. # Guardrails Source: https://docs.flowyte.com/concepts/guardrails Deterministic policies that keep an agent on-script and verify callers before disclosure. **Guardrails** are the deterministic rules that constrain what an agent will say and do — the difference between "usually behaves" and "provably won't cross a line." Unlike the agent's prompt, guardrails are enforced, not suggested. ## What guardrails cover * **Topic and policy limits** — things the agent must refuse or redirect. * **Caller verification** — require identity verification *before* disclosing sensitive information, so the agent never reads back an account detail to an unverified caller. * **Escalation rules** — when to hand off to a human. ## In the API | Action | Endpoint | | -------------------------- | --------------------------------------- | | Read / set guardrails | `GET` · `PATCH /agents/{id}/guardrails` | | Caller verification config | `GET /agents/{id}/caller-verification` | Guardrails are part of the published version — publish to apply changes. # Integrations Source: https://docs.flowyte.com/concepts/integrations Connect an external system once, then turn it into agent skills — either pre-built actions or fields you map yourself. An **integration** lets an agent act inside an external system — recognize a caller in your CRM, book on a calendar, look up an order, capture a request. You connect the provider **once** for your organization, then turn it into [skills](/concepts/skills) on any agent. No code, no per-agent credentials. There are two ways to turn a connected provider into a skill: For curated providers, ready-made actions (e.g. "find a customer", "book a visit") install as skills in one call. For any REST/GraphQL provider, discover its full schema and map its fields onto your agent's parameters — like a Zapier you build for your agent. ## 1. Provision pre-built actions For curated providers (Google Calendar, Google Sheets, Shopify, and field-service software), the actions are already built — you just connect and provision. `POST /integrations/{kind}/connect` — OAuth returns an `oauthUrl` to approve; API-key providers take `{ "credentials": { … } }`. Credentials are stored encrypted, never echoed. `GET /integrations/{kind}/actions` lists each action's slug, the parameters it collects, and any config an operator must still fill. `POST /agents/{agentId}/integrations/{kind}/provision` turns chosen actions into skills (omit `actions` to provision the important ones). Re-running is idempotent. | Provider | Connect via | Status | | --------------- | ----------- | ------ | | Google Calendar | OAuth | Live | | Google Sheets | OAuth | Live | | Shopify | API key | Live | ## 2. Map fields from a discovered schema (the open model) For any provider whose schema can be introspected (REST/OpenAPI, GraphQL, or a **SQL database**), you don't wait for pre-built actions — you **discover** the provider's entire data model and **map** the fields you want onto your agent's parameters. The mapping is the integration; there's no per-provider code. Your own **[Postgres or MySQL database](/integrations/sql-database)** is the clearest example: connect with a least-privilege credential, discover the schema, scope out sensitive columns, and bind a read (or a scoped write) as a skill — walk through it in [Connect a SQL database](/guides/connect-sql-database). `POST /integrations/{kind}/discover` introspects the connected provider into a normalized **schema** — every object, field, relationship, and operation — and returns a summary (object / field / relationship counts). Re-running refreshes it. `GET /integrations/{kind}/schema` returns the discovered schema: the objects, their fields (with types, whether they're required, allowed values, and what's sensitive), the relationships between objects, and the operations you can run. This is the catalog you map from. `POST /agents/{id}/integrations/{kind}/bindings` authors a **binding**: pick an operation, map your agent's inputs to its arguments, and **project** which provider fields come back as your own output names. It's validated against the schema and compiles to a skill. A binding maps to your **canonical parameters** (`caller_phone`, `caller_name`, `service_address`, …), so the same mapping concept works across any provider, and your agent's prompt stays the same whatever system is behind it. Bindings are authored with a human (or the AI assistant) in the loop and validated up front — what reaches a live call is a frozen, pre-approved skill. Walk through it end to end in [Map an integration's fields](/guides/map-integration-fields). ### Shortcuts: auto-map and preset packs You don't have to author every binding by hand: * **Auto-map from a goal** — `POST /agents/{id}/integrations/{kind}/bindings/auto` takes a plain-language goal ("look up a caller by phone and return their open tickets"). The AI assistant proposes a binding over the discovered schema, validates it, and saves it as a **disabled draft** for you to review before enabling. This is the fastest way to go from intent to a working skill. * **Install a preset pack** — many providers ship a **Connector Pack**: curated, ready-to-install preset skills plus guidance on how the provider's operations behave and how to identify a caller. `GET /integrations/{kind}/pack` reads it; `POST /agents/{id}/integrations/{kind}/pack/install` installs the presets onto an agent in one call. To build a mapping UI of your own, browse the discovered schema piece by piece with `GET /integrations/{kind}/objects`, `GET /integrations/{kind}/objects/{object}`, and `GET /integrations/{kind}/operations` instead of pulling the whole schema at once. ## Custom integrations If a provider can't be introspected, an agent can still reach any system you run: a webhook skill that calls your own HTTP endpoint, or tools exposed over MCP. See [Build a custom integration](/integrations/custom). ## In the API | Action | Endpoint | Scope | | ---------------------------------- | ---------------------------------------------------------- | -------------------- | | Browse the provider catalog | `GET /integrations/catalog` | `integrations:read` | | Connect a provider | `POST /integrations/{kind}/connect` | `integrations:write` | | List a provider's actions | `GET /integrations/{kind}/actions` | `integrations:read` | | Provision actions as skills | `POST /agents/{agentId}/integrations/{kind}/provision` | `skills:write` | | **Discover the schema** | `POST /integrations/{kind}/discover` | `integrations:write` | | **Read the discovered schema** | `GET /integrations/{kind}/schema` | `integrations:read` | | Browse the discovered objects | `GET /integrations/{kind}/objects` | `integrations:read` | | Get one object's detail | `GET /integrations/{kind}/objects/{object}` | `integrations:read` | | Browse bindable operations | `GET /integrations/{kind}/operations` | `integrations:read` | | **Map a binding** | `POST /agents/{agentId}/integrations/{kind}/bindings` | `skills:write` | | **Auto-map a binding from a goal** | `POST /agents/{agentId}/integrations/{kind}/bindings/auto` | `skills:write` | | Read a provider's preset pack | `GET /integrations/{kind}/pack` | `integrations:read` | | Install a provider's preset pack | `POST /agents/{agentId}/integrations/{kind}/pack/install` | `skills:write` | | Disconnect a provider | `DELETE /integrations/{kind}` | `integrations:write` | Skills run on the **published** agent — [publish](/concepts/agents) after provisioning or binding. # Knowledge Source: https://docs.flowyte.com/concepts/knowledge The sources an agent answers from, grounded so it doesn't make things up. **Knowledge** is what your agent draws on to answer accurately. You add sources — your help center URL, uploaded documents, FAQs, or plain text — and the platform indexes them so the agent retrieves the right passage at answer time instead of guessing. ## Adding a source Ingestion is **asynchronous**: after you add a source it moves through `pending` → `indexed`. Poll the source until it's `indexed` before relying on it. You can preview what the agent would retrieve for a question to sanity-check coverage. ## Knowledge gaps When callers ask things your knowledge doesn't cover, those questions surface as **knowledge gaps** so you can close them — the loop that makes the agent better over time. ## In the API | Action | Endpoint | | ------------------- | ----------------------------------------- | | Add a source | `POST /agents/{id}/knowledge/sources` | | Check ingest status | `GET /agents/{id}/knowledge/sources/{id}` | | Preview retrieval | `POST /agents/{id}/knowledge/preview` | | Review gaps | `GET /agents/{id}/knowledge-gaps` | # Languages & Voice Source: https://docs.flowyte.com/concepts/languages-and-voice How an agent picks its language, mirrors the caller, and chooses a voice. Every agent speaks a **primary language** and can **mirror the caller's language** mid-call. Separately, it has a **voice** — the identity it speaks with. Language and voice are set independently: one agent can answer in around 40 languages while keeping the same voice. ## Primary language and mirroring An agent holds a `primaryLanguage` (a BCP-47 code like `en`, `es`, or `fr`) plus a list of `languages` it supports. The greeting and default behavior use the primary language. When a caller speaks a different supported language, the agent can switch to it for the rest of the conversation — no separate flow required. Set `primaryLanguage` and `languages` when you create the agent, or update them later with `PATCH /agents/{id}`. Mirroring works across the supported set; pick the languages you actually want to serve. ## How a voice is chosen A voice is a catalog entry with a normalized set of facets — `language`, `accent`, `gender`, `age`, `category`, and mood `descriptorTags`. You find a voice three ways, then assign it. `GET /voices` returns the catalog, narrowed by any combination of facets (all AND-combined): `language`, `accent`, `region`, `gender`, `age`, `category`, `tone`, `useCase`, or free-text `q`. `GET /voices/facets` returns live counts per facet ("Female · 142") over the same filters, so a picker can show how each chip shrinks the result set. `POST /voices/search` turns a phrase like *"calm older british woman for support"* into facets and returns the resolved filters plus a short ranked shortlist to audition. `PUT /agents/{id}/voice` assigns a `voiceId` to the agent for a language (defaults to the primary language). This is the one-call way to set the voice. A voice carries a `previewUrl` to audition it, `supportedLanguages` it can speak, `capabilities` (which fine-tuning settings the voice supports and its safe speed band), and `verifiedLanguages` — languages the voice is explicitly verified for, with a localized preview. Per-language voices are stored in the agent's `voiceMap`, and the primary-language voice is also its `defaultVoiceId`. Assigning a voice for one language never changes the others. ## In the API | Action | Endpoint | Scope | | ------------------------------------------ | ------------------------ | -------------- | | Browse the voice catalog | `GET /voices` | `agents:read` | | Live facet counts | `GET /voices/facets` | `agents:read` | | Natural-language voice search | `POST /voices/search` | `agents:read` | | Assign a voice to an agent | `PUT /agents/{id}/voice` | `agents:write` | | Set primary language / supported languages | `PATCH /agents/{id}` | `agents:write` | ```bash curl theme={null} curl https://builder.flowyte.com/api/v1/voices/search \ -H "Authorization: Bearer flowyte_sk_..." \ -H "Content-Type: application/json" \ -d '{"query":"calm older british woman for support","limit":5}' ``` ```js Node theme={null} const res = await fetch("https://builder.flowyte.com/api/v1/agents/AGENT_ID/voice", { method: "PUT", headers: { Authorization: "Bearer flowyte_sk_...", "Content-Type": "application/json", }, body: JSON.stringify({ voiceId: "VOICE_ID", language: "en" }), }); ``` ```python Python theme={null} import requests requests.put( "https://builder.flowyte.com/api/v1/agents/AGENT_ID/voice", headers={"Authorization": "Bearer flowyte_sk_..."}, json={"voiceId": "VOICE_ID", "language": "en"}, ) ``` Voice and language are part of the agent's **draft**. [Publish](/concepts/agents) the agent so phone and chat callers hear the change. # Numbers Source: https://docs.flowyte.com/concepts/numbers Own a phone number and point it at an agent — search, reserve, purchase, assign. A **number** is a phone line your organization owns. Inbound calls to it ring the agent you assign. You can buy a new number from the carrier, or import one you already own, then route it to any agent — all over the API. ## The lifecycle `GET /numbers/search` browses purchasable inventory. Filter by `areaCode`, `locality`, `administrativeArea` (state), `numberType` (`local` or `toll_free`), required `features` (`voice`, `sms`, …), or a vanity `contains` / `endsWith`. `bestEffort` (default true) widens a too-narrow filter; set it false for strict last-four or vanity matching. `POST /numbers/reserve` holds a number (about 30 minutes) so it can't be taken while you confirm. This does **not** charge your wallet. Pass the returned reservation id to purchase. `POST /numbers/purchase` buys the number and debits your prepaid wallet. Include the `reservationId` from the hold so the order can't be sniped. Returns the owned number. `POST /numbers/{id}/assign` points the number at an agent (sets its default agent). Inbound calls now reach that agent. Already own a number elsewhere? `POST /numbers/import` brings a number you hold on your carrier account into Flowyte with **no wallet charge** — and can assign it to an agent in the same call. ## Releasing vs unassigning Detaching a number has two very different outcomes: | You want to… | Call | Result | | ------------------------------------------- | ----------------------------- | ----------------------------------------------------------- | | Keep owning it, just free it from the agent | `DELETE /numbers/{id}/assign` | Moves to your pool (`status: available`); reassign any time | | Stop owning and paying for it | `DELETE /numbers/{id}` | Permanently released to the carrier | `DELETE /numbers/{id}` is irreversible — the number goes back to the carrier and may be gone for good. To park a number without losing it, **unassign** it instead. ## Vendor-neutral provider field Each number carries a `provider` field. It is an **opaque, vendor-neutral label** for the carrier behind the line — treat it as an identifier, not a brand to depend on. Cost is reported as `monthlyCost` (and `setupCost` on available numbers). ## In the API | Action | Endpoint | Scope | | ----------------------------- | ------------------------------- | --------------- | | List owned numbers | `GET /numbers` | `numbers:read` | | Search available numbers | `GET /numbers/search` | `numbers:read` | | Re-check one number's price | `GET /numbers/available/{e164}` | `numbers:read` | | Reserve a hold | `POST /numbers/reserve` | `numbers:write` | | Release a hold | `DELETE /numbers/reserve/{id}` | `numbers:write` | | Purchase | `POST /numbers/purchase` | `numbers:write` | | Import an owned number | `POST /numbers/import` | `numbers:write` | | Assign to an agent | `POST /numbers/{id}/assign` | `numbers:write` | | Unassign (keep, move to pool) | `DELETE /numbers/{id}/assign` | `numbers:write` | | Release to carrier | `DELETE /numbers/{id}` | `numbers:write` | ```bash curl theme={null} # 1. Find a local number in area code 415 curl "https://builder.flowyte.com/api/v1/numbers/search?areaCode=415&numberType=local" \ -H "Authorization: Bearer flowyte_sk_..." # 2. Purchase it (debits the wallet) curl https://builder.flowyte.com/api/v1/numbers/purchase \ -H "Authorization: Bearer flowyte_sk_..." \ -H "Content-Type: application/json" \ -d '{"e164":"+14155550100"}' ``` ```js Node theme={null} // Assign an owned number to an agent await fetch("https://builder.flowyte.com/api/v1/numbers/NUMBER_ID/assign", { method: "POST", headers: { Authorization: "Bearer flowyte_sk_...", "Content-Type": "application/json", }, body: JSON.stringify({ agentId: "AGENT_ID" }), }); ``` A purchase needs funds in your prepaid wallet — an empty balance returns `402`. A number that was taken between search and purchase returns `409`. The agent must be [published](/concepts/agents) to answer live calls on its assigned number. # Observe & Receipts Source: https://docs.flowyte.com/concepts/observe Post-call analytics, full transcripts, and an event-by-event receipt for every conversation. Once an agent is live, **Observe** is where you see what actually happened — how many calls were answered, what callers asked about, where the agent fell short, and exactly what it did on any one conversation. Every call (voice **or** chat) produces a summary, a transcript, and a **receipt**. ## The answer-first analytics card `GET /agents/{agentId}/analytics` returns the money card first: total calls, answered vs. missed, containment rate, transfer rate, and `recoveredRevenueUsd` (captured calls × your own average ticket). Pass `?avgTicketCents=` or `?avgTicketUsd=` so the dollar figure is yours, and filter by `from`, `to`, and `channel`. For org-wide rollups across every agent, use `/analytics/overview`, `/analytics/by-agent`, and `/analytics/timeseries`. ## Conversations, transcripts, and receipts The **history list** (`GET /conversations`) is newest-first and full-text searchable over the transcript and the one-line summary — filter by `q`, `agentId`, `from`, `to`, `channel`, and `outcome`. Each row already carries the deterministic outcome, summary, primary topic, and sentiment. Drill into one conversation for its summary, transcript, signed recording URL (voice only), and the **receipt** — the ordered list of audit events that reconstructs the call turn by turn: `user_utterance`, `agent_message`, `tool_call`, `kb_retrieval`, `guardrail`, `verification`, `handoff`, and more. The receipt is the same event stream you watched live in [Test & Simulate](/concepts/testing). ## Sentiment, topics, and knowledge gaps * **Sentiment** is a per-call label — `positive`, `neutral`, `negative`, or `frustrated` — computed deterministically (no LLM). It's triage: the rollup surfaces the worst calls to review first. * **Topics** are zero-config clusters of what callers actually say. `GET /agents/{agentId}/topics` ranks them by volume; drill any cluster into its conversations. * **Knowledge gaps** are deduped questions the agent couldn't answer. List them, then `PATCH` a gap to dismiss it or mark it in progress — close the loop by adding [knowledge](/concepts/knowledge). ## In the API | Action | Endpoint | Scope | | ------------------------------------ | -------------------------------------------------------------------------- | ------------------------------------ | | Agent analytics card | `GET /agents/{agentId}/analytics` | `analytics:read` | | Per-agent / org timeseries | `GET /agents/{agentId}/analytics/timeseries` · `GET /analytics/timeseries` | `analytics:read` | | Org overview / per-agent leaderboard | `GET /analytics/overview` · `GET /analytics/by-agent` | `analytics:read` | | Topics (and drill-down) | `GET /agents/{agentId}/topics` · `…/topics/{clusterId}/conversations` | `analytics:read` | | Knowledge gaps (list / curate) | `GET /agents/{agentId}/knowledge-gaps` · `PATCH …/{gapId}` | `analytics:read` · `analytics:write` | | Call summaries | `GET /agents/{agentId}/calls` | `calls:read` | | Conversation history (searchable) | `GET /conversations` | `analytics:read` | | One conversation summary | `GET /conversations/{id}` | `calls:read` | | Receipt (ordered audit events) | `GET /conversations/{id}/receipt` | `calls:read` | | Transcript | `GET /conversations/{id}/transcript` | `calls:read` | | Signed recording URL (voice) | `GET /conversations/{id}/audio` | `calls:read` | ```bash theme={null} # Pull the receipt — the full event trail for one conversation curl https://builder.flowyte.com/api/v1/conversations/CONV_ID/receipt \ -H "Authorization: Bearer flowyte_sk_…" ``` Analytics and history are reporting endpoints — give read-only keys `analytics:read` and `calls:read` and nothing more. Sentiment, topics, and gaps populate only once an agent crosses a small volume floor; below it they report `dataState: collecting`. # Playbooks Source: https://docs.flowyte.com/concepts/playbooks Scripted, multi-step flows for repeatable tasks. A **playbook** is a structured, multi-step flow the agent follows for a repeatable task — collecting the fields needed to book a job, qualifying a lead, or walking a caller through a return. Where skills are single actions and knowledge is recall, a playbook is a *procedure*: do this, then this, branch on the answer. ## When to use one Reach for a playbook when a task has a fixed shape — a set of inputs to gather in order, with branches — rather than an open-ended Q\&A. The agent stays conversational, but the playbook makes sure every required step happens. ## In the API | Action | Endpoint | | ---------------------- | ------------------------------------------- | | List / create | `GET` · `POST /agents/{id}/playbooks` | | Read / update | `GET` · `PATCH /agents/{id}/playbooks/{id}` | | Inspect the flow graph | `GET /agents/{id}/playbooks/{id}/graph` | In the API and dashboard these are called **playbooks**; some internal references use the older term *procedures*. They're the same thing. # Skills Source: https://docs.flowyte.com/concepts/skills The tools an agent can use to take actions, not just answer. A **skill** is a tool the agent can call mid-conversation to *do* something — transfer a call, send an email, book an appointment, look up an order, or check a service area. Skills are what turn an answering machine into an assistant. ## How skills work Each skill has a type (what kind of action it performs) and a configuration (the specifics — which mailbox, which calendar, what parameters to collect from the caller). When the agent decides the skill is relevant, it collects the needed inputs in natural language and invokes it. ## Native integrations Some skills connect to external systems through a **native integration**. You connect the provider once (OAuth or an API key), then provision its actions as skills on any agent — no code. ## In the API | Action | Endpoint | | ----------------------- | ------------------------------------------------ | | List a skill catalog | `GET /skill-types` | | Add a skill to an agent | `POST /agents/{id}/skills` | | Update / remove | `PATCH` · `DELETE /agents/{id}/skills/{skillId}` | | Browse integrations | `GET /integrations` | Skills only take effect on the **published** version of the agent — publish after adding one. # Test & Simulate Source: https://docs.flowyte.com/concepts/testing Try your agent against the draft before you publish — over text, or live in-browser voice. Every edit you make changes a **draft**. The tester lets you talk to that draft — by text or by voice — before you publish it to phone and chat. Nothing you do here touches the live, published version, and draft runs are **not billed**. The tester always runs the **draft**. Phone and chat serve the last **published** version. If a change works in the tester but not on a real call, you probably haven't published yet — see [Versioning & Publishing](/concepts/versioning). ## Simulate over text (and voice) `POST /agents/{agentId}/simulate` opens a **server-sent events (SSE)** stream. Set `mode` to `text` for the chat tester or `voice` for the in-browser voice tester. Leave `draftMode` as its default (`true`) to run against the freshly-compiled draft. Each SSE frame is a single audit event: the `event:` line is the event type, and the `data:` line is the event JSON. You'll see `user_utterance`, `agent_message`, `tool_call`, `kb_retrieval`, `guardrail`, and more stream by in real time — the same shapes you'll later read back as a [receipt](/concepts/observe). The stream ends with `event: done` (or `event: error`). ```bash curl theme={null} curl -N https://builder.flowyte.com/api/v1/agents/AGENT_ID/simulate \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{"mode":"text","draftMode":true,"transcript":["What are your hours?"]}' ``` ```js Node theme={null} const res = await fetch( "https://builder.flowyte.com/api/v1/agents/AGENT_ID/simulate", { method: "POST", headers: { Authorization: "Bearer flowyte_sk_…", "Content-Type": "application/json", }, body: JSON.stringify({ mode: "text", draftMode: true, transcript: ["What are your hours?"] }), }, ); const reader = res.body.getReader(); // read the SSE stream until `event: done` ``` ```python Python theme={null} import httpx with httpx.stream( "POST", "https://builder.flowyte.com/api/v1/agents/AGENT_ID/simulate", headers={"Authorization": "Bearer flowyte_sk_…"}, json={"mode": "text", "draftMode": True, "transcript": ["What are your hours?"]}, ) as r: for line in r.iter_lines(): print(line) # SSE frames; stops at `event: done` ``` ## In-browser voice with a talk-token To test **voice** live in the browser, mint a short-lived **talk-token**. It returns the credentials your browser needs to join a real-time voice session — a URL, a token, and a room. `POST /agents/{agentId}/talk-token` is a **dashboard-only** call. It is authenticated by your dashboard session, not by an API key, so it cannot be called with `flowyte_sk_…`. Use it from the in-browser tester. With `draftMode: true` the session runs the ephemeral draft and is **not billed**. With `draftMode: false` it runs the latest **published** version — and returns `409 no_published_version` if the agent has never been published. ## In the API | Action | Endpoint | Scope | | ------------------------------------ | ------------------------------------------------- | ---------------------- | | Simulate (text / voice) over SSE | `POST /agents/{agentId}/simulate` | `agents:write` | | Mint an in-browser voice talk-token | `POST /agents/{agentId}/talk-token` | dashboard session only | | Kick a probe run (scripted scenario) | `POST /agents/{agentId}/probe/run` | `agents:write` | | Poll a probe run | `GET /agents/{agentId}/probe/runs/{runId}` | `agents:read` | | Stream probe progress (SSE) | `GET /agents/{agentId}/probe/runs/{runId}/stream` | `agents:read` | **Probe** runs a saved scenario end-to-end and reports whether quality gates passed — goal completion, per-turn latency, and language fidelity. Use it in CI to catch regressions before they reach callers. # Versioning & Publishing Source: https://docs.flowyte.com/concepts/versioning Edit a draft freely, publish to freeze a version, and roll back instantly if you need to. An agent always has two states: the **draft** you're editing and the **published** version your callers actually reach. Every change — persona, knowledge, skills, guardrails, playbooks — lands in the draft. Phone and chat keep serving the last published version until you publish again. A change is invisible to live calls until you **publish**. "Works in the tester, fails on the phone" almost always means the draft has unpublished edits. Confirm with the pre-publish diff before you ship. ## Publishing freezes a version `POST /agents/{id}/publish` compiles the current draft and writes a new **frozen** version — an immutable snapshot of the compiled config, stamped with a version number, who published it, and an optional `note`. From that moment, every new call runs that frozen version. Because publishing is a money-and-callers moment, it accepts an `Idempotency-Key` so a retried request never double-ships. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/AGENT_ID/publish \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Idempotency-Key: 5f3c…" \ -H "Content-Type: application/json" \ -d '{"note":"Added after-hours transfer skill"}' ``` ## Look before you publish Two read-only endpoints make the trust moment safe: * **Diff** — `GET /agents/{id}/versions/{versionId}/diff` lists exactly what changed against the current draft (each entry is a `field` with `before`, `after`, and a `kind` of `added`, `removed`, or `changed`). * **Pre-publish report** — `GET /agents/{id}/prepublish-report` shows what the agent **will say** and **won't say**, what data leaves the system (which skills send which fields, and where), active guardrails, knowledge coverage, and any warnings. ## Rolling back If a published version misbehaves, `POST /agents/{id}/rollback` with a `versionId` re-points the live agent at a previous frozen version immediately — no recompile, no redeploy. Browse the history with `GET /agents/{id}/versions`. ## In the API | Action | Endpoint | Scope | | -------------------------- | -------------------------------------------- | -------------- | | Publish (freeze a version) | `POST /agents/{id}/publish` | `agents:write` | | Roll back to a version | `POST /agents/{id}/rollback` | `agents:write` | | List version history | `GET /agents/{id}/versions` | `agents:read` | | What-changed diff | `GET /agents/{id}/versions/{versionId}/diff` | `agents:read` | | Pre-publish report | `GET /agents/{id}/prepublish-report` | `agents:read` | A clean release loop: edit the draft → [test it](/concepts/testing) → read the pre-publish report → publish with a `note`. Keep the note descriptive — it's what you'll scan when deciding which version to roll back to. # Authentication Source: https://docs.flowyte.com/get-started/authentication API keys, scopes, and the two ways requests authenticate. The Flowyte API has two authentication modes that both resolve to the same tenant-scoped identity, so a key can never reach another organization's data. ## Secret API keys (server-to-server) For your backend and for agents building against the API, use a **secret key**: ``` Authorization: Bearer flowyte_sk_… ``` Your API key already determines the organization — no extra headers are needed. The first key is minted in the dashboard's **Developer** page (a chicken-and-egg bootstrap). Manage your keys — mint, rotate, and revoke — in the dashboard Developer page. ## Browser sessions The dashboard authenticates with a session token. You won't use this directly when building against the API — it's how the builder UI talks to the same endpoints your key does. ## Publishable keys (the chat widget) The embeddable chat widget authenticates in the browser with a **publishable key** (`flowyte_pk_…`). It is agent-pinned, origin-allowlisted, and limited to public chat — safe to ship in client-side code. ## Scopes Secret keys carry **scopes** that gate which operations they can call. Request only what you need. Common scopes: | Scope | Grants | | -------------------------------- | -------------------------------------------------- | | `agents:read` / `agents:write` | Read or modify agents, simulate, mint a talk token | | `knowledge:write` | Add and manage knowledge sources | | `skills:write` | Create and configure skills | | `pubkeys:write` | Mint publishable keys for the widget | | `numbers:write` | Search, reserve, and assign phone numbers | | `billing:read` / `billing:write` | Read the wallet / manage credits and plans | | `analytics:read` | Read post-call analytics and conversations | The authoritative scope list is the `Scope` enum in the [API Reference](/api-reference/introduction). Some endpoints documented in the contract are **reserved** (planned ahead of release). Calling one returns a `403`, not a `404`. If a correctly-scoped call returns `403`, check whether the endpoint is marked reserved. # Draft vs Published vs Sandbox Source: https://docs.flowyte.com/get-started/draft-vs-published How edits, the tester, and live phone & chat traffic relate — and why you publish before going live. Every agent has two faces: the **draft** you are editing and the **published** version your customers reach. Understanding which one runs where is the difference between "it works in the tester but fails on the phone" and a clean launch. ## The three states Your live working copy. Every edit — knowledge, skills, guardrails, playbooks — changes the draft. It is never billed and never frozen. A frozen snapshot of the draft, taken at publish time. Phone calls and chat sessions serve the **last published version**, not your draft. A way to run the *draft* on demand — the tester and `draftMode: true` chat sessions compile the draft ephemerally so you can try changes before they go live. ## How traffic is routed * **The tester runs the draft.** It compiles your current draft on the fly, so it always reflects your latest unsaved-to-live changes. * **Phone & chat serve the last published version.** Until you publish, callers and the public chat endpoint keep getting the old behavior. * **Publishing freezes a version.** A publish takes your draft, compiles it, and stores it as an immutable, numbered version. That snapshot is what goes live. * **Rollback restores a version.** If a new publish misbehaves, repoint "published" at any earlier version — no re-editing required. This is the most common gotcha: you edit, the tester looks perfect, but the phone still does the old thing. **Editing changes the draft; only publishing moves it to live.** ## Why your first live call returns 409 A chat session (and any non-draft channel) needs a published version to serve. If an agent has **never been published**, a non-draft chat session responds `409` with the problem code `no_published_version`. The fix is always the same: **publish first**. ```jsonc theme={null} // POST /chat/sessions with draftMode:false on an unpublished agent { "type": "...", "title": "Conflict", "detail": "no_published_version" } ``` Testing the draft directly? Set `draftMode: true` when you create a chat session, or use the in-dashboard tester. Draft sessions compile your draft ephemerally and are **not billed**. ## A clean release flow Make your changes. Add knowledge, wire skills, tune guardrails. Use the tester or a `draftMode: true` chat session to confirm behavior. Pull the pre-publish diff and report to see exactly what is about to go live. Freeze the draft into a new numbered version. Phone & chat now serve it. Repoint published to a known-good version in one call. ## In the API | Action | Endpoint | Scope | | ----------------------------- | -------------------------------------------- | -------------- | | Publish (freeze a version) | `POST /agents/{id}/publish` | `agents:write` | | Roll back to a version | `POST /agents/{id}/rollback` | `agents:write` | | List version history | `GET /agents/{id}/versions` | `agents:read` | | Pre-publish what-changed diff | `GET /agents/{id}/versions/{versionId}/diff` | `agents:read` | | Pre-publish report | `GET /agents/{id}/prepublish-report` | `agents:read` | Authenticate every call with `Authorization: Bearer flowyte_sk_…`. A successful publish returns `201` with the new frozen version; `409` signals a publish race or version mismatch. See [Agents](/concepts/agents) and [Channels](/concepts/channels) for what each version actually serves. # For AI agents Source: https://docs.flowyte.com/get-started/for-ai-agents How an LLM or coding agent builds and operates a Flowyte agent end to end, via the API. This page is written for an **AI agent** (or a developer's coding assistant) building against Flowyte programmatically. Everything a person can do in the dashboard is available over the API, so an agent can create, configure, publish, and test a Flowyte agent on its own. ## Machine-readable resources | Resource | URL | | ------------------------------ | --------------------------------------------------------------- | | Plain-text index of every page | [`/llms.txt`](https://flowyte.mintlify.site/llms.txt) | | Full docs as one file | [`/llms-full.txt`](https://flowyte.mintlify.site/llms-full.txt) | | Markdown of any page | append `.md` to its URL | | Docs MCP server | `https://flowyte.mintlify.site/mcp` | | OpenAPI contract | the **API Reference** tab | ## Authentication Every request uses a secret API key as a bearer token: ``` Authorization: Bearer flowyte_sk_… ``` Base URL: `https://builder.flowyte.com/api/v1`. The first key is created in the dashboard's Developer page; the key alone determines your organization (no other headers needed). Each endpoint lists the **scope** the key must hold — request only what you need. ## The build sequence `POST /agents` → capture `data.id`. `POST /agents/{id}/knowledge/sources`, then **poll** `GET …/sources/{id}` until `status` is `indexed` (ingestion is asynchronous). `POST /agents/{id}/skills` for actions like transfer or booking. `POST /agents/{id}/publish` — freezes the version that channels will serve. `POST /agents/{id}/simulate` — streams the conversation over SSE. `POST /agents/{id}/publishable-keys` for the chat widget, or assign a phone number. ## Rules that prevent the common mistakes * **Test with `POST /agents/{id}/simulate`** (SSE) — not `/chat/completions`. * **Phone and chat serve the last *published* version.** Edits change the *draft*. Always publish before going live; `409 no_published_version` means "publish first." * **Knowledge ingestion is async** — poll a source until `status: indexed` before relying on it. * **Responses** are the `ApiResponse` envelope (`{ success, data }`). **Lists** use cursor pagination — follow the returned cursor, not page numbers. * **Errors** are RFC 9457 problem+json — branch on the `type` / `status`. * **Streaming** endpoints are Server-Sent Events: read frames `event:\ndata:`, stop on `event: done`. Start with the [Quickstart](/quickstart), then browse the [API Reference](/api-reference/introduction). # Add a knowledge base & verify coverage Source: https://docs.flowyte.com/guides/add-knowledge Add a URL or FAQ source, wait for it to index, preview what the agent retrieves, and close the gaps callers expose. **Knowledge** is what your agent answers from. You add sources — a help-center URL, an uploaded document, plain text, or structured FAQs — and the platform indexes them so the agent retrieves the right passage at answer time instead of guessing. This guide adds a source, confirms it indexed, previews retrieval, and reviews the questions callers ask that you don't yet cover. Authenticate with `Authorization: Bearer flowyte_sk_…`. All paths below are relative to `https://builder.flowyte.com/api/v1`. ## What you'll use | Action | Endpoint | Scope | | ------------------ | ------------------------------------------------ | ----------------- | | Add a source | `POST /agents/{agentId}/knowledge/sources` | `knowledge:write` | | Poll ingest status | `GET /agents/{agentId}/knowledge/sources/{id}` | `knowledge:read` | | Preview retrieval | `POST /agents/{agentId}/knowledge/preview` | `knowledge:read` | | Review gaps | `GET /agents/{agentId}/knowledge-gaps` | `analytics:read` | | Curate a gap | `PATCH /agents/{agentId}/knowledge-gaps/{gapId}` | `analytics:write` | A source has a `kind` (`url`, `file`, `text`, or `faq`) and a `label`. Use `url` to crawl a page, `text` to paste content inline, or `file` with a `file_id` from an upload. ```bash curl theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge/sources \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "kind": "url", "label": "Help center", "url": "https://acme.com/help" }' ``` ```ts Node theme={null} const res = await fetch( `https://builder.flowyte.com/api/v1/agents/${agentId}/knowledge/sources`, { method: "POST", headers: { Authorization: `Bearer ${process.env.FLOWYTE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ kind: "url", label: "Help center", url: "https://acme.com/help" }), }, ); const { data: source } = await res.json(); ``` ```python Python theme={null} import os, requests r = requests.post( f"https://builder.flowyte.com/api/v1/agents/{agent_id}/knowledge/sources", headers={"Authorization": f"Bearer {os.environ['FLOWYTE_API_KEY']}"}, json={"kind": "url", "label": "Help center", "url": "https://acme.com/help"}, ) source = r.json()["data"] ``` The source is created with `status: "pending"`. Capture `data.id` as `SOURCE_ID`. Ingestion is **asynchronous** — the source moves `pending` → `indexed`. Poll the source every few seconds until `data.status` is `indexed`. Don't rely on it before then. ```bash theme={null} curl https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge/sources/$SOURCE_ID \ -H "Authorization: Bearer $FLOWYTE_API_KEY" # { "data": { "id": "...", "status": "indexed", ... } } ``` Before you trust it, ask what the agent would retrieve for a real question. `preview` returns the matching chunks and a `topScore` — a low score or empty `chunks` means the answer isn't covered yet. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge/preview \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Do you offer same-day appointments?" }' ``` Pass `sourceIds` to scope the preview to specific sources. Use this to confirm a newly added source actually answers the question you added it for. Once callers start talking to the agent, the questions it couldn't answer surface as **knowledge gaps** — deduped and, above a small volume floor, ranked by how often and how recently they hit. This is the loop that makes the agent better over time. ```bash theme={null} curl "https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge-gaps?status=open" \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` The response's `dataState` is `collecting` until enough calls land, then `ready`. When `ranked` is `true`, each gap carries a `gapId`, `rankScore`, and `classification`. Fix a real gap by adding a source that covers it (back to step 1), then mark the gap handled. You can set a ranked gap to `in_progress`, `dismissed`, or back to `open` — `covered` is set automatically once the live knowledge base answers it. ```bash theme={null} curl -X PATCH https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge-gaps/$GAP_ID \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "status": "in_progress" }' ``` Learn how retrieval fits the rest of the agent in [Knowledge](/concepts/knowledge). # Build an HVAC answering agent Source: https://docs.flowyte.com/guides/build-hvac-agent An answering agent backed by your field-service software — recognizes customers, captures requests, checks job status, and escalates emergencies. This guide builds a working answering agent for a home-services business (HVAC, plumbing, electrical — same shape), backed by your **field-service software**. It recognizes returning customers, captures new jobs straight into your system, tells callers when their tech is coming, and escalates emergencies to a person. Every step is one API call against `https://builder.flowyte.com/api/v1`. Authenticate every request with a secret API key: `Authorization: Bearer flowyte_sk_…`. See [Authentication](/get-started/authentication) to mint one. ## What you'll wire up | Action | Endpoint | Scope | | ----------------------------------- | ------------------------------------------------- | -------------------- | | Create the agent | `POST /agents` | `agents:write` | | Add knowledge | `POST /agents/{id}/knowledge/sources` | `knowledge:write` | | Connect your field-service software | `POST /integrations/{kind}/connect` | `integrations:write` | | Provision its skills | `POST /agents/{id}/integrations/{kind}/provision` | `skills:write` | | Add a transfer skill | `POST /agents/{id}/skills` | `skills:write` | | Publish | `POST /agents/{id}/publish` | `agents:write` | | Simulate | `POST /agents/{id}/simulate` | `agents:read` | | Assign a number | `POST /numbers/{id}/assign` | `numbers:write` | Give it a name and a primary language. Capture `data.id` as your `AGENT_ID`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Capital Comfort Heating & Air", "primaryLanguage": "en" }' ``` Add your service area, pricing, and FAQs. Ingestion is **asynchronous** — poll the source until `status` is `indexed`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge/sources \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "kind": "url", "label": "Services & pricing", "url": "https://capitalcomfort.com/services" }' ``` Find your provider's `kind` in `GET /integrations/catalog`, then start the connection. For an OAuth provider this returns an authorization link — open it once to approve access to your account. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/$KIND/connect \ -H "Authorization: Bearer $FLOWYTE_API_KEY" # → { "data": { "oauthUrl": "https://provider.example.com/oauth/authorize?…" } } ``` Turn the connected account into skills on the agent in one call. By default it provisions the key actions — recognize a customer (`find_client`), capture a request (`create_lead`), and check a job's status (`get_job_status`). ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/integrations/$KIND/provision \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` Now the agent can greet a returning caller by name, open a request for a new job, and read back a customer's next scheduled visit. For a burst pipe or a no-heat call, add a `transfer` skill that routes to your on-call person. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/skills \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Transfer to on-call", "description": "Transfer the caller to a person for emergencies like no heat, a gas smell, or a flooding leak.", "skillType": "transfer", "executionConfig": { "destination": "+14155550199" } }' ``` Editing changes a **draft**; phone and chat serve the last **published** version. Publish to freeze a version your channels can serve. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publish \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` Test before a real caller hits it. `simulate` streams over SSE — read it with `fetch()` streaming and stop on `event: done`. ```bash theme={null} curl -N -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/simulate \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "message": "Hi, my furnace stopped working and I need someone out today.", "draftMode": false }' ``` Buy a number and point it at the agent (search → reserve → purchase → assign). See [Buy a number & go live](/guides/buy-a-number). No field-service integration? You can still capture requests by email or webhook and book to a connected calendar — the same shape, different skills. # Buy a number & go live Source: https://docs.flowyte.com/guides/buy-a-number Search available numbers, reserve one, purchase it from your wallet, assign it to an agent, and route real calls. To take real phone calls, your agent needs a phone number. This guide searches the carrier's inventory, holds a number so it can't be sniped, purchases it from your prepaid wallet, and points it at a published agent. All paths are relative to `https://builder.flowyte.com/api/v1`. Authenticate with `Authorization: Bearer flowyte_sk_…`. Purchasing **debits your prepaid wallet** — top it up first if needed. ## What you'll use | Action | Endpoint | Scope | | ------------------ | --------------------------- | --------------- | | Search inventory | `GET /numbers/search` | `numbers:read` | | Reserve (hold) | `POST /numbers/reserve` | `numbers:write` | | Purchase | `POST /numbers/purchase` | `numbers:write` | | Assign to an agent | `POST /numbers/{id}/assign` | `numbers:write` | | Check wallet | `GET /billing/wallet` | `billing:read` | Filter by area code, city, state, capabilities, or a vanity pattern. `bestEffort` (default `true`) widens a too-narrow filter to nearby matches — set it `false` for strict last-four or vanity matching. Each result carries an `e164` and a price. ```bash curl theme={null} curl "https://builder.flowyte.com/api/v1/numbers/search?areaCode=415&numberType=local&features=voice" \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` ```ts Node theme={null} const params = new URLSearchParams({ areaCode: "415", numberType: "local", features: "voice" }); const res = await fetch(`https://builder.flowyte.com/api/v1/numbers/search?${params}`, { headers: { Authorization: `Bearer ${process.env.FLOWYTE_API_KEY}` }, }); const { data: numbers } = await res.json(); ``` ```python Python theme={null} import os, requests r = requests.get( "https://builder.flowyte.com/api/v1/numbers/search", headers={"Authorization": f"Bearer {os.environ['FLOWYTE_API_KEY']}"}, params={"areaCode": "415", "numberType": "local", "features": "voice"}, ) numbers = r.json()["data"] ``` Pick an `e164` from the results, e.g. `+14155551234`. Hold the number (\~30 minutes) so it can't be purchased out from under you while you confirm. Reserving does **not** charge your wallet. Capture the returned reservation `id`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/numbers/reserve \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "e164": "+14155551234" }' ``` Reserving is optional but recommended for interactive flows. A `409` means the number can no longer be held — search again and pick another. Purchase debits your prepaid wallet. Pass the `reservationId` from the previous step so the order can't be sniped. A `402` means insufficient balance — top up with `POST /billing/wallet/topup`, then retry. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/numbers/purchase \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "e164": "+14155551234", "reservationId": "'$RESERVATION_ID'" }' ``` The response is the purchased number. Capture `data.id` as `NUMBER_ID`. Already own a number elsewhere? Use `POST /numbers/import` instead — it routes a number on your carrier account to this deployment with no wallet charge. Point the number at your agent. Inbound calls now route to the agent's **published** version. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/numbers/$NUMBER_ID/assign \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "agentId": "'$AGENT_ID'" }' ``` Calls serve the last **published** version of the agent — not your draft. If you haven't published since your latest edits, publish now, then dial the number. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publish \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` An assigned number routes to the agent's published version. If the agent has never been published, callers reach nothing — publish before you dial. ## Managing numbers later To stop using a number without losing it, `DELETE /numbers/{id}/assign` moves it back to your pool (you keep owning and paying for it). `DELETE /numbers/{id}` permanently releases it to the carrier. SMS on a number requires per-organization 10DLC brand and campaign registration before sends are allowed. # Verify a caller before disclosure Source: https://docs.flowyte.com/guides/caller-verification Make the agent prove a caller's identity before it reads back account details or takes a sensitive action. When an agent can look up orders, balances, or appointments, you don't want it reading those back to *anyone* who calls. **Caller verification** is a deterministic guardrail: the agent must verify identity before it discloses sensitive fields or runs a write action. Unlike a prompt instruction, this is enforced, not suggested. All paths are relative to `https://builder.flowyte.com/api/v1`. Authenticate with `Authorization: Bearer flowyte_sk_…`. Both endpoints below are **replace** (`PUT`) — send the full list each time. It comes together in three parts: 1. A **verifier skill** that checks the caller's answers against your system of record. 2. A **caller-verification config** that ties a verification method to that verifier. 3. A **guardrail** plus per-skill flags that block disclosure until the caller is verified. ## What you'll use | Action | Endpoint | Scope | | ---------------------------------- | ---------------------------------------------- | -------------------------------------- | | Create the verifier skill | `POST /agents/{id}/skills` | `skills:write` | | Read / replace verification config | `GET` · `PUT /agents/{id}/caller-verification` | `guardrails:read` · `guardrails:write` | | Read / replace guardrail policies | `GET` · `PUT /agents/{id}/guardrails` | `guardrails:read` · `guardrails:write` | | Gate a disclosure skill | `PATCH /agents/{id}/skills/{id}` | `skills:write` | ## Verification methods | `method` | The caller proves identity with | | ----------------- | ------------------------------------------ | | `dob_postcode` | date of birth + postcode | | `account_pin` | an account PIN | | `otp_sms` | a one-time code sent by text | | `knowledge_based` | answers only the account holder would know | `otp_sms` sends a text, which requires per-organization 10DLC brand and campaign registration before sends are allowed. The verifier is a skill (commonly `db_query` or `http_webhook`) that takes the caller's claimed identity plus a secret and returns whether they match. Capture its `id` — you'll reference it as `verifierSkillId`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/skills \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Verify identity", "description": "Confirm the caller by date of birth and postcode.", "skillType": "http_webhook", "executionConfig": { "url": "https://api.example.com/verify", "method": "POST" } }' ``` Tie a method to the verifier. `tokenTtlSeconds` controls how long a verification stays valid on the call; `maxAttempts` caps retries; `stepUpFor` names actions that demand a re-check even after basic verification. ```bash theme={null} curl -X PUT https://builder.flowyte.com/api/v1/agents/$AGENT_ID/caller-verification \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "method": "dob_postcode", "requiredFields": ["date_of_birth", "postcode"], "verifierSkillId": "'$VERIFIER_SKILL_ID'", "tokenTtlSeconds": 600, "maxAttempts": 3, "stepUpFor": ["refund"] } ]' ``` Add a `verify_before_disclose` policy, then mark every skill that returns sensitive data with `requiresVerifiedIdentity: true`. Use `verificationLevel` (`basic` or `step_up`) for higher-risk actions, and `allowedFields` to cap what may be read back. ```bash Guardrail theme={null} curl -X PUT https://builder.flowyte.com/api/v1/agents/$AGENT_ID/guardrails \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '[ { "policyType": "verify_before_disclose", "appliesToSkillType": "shopify", "isEnabled": true } ]' ``` ```bash Gate a skill theme={null} curl -X PATCH https://builder.flowyte.com/api/v1/agents/$AGENT_ID/skills/$SKILL_ID \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "requiresVerifiedIdentity": true, "verificationLevel": "basic" }' ``` Verification is enforced at runtime, but it only protects skills you actually gate. Audit every skill that returns account data and set `requiresVerifiedIdentity`. Changes apply on **publish**. Related: [Guardrails](/concepts/guardrails) · [Skills](/concepts/skills) · [Draft vs published](/get-started/draft-vs-published) # Connect a SQL database Source: https://docs.flowyte.com/guides/connect-sql-database Give an agent live read (and scoped write) access to your own Postgres or MySQL database — safely, one column at a time. This guide connects a **PostgreSQL** or **MySQL** database to an agent so it can answer from live records on a call — for example "when's my appointment?" — using a least-privilege user, data scoping, and a bound read query. Every step is one API call against `https://builder.flowyte.com/api/v1`. Authenticate every request with a secret API key: `Authorization: Bearer flowyte_sk_…`. See [Authentication](/get-started/authentication) to mint one. Below, `KIND` is `postgres` or `mysql`. ## What you'll wire up | Action | Endpoint | Scope | | ------------------------- | ------------------------------------------------ | -------------------- | | Generate a scoped DB user | `GET /integrations/{kind}/sql/scripts` | `integrations:read` | | Test the connection | `POST /integrations/{kind}/sql/test` | `integrations:write` | | Connect | `POST /integrations/{kind}/connect` | `integrations:write` | | Discover the schema | `POST /integrations/{kind}/discover` | `integrations:write` | | Scope out sensitive data | `PATCH /integrations/{kind}/scoping` | `integrations:write` | | Bind a read as a skill | `POST /agents/{id}/integrations/{kind}/bindings` | `skills:write` | | Publish | `POST /agents/{id}/publish` | `agents:write` | Generate the setup SQL, then run it on your database as an admin. Never connect Flowyte with your admin credentials — create a scoped user instead. ```bash theme={null} curl "https://builder.flowyte.com/api/v1/integrations/postgres/sql/scripts?database=appdb" \ -H "Authorization: Bearer $FLOWYTE_API_KEY" # → { "data": { "readOnly": { "sql": "CREATE ROLE flowyte_ro …" }, "generatedPassword": "…" } } ``` For read-only lookups, run the `readOnly` script. Only add the scoped `write` script (pass `?tables=appointments,requests`) if the agent needs to insert or update rows. Validate before you store anything. The check runs reachability, TLS, auth, a read probe, a read-only-session proof, and a latency verdict, and returns an overall `ok`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/sql/test \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "credentials": { "host": "db.example.com", "port": "5432", "database": "appdb", "user": "flowyte_ro", "password": "…", "sslmode": "verify-full" } }' # → { "data": { "ok": true, "checks": [ … ] } } ``` `sslmode` must be `require`, `verify-ca`, or `verify-full` (`disable` is refused). A host that resolves to a private, loopback, or metadata address is refused too. Pass the same credentials to the connect endpoint. They're encrypted at rest and never returned. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/connect \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "credentials": { "host": "db.example.com", "port": "5432", "database": "appdb", "user": "flowyte_ro", "password": "…", "sslmode": "verify-full" } }' # → { "data": { "status": "connected" } } ``` Introspect the database, then block tables or columns the agent should never see — they vanish from the schema browser and are refused at bind time. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/discover \ -H "Authorization: Bearer $FLOWYTE_API_KEY" curl -X PATCH https://builder.flowyte.com/api/v1/integrations/postgres/scoping \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "blockedColumns": { "customers": ["ssn", "card_last4"] } }' ``` Map a read onto a skill: pick the operation, map the caller's input to its filter, and project the columns you want back. It compiles to a frozen, parameterized query — the agent supplies the parameter and never sees raw SQL. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/integrations/postgres/bindings \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "toolName": "Look up an appointment", "description": "Find the caller'\''s appointment by phone and read back the time and status.", "operation": "appointments.read", "inputs": [ { "param": "caller_phone", "arg": "phone", "required": true } ], "projection": [ { "path": ["appointment", "starts_at"], "leaf": "appointment_time" }, { "path": ["appointment", "status"], "leaf": "appointment_status" } ], "enabled": true }' ``` Don't want to hand-map? `POST /agents/{id}/integrations/postgres/bindings/auto` takes a plain-language goal ("look up the caller's next appointment by phone") and the AI assistant proposes the binding, saved as a draft to review. Binding edits the **draft**. Publish so live callers get real data, then try it in the simulator. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publish \ -H "Authorization: Bearer $FLOWYTE_API_KEY" curl -N -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/simulate \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "message": "Hi, can you tell me when my appointment is?", "draftMode": false }' ``` ## Safety model * **Least privilege** — you connect a scoped user, not an admin. Reads are read-only; writes are limited to the tables you granted. * **Data scoping** — blocked tables and columns never reach the schema browser, the AI assistant, or a binding. * **Frozen queries** — a binding compiles to one parameterized statement. The agent passes parameters; it can't run arbitrary SQL, and writes land disabled until you review them. See the [SQL database connector](/integrations/sql-database) for the full endpoint reference. # Embed the chat widget Source: https://docs.flowyte.com/guides/embed-chat-widget Mint a browser-safe publishable key, drop in the loader snippet, and lock it to the origins you control. The chat widget puts a published agent on your website. It runs from a **publishable key** (`flowyte_pk_…`) — a browser-safe key scoped to a single agent, locked to an origin allowlist, and limited to the `chat:public` capability so it can never read tenant data. You mint the key with your secret key, then paste a loader snippet. All paths are relative to `https://builder.flowyte.com/api/v1`. Mint and manage keys server-side with your secret key (`flowyte_sk_…`). The publishable key is safe to ship in a web page; **never** put a secret key in client-side code. ## What you'll use | Action | Endpoint | Scope | | ---------------------- | ------------------------------------------------ | --------------- | | Mint a publishable key | `POST /agents/{id}/publishable-keys` | `pubkeys:write` | | Get the embed snippet | `GET /agents/{id}/widget/embed` | `widgets:read` | | Customize theme / copy | `PUT /agents/{id}/widget` | `widgets:write` | | Rotate the key | `POST /agents/{id}/publishable-keys/{id}/rotate` | `pubkeys:write` | | Revoke the key | `DELETE /agents/{id}/publishable-keys/{id}` | `pubkeys:write` | `allowedOrigins` is the allowlist of sites that may use this key. Match each origin exactly — scheme + host (+ port). Include both the apex and `www` if you serve both. Set `rateLimitRpm` to bound abuse. The full key is returned in `keyPublic` (it's public, so it isn't show-once). ```bash curl theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publishable-keys \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Marketing site", "allowedOrigins": ["https://www.example.com", "https://example.com"], "env": "live", "rateLimitRpm": 60 }' ``` ```ts Node theme={null} const res = await fetch( `https://builder.flowyte.com/api/v1/agents/${agentId}/publishable-keys`, { method: "POST", headers: { Authorization: `Bearer ${process.env.FLOWYTE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Marketing site", allowedOrigins: ["https://www.example.com", "https://example.com"], env: "live", rateLimitRpm: 60, }), }, ); const { data: key } = await res.json(); // key.keyPublic = flowyte_pk_live_… ``` Pass the key's `publishableKeyId` to get the loader. The response carries `scriptSnippet` and the `publishableKey` to drop into your page. ```bash theme={null} curl "https://builder.flowyte.com/api/v1/agents/$AGENT_ID/widget/embed?publishableKeyId=$PUBLISHABLE_KEY_ID" \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` Copy the exact `scriptSnippet` returned above into your page, just before ``. It looks like a single async loader tag carrying your publishable key: ```html theme={null} ``` The widget loads its theme and copy from a public bootstrap call — no secrets in the browser. Tune the look and wording without touching the snippet. `PUT /agents/{id}/widget` accepts `theme`, `copy`, and `behavior` objects. ```bash theme={null} curl -X PUT https://builder.flowyte.com/api/v1/agents/$AGENT_ID/widget \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "theme": { "accent": "#2563eb" }, "copy": { "greeting": "Hi! How can I help?" } }' ``` A request from an origin that isn't on the allowlist is rejected with **403** — add the exact origin and try again. To roll a key without changing the agent or origins, use **rotate** (it keeps a short grace window); use **revoke** to kill it immediately. The widget always serves the agent's **published** version. Publish after any edits, and make sure the agent has at least one enabled chat channel. Related: [Agents](/concepts/agents) · [Draft vs published](/get-started/draft-vs-published) · [OpenAI-compatible chat](/guides/openai-compatible-chat) # Map an integration's fields Source: https://docs.flowyte.com/guides/map-integration-fields Connect any REST or GraphQL system, discover its schema, and map its fields onto your agent — no per-provider code. When a provider's schema can be introspected, you don't need pre-built actions. You **discover** its entire data model, then **map** the fields you want onto your agent's parameters. The mapping *is* the integration. This guide builds a "look up the caller" skill against a connected CRM. Authenticate every request with a secret API key: `Authorization: Bearer flowyte_sk_…`. Base URL `https://builder.flowyte.com/api/v1`. Connect it once for your organization (OAuth approval, or API-key credentials). ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/acme_crm/connect \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` Introspect the provider into a normalized schema. The response summarizes what was found. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/acme_crm/discover \ -H "Authorization: Bearer $FLOWYTE_API_KEY" # → { "data": { "objectCount": 38, "fieldCount": 612, "edgeCount": 74, ... } } ``` Read the discovered schema to see the objects, their fields, the relationships between them, and the operations you can run. This is the catalog you map from. ```bash theme={null} curl https://builder.flowyte.com/api/v1/integrations/acme_crm/schema \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` Find the operation you want (here, a `findContact` query) and the fields you want back (`contact.name`, `contact.email`). Map the operation onto a skill. **`inputs`** map your agent's parameters to the operation's arguments; **`projection`** picks which provider fields come back, each renamed to one of your own output names (`leaf`). The mapping is validated against the schema before it's created. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/integrations/acme_crm/bindings \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "toolName": "Look up the caller", "description": "Find the calling customer in the CRM by phone and return their name and email.", "operation": "findContact", "inputs": [ { "param": "caller_phone", "arg": "phone", "required": true, "description": "The caller'\''s phone number" } ], "projection": [ { "path": ["contact", "name"], "leaf": "customer_name" }, { "path": ["contact", "email"], "leaf": "customer_email" } ], "enabled": true }' ``` This compiles to a skill on the agent. The agent now calls `Look up the caller` with the caller's phone and gets back `customer_name` and `customer_email` — without ever seeing the provider's raw field tree. The new skill takes effect on the **published** agent. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publish \ -H "Authorization: Bearer $FLOWYTE_API_KEY" curl -N -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/simulate \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "message": "Hi, it'\''s me calling about my account.", "draftMode": false }' ``` ## How it fits together * You map onto **canonical parameters** (`caller_phone`, `caller_name`, `service_address`, …), so one mapping concept is reusable across any provider and your agent's prompt never changes. * The mapping is **validated against the discovered schema** when you save it — a field that doesn't exist, or one the operation can't return, is caught up front, not on a live call. * Set `enabled: false` to save a binding as a **draft** to review before it ships. **Don't want to map by hand?** `POST /agents/{agentId}/integrations/{kind}/bindings/auto` takes a plain-language goal — "look up a caller by phone and tell me their open tickets" — and the AI assistant proposes the whole binding (operation, input mapping, and projection) over the discovered schema. It's validated and saved as a **disabled draft** for you to review, then enable. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/integrations/acme_crm/bindings/auto \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "goal": "Look up the caller by phone and return their name and open tickets" }' ``` Some providers also ship a **Connector Pack** of ready-made preset skills — install them in one call with `POST /agents/{agentId}/integrations/{kind}/pack/install` (read it first with `GET /integrations/{kind}/pack`). # Use the OpenAI-compatible chat endpoint Source: https://docs.flowyte.com/guides/openai-compatible-chat Point an OpenAI SDK at Flowyte with a base-URL swap and talk to your agent — no new client to learn. Flowyte exposes an **OpenAI-compatible** chat completions endpoint. If your code already uses an OpenAI client library, you can talk to a Flowyte agent by changing two things: the base URL and the model. The `model` is your **agent id**, and the agent's knowledge, skills, and guardrails all apply to the response. All paths are relative to `https://builder.flowyte.com/api/v1`. Authenticate with your secret key `Authorization: Bearer flowyte_sk_…` (scope `chat:write`). This endpoint serves the agent's **published** version. ## What you'll use | Action | Endpoint | Scope | | ------------------- | ------------------------ | ------------ | | Create a completion | `POST /chat/completions` | `chat:write` | ## The base-URL swap Set the client's base URL to `https://builder.flowyte.com/api/v1`, the API key to your `flowyte_sk_…`, and pass the agent id as `model`. The SDK appends `/chat/completions` for you. ```ts Node theme={null} import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.FLOWYTE_API_KEY, // flowyte_sk_… baseURL: "https://builder.flowyte.com/api/v1", // ← the only swap }); const completion = await client.chat.completions.create({ model: "agt_123", // ← your agent id messages: [{ role: "user", content: "Do you ship to Canada?" }], }); console.log(completion.choices[0].message.content); ``` ```python Python theme={null} from openai import OpenAI client = OpenAI( api_key="flowyte_sk_…", base_url="https://builder.flowyte.com/api/v1", # ← the only swap ) resp = client.chat.completions.create( model="agt_123", # ← your agent id messages=[{"role": "user", "content": "Do you ship to Canada?"}], ) print(resp.choices[0].message.content) ``` ```bash curl theme={null} curl https://builder.flowyte.com/api/v1/chat/completions \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "agt_123", "messages": [{ "role": "user", "content": "Do you ship to Canada?" }] }' ``` The response is the standard completion shape: read the reply from `choices[0].message.content`. ## Streaming Set `stream: true` and the endpoint returns OpenAI-style SSE chunks — `data: {choices:[{delta:{content}}]}` … terminated by `data: [DONE]` — so an OpenAI SDK's streaming mode works unchanged. ```ts theme={null} const stream = await client.chat.completions.create({ model: "agt_123", messages: [{ role: "user", content: "Summarize your return policy." }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); } ``` ## What's read, and what isn't * Only `model`, `messages`, `stream`, and `user` are used. The agent's own configuration governs its behavior — sampling, tools, and system prompt come from the published agent, so request-level overrides for those are ignored. * `model` must be a real agent id, not an OpenAI model name. An unknown id returns a `400`. * Roles `system`, `user`, `assistant`, and `tool` are accepted in `messages`. Need session state, tool-call events, or quick replies? Use the native chat API instead: `POST /chat/sessions` then `POST /chat/sessions/{id}/messages` with `stream: true`. For an in-browser widget, mint a publishable key — see [Embed the chat widget](/guides/embed-chat-widget). Related: [Agents](/concepts/agents) · [Authentication](/get-started/authentication) # Take an order with a delivery check Source: https://docs.flowyte.com/guides/take-orders A phone agent that knows your menu, confirms the delivery address is in range, and sends the order to your kitchen. This guide builds a phone agent for a restaurant that answers menu questions, takes an order, checks that the delivery address is in your zone, and posts the confirmed order to your kitchen. Every step is one API call against `https://builder.flowyte.com/api/v1`. Authenticate every request with a secret API key: `Authorization: Bearer flowyte_sk_…`. See [Authentication](/get-started/authentication) to mint one. Capture `data.id` as your `AGENT_ID`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Tony's Pizza", "primaryLanguage": "en" }' ``` Add your menu so the agent answers "what's on the supreme?" and "how much is a large?" from the real thing. Ingestion is **asynchronous** — poll the source until `status` is `indexed`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge/sources \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "kind": "url", "label": "Menu", "url": "https://tonyspizza.com/menu" }' ``` A free `geo` skill with the `check_service_area` action. Set your shop address as the origin and your delivery radius. The agent asks for the address and confirms coverage before taking a delivery order — and offers pickup if it's out of range. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/skills \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Check delivery area", "description": "Check whether the caller's delivery address is within our delivery zone before taking a delivery order.", "skillType": "geo", "executionConfig": { "action": "check_service_area", "originAddress": "120 Main St, Boulder, CO", "radiusMiles": 5 } }' ``` An `http_webhook` skill posts the confirmed order to your kitchen display or POS. The agent fills in the parameters it collected on the call. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/skills \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Submit order", "description": "Send the confirmed order to the kitchen. Use after the caller confirms items and (for delivery) the address is in range.", "skillType": "http_webhook", "executionConfig": { "method": "POST", "url": "https://tonyspizza.com/api/orders", "parameters": [ { "name": "items", "description": "The ordered items with size and quantity" }, { "name": "fulfillment", "description": "delivery or pickup" }, { "name": "address", "description": "Delivery address, if delivery" }, { "name": "phone", "description": "Callback number" } ] } }' ``` Publish, then simulate an order. `simulate` streams over SSE — read it with `fetch()` streaming and stop on `event: done`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publish \ -H "Authorization: Bearer $FLOWYTE_API_KEY" curl -N -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/simulate \ -H "Authorization: Bearer $FLOWYTE_API_KEY" -H "Content-Type: application/json" \ -d '{ "message": "Can I get a large supreme delivered to 200 Pearl St?", "draftMode": false }' ``` Buy a number and point it at the agent — see [Buy a number & go live](/guides/buy-a-number). The agent only spends on a geocode when it has to look up a caller's address; the in/out-of-zone math itself is free. # Warm transfer to a human Source: https://docs.flowyte.com/guides/warm-transfer Hand a live caller off to a person — with context, and only under the conditions you choose. A **warm transfer** moves a live caller from the agent to a real person, after the agent has gathered who is calling and why. Use it when a request is out of scope, the caller is frustrated, or a rule says a human must take over. The agent stays in control right up to the moment it connects the call. All paths are relative to `https://builder.flowyte.com/api/v1`. Authenticate with `Authorization: Bearer flowyte_sk_…`. Destination numbers are saved in E.164 (e.g. `+14155550100`); a 10-digit US number is normalized on save. There are two pieces, and most agents use both: * **Handoff config** on the agent — a default destination plus a "transfer by context" table that routes described situations to specific numbers. * **A transfer skill** — a tool the agent calls mid-conversation to move the caller to a fixed number, which you can gate with a spoken confirmation. ## What you'll use | Action | Endpoint | Scope | | -------------------------------- | --------------------------- | -------------- | | Set default + by-context handoff | `PATCH /agents/{id}` | `agents:write` | | Add a transfer skill | `POST /agents/{id}/skills` | `skills:write` | | List an agent's skills | `GET /agents/{id}/skills` | `skills:read` | | Publish the change | `POST /agents/{id}/publish` | `agents:write` | The agent's `handoffConfig` holds a default `transferDestination` and a `transferRules` table. Each rule has a plain-language `when` the agent matches against, plus a `destination`. Set `warm: true` so the agent summarizes the caller and the reason before connecting, rather than a silent hand-off. ```bash curl theme={null} curl -X PATCH https://builder.flowyte.com/api/v1/agents/$AGENT_ID \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "handoffConfig": { "warm": true, "transferDestination": "+14155550100", "transferRules": [ { "label": "Billing", "when": "caller asks about an invoice, charge, or refund", "destination": "+14155550111" }, { "label": "Spanish line", "when": "caller prefers Spanish", "destination": "+14155550122" } ] } }' ``` ```ts Node theme={null} await fetch(`https://builder.flowyte.com/api/v1/agents/${agentId}`, { method: "PATCH", headers: { Authorization: `Bearer ${process.env.FLOWYTE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ handoffConfig: { warm: true, transferDestination: "+14155550100", transferRules: [ { label: "Billing", when: "caller asks about an invoice, charge, or refund", destination: "+14155550111" }, ], }, }), }); ``` The default `transferDestination` is used whenever a transfer is needed and no rule matches. A `transfer` skill is a tool the agent can call on demand. Its `description` is what the agent reads to decide *when* to use it — write it as the condition. Set `requiresConfirmation` so the agent confirms before transferring, and `stakes` to tune that gate. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/skills \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Transfer to billing", "description": "Transfer the caller to a billing specialist when they ask about an invoice, charge, or refund.", "skillType": "transfer", "executionConfig": { "destination": "+14155550111" }, "requiresConfirmation": true, "stakes": "medium" }' ``` A data-lookup skill can pass a **dynamic** destination at call time (e.g. route to the rep who owns the account), instead of a fixed number — useful for franchises and large rosters. Edits change the **draft**. Phone and chat serve the last **published** version, so publish to make the transfer behavior live. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publish \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` ## Choosing when a transfer fires * **By context** — the `when` text on each rule and the `description` on a transfer skill are how the agent decides; keep them specific. * **On silence** — set `callControl.onNoResponseAction` to `transfer` so an unresponsive caller is routed to a person instead of being ended. * **After hours** — by default the agent does *not* transfer to a live person when closed. Set `afterHours.allowHumanTransfer` and a `number` to allow it. See [After hours](/solutions/after-hours). The call is transferred off-net over the carrier (a blind SIP REFER), with the original caller ID passed through. Verify the destination number is one you control and is staffed during the hours the rule can fire. Related: [Skills](/concepts/skills) · [Draft vs published](/get-started/draft-vs-published) # Flowyte Source: https://docs.flowyte.com/index Build AI voice and chat agents that answer calls, book jobs, and resolve customer questions — all via API. Flowyte is an **API-first platform for building AI voice and chat agents**. Create an agent, give it knowledge and skills, publish it, and put it on a phone number or an embeddable chat widget — every step is available through the REST API, so an agent (or your code) can build and operate another agent end to end. Mint a key and build a working agent via the API in about five minutes. API keys, scopes, and how the dual browser / key auth model works. Agents, skills, knowledge, guardrails, and playbooks — what each one does. The full REST contract, generated from the OpenAPI spec. ## What you can build * **An AI answering service** that handles calls 24/7, answers questions from your knowledge base, and transfers to a human when it should. * **Field-service automation** — book a visit, look up a job's status, or capture a new request on the phone. * **Order status & support** — resolve "where is my order" and common questions across voice and chat. ## How it fits together An **agent** is the single thing you configure: its persona, language, and voice. Add **knowledge** (your FAQs, docs, and URLs) so it can answer accurately, and **skills** (tools) so it can take actions like booking or transferring. **Guardrails** keep it on-policy; **playbooks** script multi-step flows for repeatable tasks. **Publish** a frozen version, then connect a phone number or embed the chat widget. Ready? Head to the [Quickstart](/quickstart). # Build a custom integration Source: https://docs.flowyte.com/integrations/custom Call your own endpoint from a conversation with an http_webhook skill. No native connector for your system? Build a [skill](/concepts/skills) of type `http_webhook`. The agent collects inputs from the caller, then calls **your** HTTP endpoint and uses the response in the conversation. Your service is the integration — Flowyte handles the conversation and the call. ## How it works A custom skill has two halves: * **`parametersSchema`** — a JSON Schema describing what the agent collects from the caller. Use a proper object schema with a top-level `properties` map (not a flat map). Put only caller-supplied inputs here. * **`executionConfig`** — where to send them: `url` (required), plus optional `method`, `headers`, and `timeout_ms`. Operator settings live here, never in `parametersSchema`. When the agent decides the skill is relevant, it fills the parameters in natural language and POSTs them to your `url`. ## Create the skill ```bash curl theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/AGENT_ID/skills \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{ "name": "Check warranty status", "description": "Look up a warranty by serial number.", "skillType": "http_webhook", "parametersSchema": { "type": "object", "properties": { "serial_number": { "type": "string", "description": "Product serial number" } }, "required": ["serial_number"] }, "requiredParams": ["serial_number"], "executionConfig": { "url": "https://api.yourco.com/warranty/lookup", "method": "POST", "headers": { "X-Api-Key": "…" }, "timeout_ms": 4000 } }' ``` ```python Python theme={null} import requests requests.post( "https://builder.flowyte.com/api/v1/agents/AGENT_ID/skills", headers={"Authorization": "Bearer flowyte_sk_…"}, json={ "name": "Check warranty status", "description": "Look up a warranty by serial number.", "skillType": "http_webhook", "parametersSchema": { "type": "object", "properties": { "serial_number": {"type": "string", "description": "Product serial number"} }, "required": ["serial_number"], }, "requiredParams": ["serial_number"], "executionConfig": { "url": "https://api.yourco.com/warranty/lookup", "method": "POST", "headers": {"X-Api-Key": "…"}, "timeout_ms": 4000, }, }, ) ``` Keep your endpoint fast — return within `timeout_ms` so the agent doesn't stall the caller. Return a small JSON body the agent can read back. ## The MCP path (coming soon) A managed MCP gateway — exposing your tools to the agent over the Model Context Protocol, the same surface a customer's own LLM can drive — is on the roadmap. The `mcp` skill type is reserved for it. Until it ships, use `http_webhook` for custom actions. Reserved endpoints respond with **403** (not 404), so a 403 means a capability exists but isn't enabled for you yet. ## In the API | Action | Endpoint | Scope | | ---------------------- | ------------------------------------------------ | -------------- | | Create a custom skill | `POST /agents/{agentId}/skills` | `skills:write` | | List skills | `GET /agents/{agentId}/skills` | `skills:read` | | Update / remove | `PATCH` · `DELETE /agents/{agentId}/skills/{id}` | `skills:write` | | Browse all skill types | `GET /skill-types` | `skills:read` | New skills land on the **draft**. [Publish](/concepts/agents) so phone and chat callers can use them. # Google Calendar Source: https://docs.flowyte.com/integrations/google-calendar Let an agent check availability and book appointments on a connected calendar. Connect Google Calendar once, then provision its actions as [skills](/concepts/skills) so an agent can **check open times** and **book an appointment** mid-conversation — the agent collects the details from the caller in natural language and writes the event for you. ## Connect Google Calendar uses OAuth. Start the connect, then finish consent in a browser. `POST /integrations/google_calendar/connect` returns an `oauthUrl`. Open the `oauthUrl` while signed in to your dashboard. The browser-only callback finishes the connection — it cannot be completed with an API key. `GET /integrations` now lists `google_calendar` with `status: connected` and an `accountLabel`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/google_calendar/connect \ -H "Authorization: Bearer flowyte_sk_…" # → { "data": { "oauthUrl": "https://…" } } # open this in a browser ``` ## Provision actions as skills List the actions, then provision the ones you want onto an agent. ```bash curl theme={null} # See what's available curl https://builder.flowyte.com/api/v1/integrations/google_calendar/actions \ -H "Authorization: Bearer flowyte_sk_…" # Provision the important actions (omit "actions" to take all of them) curl -X POST \ https://builder.flowyte.com/api/v1/agents/AGENT_ID/integrations/google_calendar/provision \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{ "actions": ["check_availability", "book_appointment"] }' ``` ```js Node theme={null} const res = await fetch( "https://builder.flowyte.com/api/v1/agents/AGENT_ID/integrations/google_calendar/provision", { method: "POST", headers: { Authorization: "Bearer flowyte_sk_…", "Content-Type": "application/json", }, body: JSON.stringify({ actions: ["check_availability", "book_appointment"] }), }, ); const { data } = await res.json(); // [{ skill, created }] ``` ```python Python theme={null} import requests res = requests.post( "https://builder.flowyte.com/api/v1/agents/AGENT_ID/integrations/google_calendar/provision", headers={"Authorization": "Bearer flowyte_sk_…"}, json={"actions": ["check_availability", "book_appointment"]}, ) data = res.json()["data"] # [{ "skill": …, "created": true }] ``` Each returned item carries `created: false` when the skill already existed — provisioning is an idempotent no-op, so it is safe to re-run. Actions that still need operator config land **disabled** as drafts; open them in the dashboard to finish setup. Action slugs come from `GET /integrations/{kind}/actions` — read that catalog rather than guessing names, since available actions can grow over time. ## In the API | Action | Endpoint | Scope | | ---------------- | ----------------------------------------------------------------- | -------------------- | | Connect | `POST /integrations/google_calendar/connect` | `integrations:write` | | List actions | `GET /integrations/google_calendar/actions` | `integrations:read` | | Provision skills | `POST /agents/{agentId}/integrations/google_calendar/provision` | `skills:write` | | Remove skills | `DELETE /agents/{agentId}/integrations/google_calendar/provision` | `skills:write` | | Disconnect | `DELETE /integrations/google_calendar` | `integrations:write` | Provisioning edits the agent **draft**. [Publish](/concepts/agents) before phone or chat callers can book. # Google Sheets Source: https://docs.flowyte.com/integrations/google-sheets Log every call to a spreadsheet — map captured fields to real columns, no code. Connect Google Sheets once, then provision a **log a row** action as a [skill](/concepts/skills). The agent collects fields from the caller and appends a row to the tab you choose — a lead log, an order record, a callback list. Mapping is config, not code. ## Connect Google Sheets uses OAuth. Begin the connect, then finish consent in a browser. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/google_sheets/connect \ -H "Authorization: Bearer flowyte_sk_…" # → { "data": { "oauthUrl": "https://…" } } # open this in a browser to grant access ``` The browser-only callback completes the connection; it cannot be finished with an API key. After consent, `GET /integrations` lists `google_sheets` as `connected`. ## Resolve the target sheet Before you map columns, confirm the connected account can read the spreadsheet. Pass a Sheets URL or bare id as `ref` and get back the title, its tabs, and each tab's header row — so you map captured params to the **real** column names. ```bash theme={null} curl -G https://builder.flowyte.com/api/v1/integrations/google_sheets/spreadsheet \ -H "Authorization: Bearer flowyte_sk_…" \ --data-urlencode "ref=https://docs.google.com/spreadsheets/d/SHEET_ID/edit" ``` This read-only helper uses only the granted spreadsheet scope. A `409 integration_not_connected` means Sheets isn't connected; a `404 sheet_not_accessible` means the link is wrong or the file isn't shared with the connected account. ## Provision the skill List the actions, then provision onto an agent. The column mapping is part of the skill's config — finish it in the dashboard if the action lands as a draft. ```bash curl theme={null} curl https://builder.flowyte.com/api/v1/integrations/google_sheets/actions \ -H "Authorization: Bearer flowyte_sk_…" curl -X POST \ https://builder.flowyte.com/api/v1/agents/AGENT_ID/integrations/google_sheets/provision \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{ "actions": ["append_row"] }' ``` ```js Node theme={null} await fetch( "https://builder.flowyte.com/api/v1/agents/AGENT_ID/integrations/google_sheets/provision", { method: "POST", headers: { Authorization: "Bearer flowyte_sk_…", "Content-Type": "application/json", }, body: JSON.stringify({ actions: ["append_row"] }), }, ); ``` ```python Python theme={null} import requests requests.post( "https://builder.flowyte.com/api/v1/agents/AGENT_ID/integrations/google_sheets/provision", headers={"Authorization": "Bearer flowyte_sk_…"}, json={"actions": ["append_row"]}, ) ``` ## In the API | Action | Endpoint | Scope | | ---------------- | --------------------------------------------------------------- | -------------------- | | Connect | `POST /integrations/google_sheets/connect` | `integrations:write` | | Resolve a sheet | `GET /integrations/google_sheets/spreadsheet?ref=…` | `integrations:read` | | List actions | `GET /integrations/google_sheets/actions` | `integrations:read` | | Provision skills | `POST /agents/{agentId}/integrations/google_sheets/provision` | `skills:write` | | Remove skills | `DELETE /agents/{agentId}/integrations/google_sheets/provision` | `skills:write` | | Disconnect | `DELETE /integrations/google_sheets` | `integrations:write` | Provisioning edits the **draft**. [Publish](/concepts/agents) so live calls write rows. # Integrations overview Source: https://docs.flowyte.com/integrations/overview Connect an external system once, then provision its actions as skills on any agent. An **integration** is a connection to an external system — a calendar, a spreadsheet, a store. You connect the provider once for your organization, then turn its actions into [skills](/concepts/skills) on any agent. No glue code: the action's parameters become the values the agent collects from the caller, and the platform runs the call. ## The model: catalog → connect → provision `GET /integrations/catalog` returns every connectable provider — its display name, category, a short blurb, a logo slug, how many actions it exposes, whether it ships a preset pack (`hasPack`), and whether your org has already connected it. The dashboard renders this as a card grid; you can render your own. A provider with `actionCount: 0` and `hasPack: true` is pack-based — install its [Connector Pack](#beyond-pre-built-actions-map-fields-yourself) rather than provisioning individual actions. `POST /integrations/{kind}/connect`. OAuth providers return an `oauthUrl` to open in a browser; API-key providers accept `{ "credentials": { … } }` and connect immediately. `POST /agents/{agentId}/integrations/{kind}/provision` creates one skill per action. Omit `actions` to provision every important action, or pass a list of action slugs. Provisioning edits the agent **draft**. [Publish](/concepts/agents) so phone and chat serve the new skills. ## Beyond pre-built actions: map fields yourself For any provider whose schema can be introspected, you're not limited to its pre-built actions — **discover** its full schema and **map** any field onto your agent: `POST /integrations/{kind}/discover` introspects the provider into a normalized catalog of every object, field, relationship, and operation. `GET /integrations/{kind}/schema` returns that catalog to map from. `POST /agents/{id}/integrations/{kind}/bindings` maps an operation's inputs to your agent's parameters and projects the fields you want back — compiling to a skill. Walk through it in [Map an integration's fields](/guides/map-integration-fields), or see the [Integrations concept](/concepts/integrations) for how the two models compare. Two shortcuts skip the hand-mapping: * **Auto-map from a goal** — `POST /agents/{agentId}/integrations/{kind}/bindings/auto` turns a plain-language goal into a validated binding (saved as a draft to review). * **Install a preset pack** — providers that ship a **Connector Pack** install ready-made skills in one call: `POST /agents/{agentId}/integrations/{kind}/pack/install`. ## Live vs reserved The catalog only lists providers you can connect today. Reserved providers (for example Calendly, HubSpot, Square, OpenTable) are coming soon and do not appear yet. | Provider | Auth | Status | | ------------------------------------------------------------- | ----------- | ---------------------- | | [Google Calendar](/integrations/google-calendar) | OAuth | Live | | [Google Sheets](/integrations/google-sheets) | OAuth | Live | | [Shopify](/integrations/shopify) | API key | Live | | [SQL database — Postgres & MySQL](/integrations/sql-database) | Credentials | Live | | Calendly, HubSpot, Square, OpenTable | — | Reserved (coming soon) | Need something not listed? Build a [custom integration](/integrations/custom) with an `http_webhook` skill that calls your own endpoint. ## In the API | Action | Endpoint | Scope | | ------------------------------ | ------------------------------------------------------------------------------------------- | -------------------- | | Browse the provider catalog | `GET /integrations/catalog` | `integrations:read` | | List connected integrations | `GET /integrations` | `integrations:read` | | Connect a provider | `POST /integrations/{kind}/connect` | `integrations:write` | | List a provider's actions | `GET /integrations/{kind}/actions` | `integrations:read` | | Provision actions as skills | `POST /agents/{agentId}/integrations/{kind}/provision` | `skills:write` | | Discover a provider's schema | `POST /integrations/{kind}/discover` | `integrations:write` | | Read the discovered schema | `GET /integrations/{kind}/schema` | `integrations:read` | | Browse objects / operations | `GET /integrations/{kind}/objects` · `GET /integrations/{kind}/operations` | `integrations:read` | | Map a binding (open model) | `POST /agents/{agentId}/integrations/{kind}/bindings` | `skills:write` | | Auto-map a binding from a goal | `POST /agents/{agentId}/integrations/{kind}/bindings/auto` | `skills:write` | | Read / install a preset pack | `GET /integrations/{kind}/pack` · `POST /agents/{agentId}/integrations/{kind}/pack/install` | `skills:write` | | Remove provisioned skills | `DELETE /agents/{agentId}/integrations/{kind}/provision` | `skills:write` | | Disconnect a provider | `DELETE /integrations/{kind}` | `integrations:write` | `GET /integrations` and the catalog return **status only** — connected tokens are stored encrypted and never echoed back. Authenticate every call with `Authorization: Bearer flowyte_sk_…` (see [Authentication](/get-started/authentication)). Never send an organization header with an API key. ```bash theme={null} curl https://builder.flowyte.com/api/v1/integrations/catalog \ -H "Authorization: Bearer flowyte_sk_…" ``` # Shopify Source: https://docs.flowyte.com/integrations/shopify Answer "where is my order?" by looking up live order status as a skill. Connect your store, then provision an **order lookup** action as a [skill](/concepts/skills). When a caller asks where their order is (WISMO), the agent collects the order number or email, looks up the live status, and reads it back — no transfer to a human required. ## Connect with an API key Shopify connects with an API-key credential, so it completes in a single call — no browser hop. Pass the credential in the connect body; it's encrypted at rest and never echoed back. ```bash curl theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/shopify/connect \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{ "credentials": { "shop": "your-store.myshopify.com", "access_token": "shpat_…" } }' # → { "data": { "status": "connected" } } ``` ```js Node theme={null} const res = await fetch( "https://builder.flowyte.com/api/v1/integrations/shopify/connect", { method: "POST", headers: { Authorization: "Bearer flowyte_sk_…", "Content-Type": "application/json", }, body: JSON.stringify({ credentials: { shop: "your-store.myshopify.com", access_token: "shpat_…" }, }), }, ); const { data } = await res.json(); // { status: "connected" } ``` ```python Python theme={null} import requests res = requests.post( "https://builder.flowyte.com/api/v1/integrations/shopify/connect", headers={"Authorization": "Bearer flowyte_sk_…"}, json={"credentials": {"shop": "your-store.myshopify.com", "access_token": "shpat_…"}}, ) print(res.json()["data"]["status"]) # connected ``` A store-scoped admin API token (a custom app token) is the fastest path — no OAuth app to publish. The exact credential keys are listed on the connect form in the dashboard. ## Provision the lookup skill ```bash theme={null} # Browse the actions curl https://builder.flowyte.com/api/v1/integrations/shopify/actions \ -H "Authorization: Bearer flowyte_sk_…" # Provision onto an agent (omit "actions" to take every important one) curl -X POST \ https://builder.flowyte.com/api/v1/agents/AGENT_ID/integrations/shopify/provision \ -H "Authorization: Bearer flowyte_sk_…" \ -H "Content-Type: application/json" \ -d '{ "actions": ["lookup_order"] }' ``` Lookups are read-only, so the agent answers without taking any action on the store. Re-running provision is idempotent — already-created skills return `created: false`. ## In the API | Action | Endpoint | Scope | | ----------------- | --------------------------------------------------------- | -------------------- | | Connect (API key) | `POST /integrations/shopify/connect` | `integrations:write` | | List actions | `GET /integrations/shopify/actions` | `integrations:read` | | Provision skills | `POST /agents/{agentId}/integrations/shopify/provision` | `skills:write` | | Remove skills | `DELETE /agents/{agentId}/integrations/shopify/provision` | `skills:write` | | Disconnect | `DELETE /integrations/shopify` | `integrations:write` | Provisioning edits the **draft**. [Publish](/concepts/agents) so live callers get real order status. # SQL database (Postgres & MySQL) Source: https://docs.flowyte.com/integrations/sql-database Connect your own Postgres or MySQL database and let the agent read (and optionally write) live records on a call — you choose the exact columns. Connect your own **PostgreSQL** or **MySQL** database and the agent can look up and update live records mid-call — check an appointment, confirm a balance, capture a request. Unlike the curated providers, a database has no pre-built actions: you **discover** its schema and **map** the exact queries you want as [skills](/concepts/skills). Nothing runs that you didn't author and approve. Authenticate every request with a secret API key: `Authorization: Bearer flowyte_sk_…`. The `kind` is `postgres` or `mysql`. ## How it fits together Generate the SQL to create a scoped user, then run it on your database — the agent never uses your admin credentials. Validate the credentials, then connect with them. They're encrypted at rest and never echoed back. Introspect the database, then optionally block tables/columns the agent must never see. Map a read (or a scoped write) onto a skill — a frozen, parameterized statement the agent calls. ## 1. Generate a scoped database user `GET /integrations/{kind}/sql/scripts` returns copy-paste SQL to create a **least-privilege** database user: a read-only role (SELECT only) and, if you need writes, a script scoped to **only** the tables your write skills touch — never blanket write. If you don't pass a password, a strong one is generated and returned once. ```bash theme={null} curl "https://builder.flowyte.com/api/v1/integrations/postgres/sql/scripts?database=appdb&tables=appointments" \ -H "Authorization: Bearer flowyte_sk_…" # → { "data": { "readOnly": { … }, "write": { … }, "generatedPassword": "…" } } ``` Run the returned scripts on your database as an admin, then use the scoped user's credentials below. ## 2. Test the connection `POST /integrations/{kind}/sql/test` runs an ordered set of checks — reachability and TLS, authentication, reading the schema, a timed read probe, a read-only-session proof, and a live-call latency verdict — **without storing anything**. It always returns `200` with the check results and an overall `ok` flag; a host that resolves to a private, loopback, or metadata address is refused. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/sql/test \ -H "Authorization: Bearer flowyte_sk_…" -H "Content-Type: application/json" \ -d '{ "credentials": { "host": "db.example.com", "port": "5432", "database": "appdb", "user": "flowyte_ro", "password": "…", "sslmode": "verify-full" } }' # → { "data": { "ok": true, "checks": [ … ] } } ``` `sslmode` must be `require`, `verify-ca`, or `verify-full` — `disable` is refused at the connect boundary. Prefer `verify-full` and supply `ca_pem` when your database uses a private CA. ## 3. Connect Pass the same credentials to the generic connect endpoint. The `kind` is `postgres` or `mysql`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/connect \ -H "Authorization: Bearer flowyte_sk_…" -H "Content-Type: application/json" \ -d '{ "credentials": { "host": "db.example.com", "port": "5432", "database": "appdb", "user": "flowyte_ro", "password": "…", "sslmode": "verify-full" } }' # → { "data": { "status": "connected" } } ``` ## 4. Discover the schema and scope it Introspect the database into a normalized schema, then read it (or search it) to find what to map: ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/integrations/postgres/discover \ -H "Authorization: Bearer flowyte_sk_…" curl "https://builder.flowyte.com/api/v1/integrations/postgres/search?q=appointment%20status" \ -H "Authorization: Bearer flowyte_sk_…" ``` Set **data scoping** to keep sensitive tables and columns out of reach — they disappear from the schema browser and are refused at bind time. Scoping is a privacy control you set per connection and it survives re-discovery. ```bash theme={null} curl -X PATCH https://builder.flowyte.com/api/v1/integrations/postgres/scoping \ -H "Authorization: Bearer flowyte_sk_…" -H "Content-Type: application/json" \ -d '{ "blockedTables": ["payments"], "blockedColumns": { "customers": ["ssn", "card_last4"] } }' ``` ## 5. Bind a query as a skill Map an operation onto a skill with [a binding](/guides/map-integration-fields): a **read** filters on one indexed column; a **write** is a single-row insert or an update-by-unique-key. The binding compiles to a frozen, parameterized statement — the agent supplies the parameters and never sees raw SQL or the full schema. Writes land disabled for review, and columns you scoped out are rejected. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/AGENT_ID/integrations/postgres/bindings \ -H "Authorization: Bearer flowyte_sk_…" -H "Content-Type: application/json" \ -d '{ "toolName": "Look up an appointment", "operation": "appointments.read", "inputs": [ { "param": "caller_phone", "arg": "phone", "required": true } ], "projection": [ { "path": ["appointment", "starts_at"], "leaf": "appointment_time" }, { "path": ["appointment", "status"], "leaf": "appointment_status" } ], "enabled": true }' ``` Prefer describing the goal instead? `POST /agents/{id}/integrations/postgres/bindings/auto` takes a plain-language goal and the AI assistant proposes the binding for you to review. ## In the API | Action | Endpoint | Scope | | ------------------------ | --------------------------------------------------------------------- | ------------------------------------------ | | Generate setup scripts | `GET /integrations/{kind}/sql/scripts` | `integrations:read` | | Test a connection | `POST /integrations/{kind}/sql/test` | `integrations:write` | | Connect | `POST /integrations/{kind}/connect` | `integrations:write` | | Discover the schema | `POST /integrations/{kind}/discover` | `integrations:write` | | Search / read the schema | `GET /integrations/{kind}/search` · `GET /integrations/{kind}/schema` | `integrations:read` | | Get / set data scoping | `GET` · `PATCH /integrations/{kind}/scoping` | `integrations:read` · `integrations:write` | | Bind a query as a skill | `POST /agents/{agentId}/integrations/{kind}/bindings` | `skills:write` | | Disconnect | `DELETE /integrations/{kind}` | `integrations:write` | Binding edits the **draft**. [Publish](/concepts/agents) so live callers get real data. Full walkthrough: [Connect a SQL database](/guides/connect-sql-database). # Quickstart Source: https://docs.flowyte.com/quickstart Mint an API key and build a working agent in about five minutes. This walks you from zero to a published agent you can talk to. Every step is a single API call against the base URL `https://builder.flowyte.com/api/v1`. All requests authenticate with a secret API key: `Authorization: Bearer flowyte_sk_…`. See [Authentication](/get-started/authentication) for how to mint one. ## 1. Create an agent ```bash curl theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Support" }' ``` ```ts Node theme={null} const res = await fetch("https://builder.flowyte.com/api/v1/agents", { method: "POST", headers: { Authorization: `Bearer ${process.env.FLOWYTE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Acme Support" }), }); const { data: agent } = await res.json(); ``` ```python Python theme={null} import os, requests r = requests.post( "https://builder.flowyte.com/api/v1/agents", headers={"Authorization": f"Bearer {os.environ['FLOWYTE_API_KEY']}"}, json={"name": "Acme Support"}, ) agent = r.json()["data"] ``` The response is the `ApiResponse` envelope. Capture `data.id` — that's your `agentId`. ## 2. Give it knowledge Add a source the agent can answer from. Knowledge ingestion is **asynchronous** — poll the source until its `status` is `indexed`. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge/sources \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "kind": "url", "label": "Help center", "url": "https://acme.com/help" }' # poll until indexed curl https://builder.flowyte.com/api/v1/agents/$AGENT_ID/knowledge/sources/$SOURCE_ID \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` ## 3. Publish The tester runs your **draft**; phone and chat run the last **published** version. Publish to freeze a version your channels can serve. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publish \ -H "Authorization: Bearer $FLOWYTE_API_KEY" ``` If you connect a channel before publishing, you'll get `409 no_published_version`. Publish first. ## 4. Test it Simulate a conversation. This streams over Server-Sent Events — read it with `fetch()` streaming and stop on the `event: done` frame. ```bash theme={null} curl -N -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/simulate \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "message": "Do you offer same-day appointments?", "draftMode": false }' ``` ## 5. Go live Put the agent on an embeddable chat widget by minting a publishable key (browser-safe, origin-allowlisted), or assign it a phone number from the Numbers API. ```bash theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/publishable-keys \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Website", "allowedOrigins": ["https://acme.com"] }' ``` Drop the returned loader snippet on your site and the agent is live. Learn how agents, skills, knowledge, guardrails, and playbooks fit together. # Errors Source: https://docs.flowyte.com/resources/errors The problem+json error shape, common status codes, and named problems you'll hit. When a request fails, Flowyte returns an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) **problem+json** body with the `application/problem+json` content type. The HTTP status is the source of truth; the body adds machine-readable detail. ## The problem shape ```json theme={null} { "type": "about:blank", "title": "Validation failure", "status": 400, "detail": "name is required", "instance": "/api/v1/agents", "code": "validation_error", "errors": [ { "field": "name", "message": "is required" } ] } ``` | Field | Meaning | | ---------- | ------------------------------------------------------------------ | | `status` | HTTP status code (also on the response). | | `title` | Short, human-readable summary of the problem type. | | `detail` | Explanation specific to this occurrence. | | `code` | Stable machine string for branching (e.g. `no_published_version`). | | `errors[]` | Field-level validation errors (`field`, `message`), when relevant. | Branch on `status` and `code`, not on `detail` or `title` — those wording may change. ## Common statuses | Status | When | | ----------------------- | --------------------------------------------------------------------------------------------- | | `400 Bad Request` | Malformed or invalid request body; `errors[]` lists the offending fields. | | `401 Unauthorized` | Missing or invalid key/session. Check `Authorization: Bearer flowyte_sk_…`. | | `403 Forbidden` | Org mismatch, insufficient scope, disallowed origin — or a **reserved** endpoint (see below). | | `404 Not Found` | The resource doesn't exist **in your org's scope**. | | `409 Conflict` | Duplicate name, a publish race, or a stale `If-Match` version. | | `422 Unprocessable` | Semantically invalid — e.g. a language your plan's speech engine doesn't support. | | `429 Too Many Requests` | Rate-limited. Honor `Retry-After` and the `RateLimit-*` headers. | ## Reserved endpoints return 403 Some endpoints in the contract are **reserved** (planned ahead of release). Calling one returns **`403`, not `404`** — the route exists but isn't open to you yet. If a correctly-authenticated, correctly-scoped call returns `403`, check whether the endpoint is reserved before assuming a permissions bug. The same `403` also covers genuine scope and cross-org denials. ## Named problems A few problems carry a specific `code` worth handling: | `code` | Status | Meaning | | --------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `no_published_version` | `409` | You requested a published-mode session (e.g. a non-draft talk token) but the agent has never been published. Publish it, or use `draftMode: true`. | | `integration_unavailable` | `400` | The provider's OAuth app isn't configured for this deployment yet. | | `integration_not_connected` | `409` | A config helper needs a connected provider (e.g. resolving a Sheet before Sheets is connected). | | `sheet_not_accessible` | `404` | The spreadsheet link is wrong or hasn't been shared with the connected account. | | `file_expired` | `404` | An uploaded `fileId` was garbage-collected before it was attached. Re-upload. | | `file_too_large` | `413` | The upload exceeds the 25 MB limit. | ## Optimistic concurrency (409 on `PATCH /agents/{id}`) Agent autosave uses an `If-Match` version. If you send a stale version, the server responds `409` **with the current agent state and version** in the body. Rebase: refetch, re-apply your pending edits, and retry with the new version. For status codes per endpoint, see the [API Reference](/api-reference/introduction). # Feature matrix Source: https://docs.flowyte.com/resources/feature-matrix Every platform capability — what it does, where it lives in the dashboard, its endpoints, and the scope it needs. One row per capability. Use it to map a thing you want to do to the dashboard area that configures it, the API endpoints behind it, and the [scope](/get-started/authentication) a secret key needs to call them. All paths are relative to `https://builder.flowyte.com/api/v1`. | Capability | What it does | Dashboard | Endpoints | Scope | | ------------------------ | ------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- | | **Agents** | The single entity callers reach; everything else attaches to it. Create, edit (draft), publish, roll back. | Agents | `GET/POST /agents` · `PATCH /agents/{id}` · `POST /agents/{id}/publish` · `POST /agents/{id}/rollback` | `agents:read` / `agents:write` | | **Voice** | Assign a catalog voice per language and browse/search voices. | Agent → Voice | `GET /voices` · `POST /voices/search` · `PUT /agents/{id}/voice` | `agents:read` / `agents:write` | | **Knowledge** | Add sources (URL, file, text) the agent answers from; ingestion is async. | Agent → Knowledge | `GET/POST /agents/{agentId}/knowledge/sources` · `POST …/knowledge/preview` | `knowledge:read` / `knowledge:write` | | **Skills** | Tools the agent calls mid-conversation (transfer, email, lookups, geo). | Agent → Skills | `GET /skill-types` · `GET/POST /agents/{agentId}/skills` · `PATCH/DELETE …/skills/{id}` | `skills:read` / `skills:write` | | **Integrations** | Connect a provider once, then provision its actions as skills. Live: Google Calendar, Google Sheets, Shopify. | Integrations | `GET /integrations` · `GET /integrations/catalog` · `POST /integrations/{kind}/connect` · `POST /agents/{agentId}/integrations/{kind}/provision` | `integrations:read` / `integrations:write` (+ `skills:write` to provision) | | **Guardrails** | Deterministic policies and caller-identity verification. | Agent → Guardrails | `GET/PUT /agents/{agentId}/guardrails` · `GET/PUT …/caller-verification` | `guardrails:read` / `guardrails:write` | | **Playbooks** | Optional node graph for scripted, step-by-step flows. | Agent → Playbooks | `GET/POST /agents/{agentId}/playbooks` · `GET/PUT …/playbooks/{id}/graph` | `playbooks:read` / `playbooks:write` | | **Numbers** | Search, reserve, purchase, import, and assign phone numbers. | Numbers | `GET /numbers/search` · `POST /numbers/reserve` · `POST /numbers/purchase` · `POST /numbers/{id}/assign` | `numbers:read` / `numbers:write` | | **Voice channel (PSTN)** | Inbound calls reach an assigned number; mint a talk token for the in-browser tester. | Agent → Test | `POST /agents/{agentId}/talk-token` · `POST /agents/{agentId}/simulate` | `agents:write` (talk token is session-only) | | **Chat channel** | Server-side chat sessions plus an OpenAI-compatible completions endpoint. | Agent → Test / Channels | `POST /chat/sessions` · `POST /chat/sessions/{id}/messages` · `POST /chat/completions` | `chat:read` / `chat:write` | | **Widget** | Embeddable browser chat, authenticated by a publishable key. | Agent → Channels | `GET/PUT /agents/{agentId}/widget` · `POST /chat/widget/sessions` | `widgets:read` / `widgets:write` · `pubkeys:*` | | **SMS (10DLC)** | Text messaging — available after per-org brand & campaign registration. | Numbers / Channels | *registration-gated* | `sms:read` / `sms:write` | | **Observe** | Post-call analytics, conversation history, transcripts, receipts, topics, knowledge gaps. | Observe | `GET /agents/{agentId}/analytics` · `GET /conversations` · `GET /conversations/{id}/receipt` · `GET /analytics/overview` | `analytics:read` · `calls:read` | | **Billing** | Wallet, top-ups, plans, usage, and invoices via Stripe. | Billing | `GET /billing/wallet` · `POST /billing/wallet/topup` · `GET /billing/usage` · `GET /billing/invoices` | `billing:read` / `billing:write` | | **API keys** | Authenticate the API. Keys are minted and managed in the dashboard Developer page. | Developer | — | — | | **Webhooks** | Subscribe to events with signed deliveries; send test events. | Developer | `GET/POST /webhooks` · `POST /webhooks/{id}/test` · `GET /webhooks/{id}/deliveries` | `webhooks:write` | | **Audit logs** | API and key activity history. | Developer | `GET /audit-logs` | `audit:read` | **DTMF** keypad input is supported on voice calls for capturing digits (menus, account numbers). Configure it on the agent — there's no separate endpoint. Changes always edit a **draft**. Phone and chat serve the last **published** version, so publish the agent after wiring up any capability above. See [draft vs published](/get-started/draft-vs-published). For the authoritative, machine-readable list, see the [API Reference](/api-reference/introduction). # Glossary Source: https://docs.flowyte.com/resources/glossary The core Flowyte terms, defined once. The vocabulary used across these docs and the API. Each term links to where it's covered in depth. ### Agent The single user-facing entity. An agent answers calls and chats; its persona, voice, [knowledge](/concepts/knowledge), [skills](/concepts/skills), [guardrails](/concepts/guardrails), and [playbooks](/concepts/playbooks) all attach to it. You build, publish, and observe one agent at a time. See [Agents](/concepts/agents). ### Skill A tool the agent can call mid-conversation to *do* something — transfer a call, send an email, book an appointment, look up an order, check a service area. A skill has a type and a configuration. See [Skills](/concepts/skills). ### Knowledge source A URL, file, or block of text the agent answers from. Ingestion is **asynchronous**: a new source starts in a pending state and becomes answerable once its status reaches `indexed`. Poll the source until then. See [Knowledge](/concepts/knowledge). ### Guardrail A deterministic policy that constrains what the agent does — what it must not say, when to escalate, and identity checks (caller verification) that gate sensitive answers. Guardrails run as rules, not suggestions. See [Guardrails](/concepts/guardrails). ### Playbook An optional node graph for scripted, step-by-step flows (collect, branch, end) when you need the conversation to follow an exact procedure rather than free-form reasoning. See [Playbooks](/concepts/playbooks). ### Draft / published Editing an agent changes its **draft**. Phone and chat always serve the last **published** version. Publishing freezes a version of the compiled config; **rollback** repoints to an earlier one. This is why a change can work in the tester but not on a live call until you publish. See [draft vs published](/get-started/draft-vs-published). ### Scope A permission string carried by a secret API key that gates which operations it can call (e.g. `agents:write`, `knowledge:write`, `analytics:read`). Request only the scopes you need. See [Authentication](/get-started/authentication). ### Publishable key A browser-safe key (`flowyte_pk_…`) used by the embeddable chat widget. It is **agent-pinned**, **origin-allowlisted**, and limited to public chat (`chat:public`) — safe to ship in client-side code, and it can never read tenant data. ### Receipt The ordered audit trail of a single conversation — every event the agent took (answers, skill calls, transfers), in sequence. Receipts back the transcript and let you see exactly what happened on a call. Retrieved via `GET /conversations/{id}/receipt`. ### Containment An analytics measure: the share of conversations the agent fully handled **without** transferring to a human. Higher containment means more calls resolved by the agent. It appears in [Observe](/resources/feature-matrix) alongside answer and transfer rates. Looking for an error term or status code? See [Errors](/resources/errors). For a capability → endpoint → scope map, see the [Feature matrix](/resources/feature-matrix). # After-hours & overflow answering Source: https://docs.flowyte.com/solutions/after-hours Catch the calls you'd otherwise miss — route the urgent ones, capture the rest. The calls you lose aren't during business hours — they're at 9pm, on a Sunday, or all at once when every line is busy. A Flowyte **agent** picks those up so an unanswered ring never becomes a lost customer. It knows when you're open, what to do while you're closed, and which calls truly can't wait. ## The two gaps it closes When you're closed, the agent still answers — books what it can, takes a message for the rest, and tells the caller when you reopen. When your team is on other calls, it answers in parallel instead of sending callers to voicemail. No busy signal, no queue. ## It knows when you're open Each agent carries your **business hours** — a timezone, a weekly open/closed grid, and one-off holidays. The agent is live-aware of whether you're open or closed and changes its behavior accordingly, so a closed Sunday and a busy Tuesday afternoon are handled differently and correctly. ## What happens while you're closed By default the agent keeps full functionality after hours — it can still answer questions, book, and capture requests. You decide what changes: * **Take a message** — capture the caller's details and email them to your team for the morning. * **Route urgent calls** — let self-declared emergencies (no heat, a leak, an outage) go to an on-call number, even when ordinary transfers are off. * **Hand off to a person** — turn on after-hours transfer to a specific number when someone is genuinely on call. Most businesses have no one to take a 2am call, so after-hours human transfer is **off by default** — the agent helps directly and leaves a clean message instead of ringing an empty desk. Turn it on only when an on-call person is real. ## In the API Business hours and after-hours behavior live on the agent. You set them with a normal agent update, then publish. | Action | Endpoint | Scope | | ----------------------------------------- | --------------------------- | -------------- | | Set business hours & after-hours behavior | `PATCH /agents/{id}` | `agents:write` | | Add a take-a-message skill | `POST /agents/{id}/skills` | `skills:write` | | Publish the change | `POST /agents/{id}/publish` | `agents:write` | | Review what you caught | `GET /agents/{id}/calls` | `calls:read` | Editing changes the **draft**; phone and chat serve the last **published** version. Publish after you adjust hours, or the live agent keeps the old schedule. See also: [warm transfer to your team](/solutions/warm-transfer) and the [AI answering service](/solutions/answering-service). # AI answering service for HVAC & field service Source: https://docs.flowyte.com/solutions/answering-service Answer every call 24/7, recognize customers, capture new jobs into your field-service software, and hand the urgent ones to a person. Missed calls are missed jobs. For HVAC, plumbing, and trades, the phone rings during a service call, after hours, and all at once on the first cold morning of the season. A Flowyte **agent** answers every one — in a natural voice, around the clock — and works straight out of your **field-service software** instead of just taking a message. ## What it does on a call Picks up on the first ring with your greeting, in the caller's language. No hold music, no voicemail. Looks them up by phone, greets them by name, and can confirm the property on file. Collects the details and opens a request in your field-service software, tied to the customer — your office turns it into a scheduled job. Checks the customer's jobs and reads back their next scheduled visit. ## Backed by your field-service software Connect your field-service account once, then provision its actions as **skills** on the agent. Each becomes a tool the agent uses mid-call: | Skill | What the agent does | | ---------------- | ----------------------------------------------------- | | `find_client` | Recognize a returning customer and pull up their jobs | | `create_lead` | Capture a new service request in your system | | `get_job_status` | Tell a caller when their technician is coming | For a burst pipe or a no-heat call, a **transfer** skill routes straight to your on-call person. Add your hours, pricing, and FAQs as [knowledge](/concepts/knowledge) so it answers from your real policies. Want to gate by location? A free [geo](/concepts/skills) skill checks whether the caller's address is in your service area. ## Build it `POST /integrations/{kind}/connect` returns an authorization link; approve it once. Find your provider's `kind` in `GET /integrations/catalog`. `POST /agents/{id}/integrations/{kind}/provision` turns the connected account's actions into skills on the agent. Feed it your service area, pricing, and FAQs, and add a transfer for emergencies. Publish a version, test it with the simulator, then point a phone number at it. Full walkthrough: [Build an HVAC answering agent](/guides/build-hvac-agent). See also: [after-hours & overflow](/solutions/after-hours) and [warm transfer to your team](/solutions/warm-transfer). # Order status & WISMO Source: https://docs.flowyte.com/solutions/order-status Resolve "where is my order?" on voice and chat with a connected Shopify store. "Where is my order?" is the question that floods support after every shipment. A Flowyte **agent** answers it for you — on the phone, in chat, and in the website widget — by looking the order up in your **Shopify** store in real time and reading back the status. No ticket, no wait, no human required for the routine 80%. ## What the customer gets The agent looks up the actual order and tells the customer its status — not a canned "check your email." The same lookup works on a phone call, a chat session, and the embeddable widget on your site. Pair it with caller verification so it only reveals order details to the right person. A damaged or lost package can hand off to a person, with the order context already gathered. ## How it's built Connect **Shopify** once with a store credential. Tokens are stored encrypted — never echoed back. Turn the store's order-lookup action into a [skill](/concepts/skills) on your agent. The agent now collects an order number or email and fetches the status itself. Serve it on the phone, on the [chat](/concepts/agents) channel, or drop the widget on your storefront. An OpenAI-compatible completions endpoint is also available for your own app. Publish the agent so every channel serves the new capability. ## In the API | Action | Endpoint | Scope | | ------------------------------ | ------------------------------------------------- | -------------------- | | Connect the store | `POST /integrations/{kind}/connect` | `integrations:write` | | Provision lookup as a skill | `POST /agents/{id}/integrations/{kind}/provision` | `skills:write` | | Publish the agent | `POST /agents/{id}/publish` | `agents:write` | | Answer over chat (server-side) | `POST /chat/sessions/{id}/messages` | `chat:write` | | Drop-in for your own app | `POST /chat/completions` | `chat:write` | Order lookup runs against your live store, so a customer always hears the current status. Add [guardrails](/concepts/guardrails) to verify identity before disclosing order details. See also: the [AI answering service](/solutions/answering-service) and [warm transfer to your team](/solutions/warm-transfer). # Take phone orders with a delivery-area check Source: https://docs.flowyte.com/solutions/restaurant Answer menu questions, take the order, confirm it's in your delivery zone, and send it to the kitchen — by phone. A busy pizzeria can't pick up every call at the dinner rush — and every missed call is an order that went to the place down the street. A Flowyte **agent** answers, knows your menu, takes the order, and only promises delivery when the address is actually in your zone. ## What it does on a call Answers "what's on the supreme?" and "how much is a large?" from your real menu — no guessing. Before promising delivery, it checks the caller's address against your zone and offers pickup if it's out of range. Collects items, size, and any notes, reads the total back, and confirms. Posts the finished order to your kitchen display or order system the moment it's confirmed. ## How it's built Three pieces on one agent: Add your menu (a URL, a PDF, or pasted text) as [knowledge](/concepts/knowledge). The agent answers menu and price questions from it. Add a free [geo](/concepts/skills) skill with the `check_service_area` action — your shop address as the origin and your delivery radius. The agent asks for the address and confirms coverage before taking a delivery order. Add an `http_webhook` skill that posts the confirmed order to your kitchen display or POS (or an email skill if you'd rather it land in an inbox). ```bash Delivery-area skill theme={null} curl -X POST https://builder.flowyte.com/api/v1/agents/$AGENT_ID/skills \ -H "Authorization: Bearer $FLOWYTE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Check delivery area", "description": "Check whether the caller's delivery address is within our delivery zone before taking a delivery order.", "skillType": "geo", "executionConfig": { "action": "check_service_area", "originAddress": "120 Main St, Boulder, CO", "radiusMiles": 5 } }' ``` Geometry is free — you're only charged when the agent has to look up a caller's address. See the full build in [Take an order with a delivery check](/guides/take-orders). # Warm transfer to your team Source: https://docs.flowyte.com/solutions/warm-transfer Let the agent handle routine calls and hand off cleanly to a person when it should. Not every call should reach a human — but some absolutely should. A Flowyte **agent** handles the routine volume (hours, pricing, booking, status) and recognizes the moment a caller needs a real person. When it does, it doesn't dump the call: it has already gathered who's calling and why, then routes them to the right number with their original caller ID intact. ## Why it's "warm" The agent does the qualifying work before the handoff, so your team doesn't start cold: The agent collects the caller's name, reason, and any details it needs, so the person who picks up isn't starting from zero. A sales question, a billing issue, and an emergency each go to a different number — based on what the caller actually said. Your team sees the customer's real number on the transferred call, not the platform's. Routine calls stay contained. The agent transfers when its rules say to — not by default. ## How routing decisions are made You set up two layers: * A **default handoff destination** — where a transfer goes when nothing more specific matches. * **Transfer-by-context rules** — a small table that maps a described situation ("caller wants to speak to billing", "caller reports an emergency") to a specific phone number. The agent matches the live conversation to a rule and transfers there. This is a **skill** the agent calls mid-conversation. Because routing is data, the same pattern scales from one front desk to thousands of locations — look up the right destination, then transfer. ## In the API | Action | Endpoint | Scope | | --------------------------------------- | --------------------------- | -------------- | | Set default destination & context rules | `PATCH /agents/{id}` | `agents:write` | | Add a transfer skill | `POST /agents/{id}/skills` | `skills:write` | | Publish | `POST /agents/{id}/publish` | `agents:write` | | See your transfer rate | `GET /agents/{id}/calls` | `calls:read` | Pair this with [guardrails](/concepts/guardrails) so the agent verifies a caller before it ever transfers sensitive requests, and with [skills](/concepts/skills) it can try first so only the calls that truly need a person reach one. See also: [after-hours & overflow answering](/solutions/after-hours) and the [AI answering service](/solutions/answering-service). # Trust & Security Source: https://docs.flowyte.com/trust/security How Flowyte handles your data, recordings, and caller PII — and how to report a vulnerability. Flowyte runs production voice and chat agents, which means we handle call audio, transcripts, and whatever a caller says out loud. This page describes how that data is protected, what your compliance obligations are, and how to reach us. It states our posture honestly — where something is in progress, we say so. ## Tenant isolation Every record belongs to one organization. Each request — whether it carries a dashboard session or a secret API key — resolves to a single tenant-scoped identity, and database row-level security enforces that boundary on every query. **A key can never read another organization's data**, even if it asks for a resource by id. ## Call recording & consent Voice calls can be recorded and transcribed so you can review them in **Observe**. Recordings are retrieved through short-lived signed URLs, not public links. You are the controller of your callers' data and are responsible for disclosing recording where the law requires it. Many jurisdictions require **two-party (all-party) consent** before a call is recorded. Configure your agent's greeting (or an opening notice) to announce recording when you operate in those regions, and confirm your obligations with counsel. You decide what the agent says, including any recording notice, and you can disable retention of recordings on request. ## Data handling & PII * **In transit:** all API traffic is served over TLS. * **At rest:** sensitive secrets — such as integration access tokens — are encrypted with authenticated encryption (AES-256-GCM) and are never returned by the API. Token DTOs surface status only. * **Minimize what you collect.** Only capture the caller fields a skill actually needs. * **Verify before disclosing.** Use a [caller-verification guardrail](/concepts/guardrails) to gate sensitive answers behind identity checks. * **Secrets show once.** API-key and webhook-signing secrets are returned a single time at creation. Store them in your secret manager. ## Data residency & retention Recordings, transcripts, and the per-call audit trail are retained so analytics and receipts work. Retention windows and regional hosting options depend on your plan — **contact us** to configure a specific residency or retention requirement, or to request deletion of stored conversation data. ## Sub-processors Flowyte relies on third-party infrastructure to deliver the service. We describe them by **category** rather than by name; the current named list is available under NDA on request. | Category | Purpose | | ------------------------------ | ------------------------------------------------------- | | Cloud hosting & infrastructure | Compute, storage, and managed databases | | Telephony carrier | PSTN connectivity, phone numbers, call routing | | Speech & language models | Real-time voice synthesis, transcription, and reasoning | | Payment processing (Stripe) | Checkout, wallet top-ups, invoices | | Email & notification delivery | Outbound notification skills | | Product analytics | Usage metrics and session replay in the dashboard | We review sub-processors before onboarding them and maintain a Data Processing Addendum (DPA), available on request. ## Compliance posture We aim to be precise here rather than aspirational: * **SOC 2:** an audit is **in progress**. We do not yet hold a completed report — contact us for current status and to be notified when it is available. * **GDPR / CCPA:** we support data-subject and deletion requests and provide a DPA on request. * **A2P 10DLC:** SMS sending requires per-organization brand and campaign registration before messages are delivered (a regulatory requirement, not a Flowyte limitation). If a compliance questionnaire or security review is part of your procurement, reach out and we'll work through it with you. ## Reporting a vulnerability If you believe you've found a security issue, email **[security@flowyte.com](mailto:security@flowyte.com)** with steps to reproduce. Please do not publicly disclose the issue until we've had a chance to investigate and respond. We welcome good-faith research and will not pursue action against researchers who act responsibly and avoid privacy violations or service disruption.