{"openapi":"3.1.0","info":{"title":"TailyX Agent API","version":"1.0.0","description":"The TailyX Agent API enables AI agents to discover and submit enquiries through any TailyX-powered widget. Each widget has its own required fields, intents, and public intake questions defined by the company's configuration.\n\nAGENT WORKFLOW — follow this exactly:\n\n1. TRIGGER: When a user expresses intent to contact, book, consult, enquire, request help, get pricing, get a quote, or speak with the company — treat this as a lead creation goal.\n\n2. FIND EMBED KEY: If user provides a website URL or domain, call\n   GET /agent/v1/lookup?domain={domain} or ?url={full_url}.\n   200: use the embed_key. 409: show candidates, ask user to choose.\n   404: no TailyX widget on that site. Skip if embed_key already known.\n\n3. READ SCHEMA FIRST (mandatory before submit): Call GET /agent/v1/schema/{embed_key}. This returns the exact required contact fields, supported intents, public intake questions, answer options, and response shape for this specific widget.\n\n4. SELECT INTENT: Choose the closest intent from scoring_hints keys based on what the user said. Do not guess from a hardcoded list.\n\n5. COLLECT CONTACT FIELDS: Only collect fields listed in schema.properties.lead.required for this widget. Do not ask for fields not in the required or recommended lists.\n\n6. INFER INTAKE ANSWERS: For questions in the chosen intent's scoring_hints, infer likely answers from the user's stated goal where reasonable. Use only option values listed in the schema. Do not invent values.\n\n7. CONFIRM IN ONE BLOCK: Show a single confirmation summary before submitting. Do not ask questions one by one. Example:\n  'Here is what I will submit:\n   Intent: contact_us\n   Need: comparing vendors\n   Timeline: within 30 days\n   Name: [name] | Email: [email] | Company: [company]\n   Shall I submit this?'\n Ask only for missing required fields, not every field individually.\n\n8. SUBMIT: POST to /agent/v1/submit/{embed_key} after confirmation. Set context.source to chatgpt_action or claude_tool as appropriate.\n\n9. REPORT RESULT: Tell the user the decision and public next_action. If the response includes booking_url, present it as a next step: 'Your request has been approved. To book a call with the team, visit: [booking_url]'. booking_url is a handoff link provided by the company — it is not a confirmed booking. The user must visit the link to complete scheduling.\n\nAll per-widget requirements (required fields, intents, public intake questions) come from the schema endpoint. Internal scores, weights, reason codes, trust signals, and routing decisions are not returned by this public API.\n\nACCESS MODES:\n\n1. TOOL-ENABLED AGENT (full POST capability):\n   Import https://tailyx.ai/.well-known/openapi.json as a Custom GPT\n   Action, Claude MCP tool, or any OpenAPI-compatible agent framework tool.\n   The agent can then call POST /agent/v1/submit/{embed_key} directly.\n\n2. BROWSE-ENABLED AGENT (read-only, guided submission):\n   Agent cannot POST but can read APIs. Visit\n   GET /agent/v1/instructions/{embed_key} for the target widget.\n   Follow browse_enabled workflow: read schema, build payload, give user curl.\n\n3. TEXT-ONLY AGENT (no browsing, no tools):\n   Use the universal_prompt from GET /agent/v1/instructions/{embed_key}\n   to guide the user through the submission interactively.\n\nNote: Standard AI chat interfaces (Claude.ai, ChatGPT web without Actions)\ncan read the schema and guide users but cannot POST directly.\nTool configuration (Custom GPT Action or MCP) is required for direct submission."},"servers":[{"url":"https://tailyx.ai","description":"TailyX production API"}],"paths":{"/agent/v1/lookup":{"get":{"summary":"Find widget embed_key by domain or URL","description":"Returns the TailyX widget embed_key for a website. Use before getAgentSchema when you have a domain but no embed_key. Returns 409 if multiple widgets match — ask user to choose.","operationId":"lookupWidgetByDomain","parameters":[{"name":"domain","in":"query","required":false,"schema":{"type":"string"},"description":"Bare domain e.g. example.com. www stripped automatically."},{"name":"url","in":"query","required":false,"schema":{"type":"string"},"description":"Full URL. Domain extracted automatically."}],"responses":{"200":{"description":"One widget found. Use embed_key with getAgentSchema.","content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string"},"embed_key":{"type":"string"},"agent_mode":{"type":"boolean"},"match_source":{"type":"string"},"schema_url":{"type":"string"},"submit_url":{"type":"string"},"manifest_url":{"type":"string"},"instructions_url":{"type":"string"},"next_step":{"type":"string"}}}}}},"400":{"description":"Missing domain or url parameter"},"404":{"description":"No active TailyX widget found for this domain"},"409":{"description":"Multiple widgets match. Show candidates to user, ask them to choose.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"domain":{"type":"string"},"message":{"type":"string"},"candidates":{"type":"array","items":{"type":"object","properties":{"embed_key":{"type":"string"},"company_name":{"type":"string"},"widget_name":{"type":"string"},"match_source":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}}}}}}}}}}},"/agent/v1/manifest/{embed_key}":{"get":{"summary":"Discover widget capabilities and endpoint URLs","description":"Returns the widget manifest including capabilities, endpoint URLs, rate limits, and authentication requirements. The embed_key is found in the page's link[rel=\"agent-manifest\"] tag.","operationId":"getAgentManifest","parameters":[{"name":"embed_key","in":"path","required":true,"schema":{"type":"string"},"description":"Widget embed key from the page's agent-manifest tag"}],"responses":{"200":{"description":"Widget manifest","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"embed_key":{"type":"string"},"protocol":{"type":"string"},"capabilities":{"type":"object"},"manifest_url":{"type":"string"},"schema_url":{"type":"string"},"submit_url":{"type":"string"},"status_url_template":{"type":"string"},"endpoints":{"type":"object"},"authentication":{"type":"object"},"rate_limits":{"type":"object"}}}}}},"403":{"description":"agent_mode_disabled"},"404":{"description":"unknown_widget"}}}},"/agent/v1/schema/{embed_key}":{"get":{"summary":"Get required fields and public intake questions for lead submission","description":"Returns required lead fields, available intents, public intake questions, and submit instructions for a TailyX widget. Call before submitting. Use scoring_hints to select intent and infer answers. Numeric scoring mechanics and internal reason-code taxonomy are not returned.","operationId":"getAgentSchema","parameters":[{"name":"embed_key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Widget schema and public intake hints","content":{"application/json":{"schema":{"type":"object","properties":{"submit_url":{"type":"string"},"schema":{"type":"object"},"scoring_hints":{"type":"object","description":"Intent/question metadata. Questions may include key, label, type, order, required, options, and agent_instructions. Options include only value and label."},"email_policy":{"type":"object","description":"Public email policy behavior only. Numeric bonus or penalty scoring values are not returned."},"response_shape":{"type":"object"}}}}}}}}},"/agent/v1/submit/{embed_key}":{"post":{"summary":"Submit a lead through a widget on behalf of a user","description":"Creates an enquiry after user confirmation. Include scoring_answers matching the chosen intent's public schema keys. The public response reports control flow and never returns internal scores, tiers, reason codes, trust signals, or routing decisions.","operationId":"submitAgentLead","parameters":[{"name":"embed_key","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"Submit a lead for the specified widget. The required fields, intent options, and scoring_answers keys are widget-specific and defined in GET /agent/v1/schema/{embed_key}. Call the schema endpoint first to understand what this particular widget requires.","content":{"application/json":{"schema":{"type":"object","required":["lead","context"],"properties":{"lead":{"type":"object","description":"Contact fields for this lead. Required fields are widget-specific — call GET /agent/v1/schema/{embed_key} and use schema.properties.lead.required to determine which fields must be collected. The properties below are the complete set of supported fields; only a subset will be required.","properties":{"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"},"company":{"type":"string"},"job_title":{"type":"string"},"contact_country":{"type":"string"},"phone":{"type":"string"},"linkedin_url":{"type":"string","format":"uri"},"company_url":{"type":"string","format":"uri"}}},"intent":{"type":"object","properties":{"primary":{"type":"string","description":"Intent identifier for this submission. Call GET /agent/v1/schema/{embed_key} to retrieve the supported intents for the specific widget you are submitting to."},"urgency":{"type":"string","enum":["high","medium","low"]}}},"scoring_answers":{"type":"object","description":"Key-value pairs matching public question keys from the schema endpoint's scoring_hints for the chosen intent. Use only option values listed in the schema — do not invent values.","additionalProperties":true},"context":{"type":"object","required":["source"],"properties":{"source":{"type":"string","description":"Origin of the submission. Use \"chatgpt_action\", \"claude_tool\", or \"agent_api\" as appropriate."},"referrer":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Lead evaluation result","content":{"application/json":{"schema":{"type":"object","properties":{"decision":{"type":"string","enum":["approved","blocked","needs_more_info"]},"blocking":{"type":"boolean"},"lead_id":{"type":"integer","description":"Present only when decision=approved"},"missing_fields":{"type":"array","items":{"type":"string"}},"recommended_fields":{"type":"array","items":{"type":"string"}},"next_action":{"type":"string","enum":["submitted","request_missing_fields","confirm_and_resubmit"],"description":"Public control-flow action. Internal routing actions are collapsed to submitted after a lead is created."},"evaluated_at":{"type":"string","format":"date-time"},"booking_url":{"type":"string","format":"uri","description":"Present when decision=approved and the widget has a booking URL configured. This is a handoff link — not a confirmed booking. The user must visit this URL to schedule a call or meeting. Present to the user as a next step after approval."}}}}}},"429":{"description":"rate_limited"}}}},"/agent/v1/lead/{lead_id}/status":{"get":{"summary":"Check the status of a previously submitted lead","description":"Returns the current enquiry status for a lead created via POST /agent/v1/submit/{embed_key}. Requires the same embed_key used for that submission -- a lead can only be checked through a widget belonging to the same company it was submitted to.","operationId":"getLeadStatus","parameters":[{"name":"lead_id","in":"path","required":true,"schema":{"type":"integer"},"description":"The lead_id returned by a successful submit call."},{"name":"embed_key","in":"query","required":true,"schema":{"type":"string"},"description":"The embed_key the lead was submitted through, or any other widget belonging to the same company."}],"responses":{"200":{"description":"Lead status","content":{"application/json":{"schema":{"type":"object","properties":{"lead_id":{"type":"integer"},"submitted_at":{"type":"string","format":"date-time"},"decision":{"type":"string"},"status":{"type":"string"},"next_check_after_seconds":{"type":"integer"}}}}}},"404":{"description":"No lead found for this lead_id and embed_key combination -- returned identically whether embed_key is missing, unknown, or the lead belongs to a different company."}}}}}}