{"serverInfo":{"name":"plyto","title":"Plyto · the CRM that runs itself","version":"0.1.0"},"authentication":{"required":true,"schemes":["oauth2","bearer"],"description":"Connect via OAuth 2.1 (discovery at /.well-known/oauth-authorization-server; dynamic client registration supported), or send a per-business Plyto API key as 'Authorization: Bearer <key>' or 'x-api-key: <key>'. Create keys in Plyto under Settings, API keys. Docs: https://plyto.ai/developers"},"tools":[{"name":"memory__save","title":"Save: memory","annotations":{"title":"Save: memory","readOnlyHint":false,"destructiveHint":false},"description":"Save (or update) a durable memory: a standing preference, playbook rule, correction, or business fact that should hold across ALL future conversations. Use when the user says \"remember…\", \"always…\", \"never…\", \"from now on…\", states a lasting preference, or corrects how you did something. One distilled rule per memory, ≤500 characters. If an existing memory covers the same topic (they're listed in your context with ids), pass its memoryId to UPDATE it instead of creating a near-duplicate. Do NOT save one-off task details, secrets, credentials, or personal contact data. Contact facts belong on the contact (crm.notes.add).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"content":{"type":"string","minLength":1,"maxLength":500,"description":"The rule itself, self-contained and imperative."},"kind":{"type":"string","enum":["preference","fact","playbook","correction"],"description":"preference = how they like things done; fact = stable business fact; playbook = how to run a recurring task; correction = a fix after you got something wrong."},"scope":{"description":"business (default) = applies to everyone; personal = only the current user's own preference (e.g. how THEY want summaries).","type":"string","enum":["business","personal"]},"source":{"description":"user_told (default) = the user explicitly said it; learned = you inferred it from a correction or repeated behavior.","type":"string","enum":["user_told","learned"]},"memoryId":{"description":"Update this existing memory instead of creating a new one.","type":"string"}},"required":["content","kind"]}},{"name":"memory__list","title":"List: memory","annotations":{"title":"List: memory","readOnlyHint":true,"destructiveHint":false},"description":"List every durable memory you hold for this business (business-wide plus the current user's personal ones), with ids and when each was last confirmed. Use before consolidating, when the user asks what you remember, or to find a memoryId to update/forget.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"memory__forget","title":"Forget: memory","annotations":{"title":"Forget: memory","readOnlyHint":false,"destructiveHint":false},"description":"Permanently delete a saved memory. Destructive: confirm with the user first unless they just explicitly asked you to forget exactly this. Users can also remove memories themselves under Settings → Business profile.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"memoryId":{"type":"string","minLength":1}},"required":["memoryId"]}},{"name":"business__update","title":"Update: business","annotations":{"title":"Update: business","readOnlyHint":false,"destructiveHint":false},"description":"Save what you learn about the business the moment the owner says it: real name, website, industry, who their customers are, what sets them apart. A workspace created from a signup carries a placeholder name until you set the real one, and the placeholder would otherwise end up in their ads and emails. Setting a website starts a background scan that fills the brand profile (industry, audience, voice, logo, colors) and, for a workspace with nothing in it yet, stages a starter kit (lead form, welcome email, follow-up automation, all as drafts); tell the user the scan takes about a minute. Pass only the fields you actually learned.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"description":"The business's real name, exactly as the owner gave it.","type":"string","minLength":1,"maxLength":120},"websiteUrl":{"description":"Their website, with or without https://.","type":"string","minLength":3,"maxLength":300},"industry":{"description":"What kind of business, in their words: 'mobile dog grooming', 'roofing contractor', 'boutique fitness studio'.","type":"string","minLength":2,"maxLength":80},"audience":{"description":"Who they sell to, as they described it: 'homeowners in Austin with dogs', 'B2B SaaS founders under 20 employees'.","type":"string","minLength":3,"maxLength":600},"valueProp":{"description":"What sets them apart, in one or two sentences, from the owner's own words.","type":"string","minLength":3,"maxLength":600}}}},{"name":"crm__contacts__create","title":"Create: contacts","annotations":{"title":"Create: contacts","readOnlyHint":false,"destructiveHint":false},"description":"Create a contact (or find-and-update if one with the same email already exists in this business). Returns the created/updated contact. Use when the user mentions adding, capturing, or tracking a new person. Email is the natural dedup key; if you have an email, always pass it.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"email":{"description":"Primary email, used as dedup key.","type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"firstName":{"type":"string","minLength":1,"maxLength":120},"lastName":{"type":"string","minLength":1,"maxLength":120},"phone":{"type":"string","maxLength":40},"companyId":{"description":"Existing company id to link this contact to.","type":"string"},"title":{"type":"string","maxLength":120},"lifecycleStage":{"type":"string","enum":["subscriber","lead","marketing_qualified","sales_qualified","opportunity","customer","evangelist","other"]},"leadStatus":{"type":"string","enum":["new","open","in_progress","open_deal","unqualified","attempted","connected","bad_timing"]},"source":{"description":"Where this contact came from: 'manual', 'import', 'form', 'ad', etc.","type":"string","maxLength":60},"sourceDetail":{"type":"string","maxLength":200}}}},{"name":"crm__contacts__update","title":"Update: contacts","annotations":{"title":"Update: contacts","readOnlyHint":false,"destructiveHint":false},"description":"Update fields on an existing contact. Only pass fields you want to change. No-op fields are silently skipped.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string","minLength":1},"email":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"firstName":{"anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"lastName":{"anyOf":[{"type":"string","minLength":1,"maxLength":120},{"type":"null"}]},"phone":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}]},"companyId":{"anyOf":[{"type":"string"},{"type":"null"}]},"title":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"lifecycleStage":{"type":"string","enum":["subscriber","lead","marketing_qualified","sales_qualified","opportunity","customer","evangelist","other"]},"leadStatus":{"anyOf":[{"type":"string","enum":["new","open","in_progress","open_deal","unqualified","attempted","connected","bad_timing"]},{"type":"null"}]},"ownerUserId":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["contactId"]}},{"name":"crm__contacts__get","title":"Get: contacts","annotations":{"title":"Get: contacts","readOnlyHint":true,"destructiveHint":false},"description":"Fetch a single contact by id, with where they came from: the ad campaign and search keyword that brought them (when an ad did), where they were and on what device, what they asked for on the landing page, how many forms they submitted, and whether they have created an account. Use this, not source_detail alone, to say how someone arrived. Returns null if not found in this business.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string","minLength":1}},"required":["contactId"]}},{"name":"crm__contacts__list","title":"List: contacts","annotations":{"title":"List: contacts","readOnlyHint":true,"destructiveHint":false},"description":"List contacts in this business with simple filters. For free-text search use crm.contacts.search. For complex filters use crm.contacts.query (W3).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100},"offset":{"type":"integer","minimum":0,"maximum":9007199254740991},"ownerUserId":{"type":"string"},"lifecycleStage":{"type":"string","enum":["subscriber","lead","marketing_qualified","sales_qualified","opportunity","customer","evangelist","other"]},"hasEmail":{"type":"boolean"}}}},{"name":"crm__contacts__search","title":"Search: contacts","annotations":{"title":"Search: contacts","readOnlyHint":true,"destructiveHint":false},"description":"Free-text search across contact email, first/last name, and phone. Case-insensitive substring match. Returns up to 20 contacts by default.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"limit":{"type":"integer","minimum":1,"maximum":50}},"required":["query"]}},{"name":"crm__contacts__get_properties","title":"Get properties: contacts","annotations":{"title":"Get properties: contacts","readOnlyHint":true,"destructiveHint":false},"description":"Return all custom property values for a contact as a {key: value} map.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string","minLength":1}},"required":["contactId"]}},{"name":"crm__companies__create","title":"Create: companies","annotations":{"title":"Create: companies","readOnlyHint":false,"destructiveHint":false},"description":"Create a company (or find-and-update if one with the same domain already exists). Domain is the natural dedup key; always pass the bare domain like 'acme.com' (not the full URL).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"domain":{"type":"string","maxLength":255},"industry":{"type":"string","maxLength":120},"size":{"type":"string","enum":["1-10","11-50","51-200","201-1000","1001-5000","5001+"]},"annualRevenueCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"description":{"type":"string","maxLength":2000}},"required":["name"]}},{"name":"crm__companies__update","title":"Update: companies","annotations":{"title":"Update: companies","readOnlyHint":false,"destructiveHint":false},"description":"Update fields on an existing company. Only pass fields you want to change.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"companyId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":200},"domain":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}]},"industry":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}]},"size":{"anyOf":[{"type":"string","enum":["1-10","11-50","51-200","201-1000","1001-5000","5001+"]},{"type":"null"}]},"annualRevenueCents":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"description":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]},"ownerUserId":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["companyId"]}},{"name":"crm__companies__get","title":"Get: companies","annotations":{"title":"Get: companies","readOnlyHint":true,"destructiveHint":false},"description":"Fetch a single company by id. Returns null if not found.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"companyId":{"type":"string","minLength":1}},"required":["companyId"]}},{"name":"crm__companies__list","title":"List: companies","annotations":{"title":"List: companies","readOnlyHint":true,"destructiveHint":false},"description":"List companies in this business. For text search use crm.companies.search.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100},"offset":{"type":"integer","minimum":0,"maximum":9007199254740991},"ownerUserId":{"type":"string"}}}},{"name":"crm__companies__search","title":"Search: companies","annotations":{"title":"Search: companies","readOnlyHint":true,"destructiveHint":false},"description":"Free-text search across company name, domain, and industry. Case-insensitive substring match.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"limit":{"type":"integer","minimum":1,"maximum":50}},"required":["query"]}},{"name":"crm__companies__get_properties","title":"Get properties: companies","annotations":{"title":"Get properties: companies","readOnlyHint":true,"destructiveHint":false},"description":"Return all custom property values for a company as a {key: value} map.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"companyId":{"type":"string","minLength":1}},"required":["companyId"]}},{"name":"crm__tags__create","title":"Create: tags","annotations":{"title":"Create: tags","readOnlyHint":false,"destructiveHint":false},"description":"Create a new tag (or find one with the same name; idempotent). Tags are lightweight orthogonal categories like 'VIP', 'Newsletter-only', 'Cold'.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"}},"required":["name"]}},{"name":"crm__tags__list","title":"List: tags","annotations":{"title":"List: tags","readOnlyHint":true,"destructiveHint":false},"description":"List all tags defined in this business.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"crm__tags__add_to_contact","title":"Add to contact: tags","annotations":{"title":"Add to contact: tags","readOnlyHint":false,"destructiveHint":false},"description":"Apply a tag to a contact. Idempotent: adding an already-applied tag is a no-op.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string","minLength":1},"tagId":{"type":"string","minLength":1}},"required":["contactId","tagId"]}},{"name":"crm__tags__remove_from_contact","title":"Remove from contact: tags","annotations":{"title":"Remove from contact: tags","readOnlyHint":false,"destructiveHint":false},"description":"Remove a tag from a contact. Idempotent.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string","minLength":1},"tagId":{"type":"string","minLength":1}},"required":["contactId","tagId"]}},{"name":"crm__tags__list_for_contact","title":"List for contact: tags","annotations":{"title":"List for contact: tags","readOnlyHint":true,"destructiveHint":false},"description":"List all tags applied to a contact.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string","minLength":1}},"required":["contactId"]}},{"name":"crm__tags__add_to_company","title":"Add to company: tags","annotations":{"title":"Add to company: tags","readOnlyHint":false,"destructiveHint":false},"description":"Apply a tag to a company. Idempotent.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"companyId":{"type":"string","minLength":1},"tagId":{"type":"string","minLength":1}},"required":["companyId","tagId"]}},{"name":"crm__tags__remove_from_company","title":"Remove from company: tags","annotations":{"title":"Remove from company: tags","readOnlyHint":false,"destructiveHint":false},"description":"Remove a tag from a company. Idempotent.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"companyId":{"type":"string","minLength":1},"tagId":{"type":"string","minLength":1}},"required":["companyId","tagId"]}},{"name":"crm__tags__list_for_company","title":"List for company: tags","annotations":{"title":"List for company: tags","readOnlyHint":true,"destructiveHint":false},"description":"List all tags applied to a company.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"companyId":{"type":"string","minLength":1}},"required":["companyId"]}},{"name":"crm__properties__define","title":"Define: properties","annotations":{"title":"Define: properties","readOnlyHint":false,"destructiveHint":false},"description":"Define a new custom property on contact or company. Keys must be lowercase snake_case (e.g. 'plan_tier'). For single_select / multi_select, options is required.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"entityType":{"type":"string","enum":["contact","company","deal"]},"key":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z][a-z0-9_]*$"},"label":{"type":"string","minLength":1,"maxLength":120},"propertyType":{"type":"string","enum":["text","number","date","single_select","multi_select","checkbox","url","email"]},"options":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"value":{"type":"string","minLength":1,"maxLength":120},"label":{"type":"string","minLength":1,"maxLength":120},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"}},"required":["value","label"]}},"isRequired":{"type":"boolean"},"displayOrder":{"type":"integer","minimum":0,"maximum":10000},"description":{"type":"string","maxLength":500}},"required":["entityType","key","label","propertyType"]}},{"name":"crm__properties__list","title":"List: properties","annotations":{"title":"List: properties","readOnlyHint":true,"destructiveHint":false},"description":"List custom property definitions for an entity type. Excludes archived by default.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"entityType":{"type":"string","enum":["contact","company","deal"]},"includeArchived":{"type":"boolean"}},"required":["entityType"]}},{"name":"crm__properties__archive","title":"Archive: properties","annotations":{"title":"Archive: properties","readOnlyHint":false,"destructiveHint":false},"description":"Archive a custom property. Existing values are preserved but the property is hidden from UI and rejected for new value sets. To 'rename', archive + redefine.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"propertyDefinitionId":{"type":"string","minLength":1}},"required":["propertyDefinitionId"]}},{"name":"crm__properties__set_contact_value","title":"Set contact value: properties","annotations":{"title":"Set contact value: properties","readOnlyHint":false,"destructiveHint":false},"description":"Set the value of a custom property on a contact. Value type must match property type. For multi_select pass an array of option values.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string","minLength":1},"propertyDefinitionId":{"type":"string","minLength":1},"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}}]}},"required":["contactId","propertyDefinitionId","value"]}},{"name":"crm__properties__set_company_value","title":"Set company value: properties","annotations":{"title":"Set company value: properties","readOnlyHint":false,"destructiveHint":false},"description":"Set the value of a custom property on a company.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"companyId":{"type":"string","minLength":1},"propertyDefinitionId":{"type":"string","minLength":1},"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}}]}},"required":["companyId","propertyDefinitionId","value"]}},{"name":"crm__properties__set_deal_value","title":"Set deal value: properties","annotations":{"title":"Set deal value: properties","readOnlyHint":false,"destructiveHint":false},"description":"Set the value of a custom property on a deal.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dealId":{"type":"string","minLength":1},"propertyDefinitionId":{"type":"string","minLength":1},"value":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}}]}},"required":["dealId","propertyDefinitionId","value"]}},{"name":"crm__properties__clear_contact_value","title":"Clear contact value: properties","annotations":{"title":"Clear contact value: properties","readOnlyHint":false,"destructiveHint":false},"description":"Remove a custom property value from a contact.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string","minLength":1},"propertyDefinitionId":{"type":"string","minLength":1}},"required":["contactId","propertyDefinitionId"]}},{"name":"crm__properties__clear_company_value","title":"Clear company value: properties","annotations":{"title":"Clear company value: properties","readOnlyHint":false,"destructiveHint":false},"description":"Remove a custom property value from a company.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"companyId":{"type":"string","minLength":1},"propertyDefinitionId":{"type":"string","minLength":1}},"required":["companyId","propertyDefinitionId"]}},{"name":"crm__properties__clear_deal_value","title":"Clear deal value: properties","annotations":{"title":"Clear deal value: properties","readOnlyHint":false,"destructiveHint":false},"description":"Remove a custom property value from a deal.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dealId":{"type":"string","minLength":1},"propertyDefinitionId":{"type":"string","minLength":1}},"required":["dealId","propertyDefinitionId"]}},{"name":"crm__properties__get_deal_values","title":"Get deal values: properties","annotations":{"title":"Get deal values: properties","readOnlyHint":true,"destructiveHint":false},"description":"Return all custom property values for a deal as a {key: value} map.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dealId":{"type":"string","minLength":1}},"required":["dealId"]}},{"name":"crm__pipelines__list","title":"List: pipelines","annotations":{"title":"List: pipelines","readOnlyHint":true,"destructiveHint":false},"description":"List all pipelines in this business (excludes archived by default).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"includeArchived":{"type":"boolean"}}}},{"name":"crm__pipelines__list_stages","title":"List stages: pipelines","annotations":{"title":"List stages: pipelines","readOnlyHint":true,"destructiveHint":false},"description":"List the ordered stages in a pipeline. Use to look up stageIds for crm.deals.move_stage.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"pipelineId":{"type":"string","minLength":1}},"required":["pipelineId"]}},{"name":"crm__pipelines__get_default","title":"Get default: pipelines","annotations":{"title":"Get default: pipelines","readOnlyHint":true,"destructiveHint":false},"description":"Get the default pipeline for this business (the one new deals land in by default). Returns null if no default is set.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"crm__deals__create","title":"Create: deals","annotations":{"title":"Create: deals","readOnlyHint":false,"destructiveHint":false},"description":"Create a deal in a pipeline. If pipelineId/stageId omitted, lands in the business's default pipeline at the first open stage. Attach to a contact and/or company for full context. valueCents is the deal size in cents (e.g. $5,000 = 500000).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"pipelineId":{"type":"string"},"stageId":{"type":"string"},"contactId":{"type":"string"},"companyId":{"type":"string"},"valueCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"currency":{"description":"ISO 4217 code, default USD.","type":"string","minLength":3,"maxLength":3},"expectedCloseAt":{"description":"ISO 8601 timestamp.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"ownerUserId":{"type":"string"},"source":{"type":"string","maxLength":60}},"required":["name"]}},{"name":"crm__deals__update","title":"Update: deals","annotations":{"title":"Update: deals","readOnlyHint":false,"destructiveHint":false},"description":"Update fields on a deal. Use crm.deals.move_stage to change stage (it handles close events). Use crm.deals.close_won / close_lost for the won/lost transitions.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dealId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":200},"contactId":{"anyOf":[{"type":"string"},{"type":"null"}]},"companyId":{"anyOf":[{"type":"string"},{"type":"null"}]},"valueCents":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]},"currency":{"type":"string","minLength":3,"maxLength":3},"expectedCloseAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"ownerUserId":{"anyOf":[{"type":"string"},{"type":"null"}]},"source":{"anyOf":[{"type":"string","maxLength":60},{"type":"null"}]}},"required":["dealId"]}},{"name":"crm__deals__move_stage","title":"Move stage: deals","annotations":{"title":"Move stage: deals","readOnlyHint":false,"destructiveHint":false},"description":"Move a deal to a different stage in the same pipeline. If the target stage is a won/lost stage, this auto-sets closedAt and emits the appropriate close event.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dealId":{"type":"string","minLength":1},"targetStageId":{"type":"string","minLength":1}},"required":["dealId","targetStageId"]}},{"name":"crm__deals__close_won","title":"Close won: deals","annotations":{"title":"Close won: deals","readOnlyHint":false,"destructiveHint":false},"description":"Close a deal as won: moves it to the won stage in its pipeline. Sets closedAt and emits deal.won.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dealId":{"type":"string","minLength":1}},"required":["dealId"]}},{"name":"crm__deals__close_lost","title":"Close lost: deals","annotations":{"title":"Close lost: deals","readOnlyHint":false,"destructiveHint":false},"description":"Close a deal as lost: moves it to the lost stage in its pipeline. Records lostReason and emits deal.lost.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dealId":{"type":"string","minLength":1},"reason":{"type":"string","maxLength":500}},"required":["dealId"]}},{"name":"crm__deals__get","title":"Get: deals","annotations":{"title":"Get: deals","readOnlyHint":true,"destructiveHint":false},"description":"Fetch a single deal by id.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"dealId":{"type":"string","minLength":1}},"required":["dealId"]}},{"name":"crm__deals__list","title":"List: deals","annotations":{"title":"List: deals","readOnlyHint":true,"destructiveHint":false},"description":"List deals with simple filters. Filter by pipeline/stage for Kanban view, or by contact/company for deal history. `open: true` returns only open-stage deals; omit for all.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"pipelineId":{"type":"string"},"stageId":{"type":"string"},"ownerUserId":{"type":"string"},"contactId":{"type":"string"},"companyId":{"type":"string"},"open":{"type":"boolean"},"limit":{"type":"integer","minimum":1,"maximum":200}}}},{"name":"crm__deals__search","title":"Search: deals","annotations":{"title":"Search: deals","readOnlyHint":true,"destructiveHint":false},"description":"Free-text search across deal names.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"limit":{"type":"integer","minimum":1,"maximum":50}},"required":["query"]}},{"name":"crm__activities__log","title":"Log: activities","annotations":{"title":"Log: activities","readOnlyHint":false,"destructiveHint":false},"description":"Log an activity: something that happened (past or scheduled). Distinguished from tasks (future intent) and notes (no structure). Use 'log a 30-min discovery call with Jane' style prompts. happenedAt defaults to now if omitted.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"type":{"type":"string","enum":["call","email","meeting","sms","other"]},"title":{"type":"string","minLength":1,"maxLength":200},"body":{"type":"string","maxLength":5000},"happenedAt":{"description":"ISO 8601. Default: now.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"durationMinutes":{"type":"integer","exclusiveMinimum":0,"maximum":1440},"contactId":{"type":"string"},"companyId":{"type":"string"},"dealId":{"type":"string"},"ownerUserId":{"type":"string"}},"required":["type","title"]}},{"name":"crm__activities__list","title":"List: activities","annotations":{"title":"List: activities","readOnlyHint":true,"destructiveHint":false},"description":"List activities, optionally filtered by entity (contact/company/deal) or owner. Returned in reverse chronological order by happenedAt.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string"},"companyId":{"type":"string"},"dealId":{"type":"string"},"ownerUserId":{"type":"string"},"type":{"type":"string","enum":["call","email","meeting","sms","other"]},"limit":{"type":"integer","minimum":1,"maximum":200}}}},{"name":"crm__activities__get","title":"Get: activities","annotations":{"title":"Get: activities","readOnlyHint":true,"destructiveHint":false},"description":"Fetch a single activity by id.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"activityId":{"type":"string","minLength":1}},"required":["activityId"]}},{"name":"crm__tasks__create","title":"Create: tasks","annotations":{"title":"Create: tasks","readOnlyHint":false,"destructiveHint":false},"description":"Create a task: future intent ('follow up with Acme by Friday'). Distinguished from activities (past event) and notes (no structure). Auto-assigns to the calling user if assignedToUserId omitted.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":5000},"dueAt":{"description":"ISO 8601 timestamp.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"priority":{"type":"string","enum":["low","medium","high","urgent"]},"assignedToUserId":{"type":"string"},"contactId":{"type":"string"},"companyId":{"type":"string"},"dealId":{"type":"string"}},"required":["title"]}},{"name":"crm__tasks__complete","title":"Complete: tasks","annotations":{"title":"Complete: tasks","readOnlyHint":false,"destructiveHint":false},"description":"Mark a task as completed. Sets completedAt to now.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"taskId":{"type":"string","minLength":1}},"required":["taskId"]}},{"name":"crm__tasks__cancel","title":"Cancel: tasks","annotations":{"title":"Cancel: tasks","readOnlyHint":false,"destructiveHint":false},"description":"Mark a task as cancelled (rejected, different from completed). Use when the user decides a task is no longer relevant.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"taskId":{"type":"string","minLength":1}},"required":["taskId"]}},{"name":"crm__tasks__list","title":"List: tasks","annotations":{"title":"List: tasks","readOnlyHint":true,"destructiveHint":false},"description":"List tasks. Open tasks sort by due date (closest first). Completed tasks sort by completion (most recent first).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"assignedToUserId":{"type":"string"},"status":{"type":"string","enum":["open","completed","cancelled"]},"contactId":{"type":"string"},"companyId":{"type":"string"},"dealId":{"type":"string"},"limit":{"type":"integer","minimum":1,"maximum":200}}}},{"name":"crm__tasks__get","title":"Get: tasks","annotations":{"title":"Get: tasks","readOnlyHint":true,"destructiveHint":false},"description":"Fetch a single task by id.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"taskId":{"type":"string","minLength":1}},"required":["taskId"]}},{"name":"reports__preview","title":"Preview: reports","annotations":{"title":"Preview: reports","readOnlyHint":true,"destructiveHint":false},"description":"Run a report config WITHOUT saving. Use it to answer analytical questions ('deals by stage', 'contacts added per month', 'email opens last 30 days') and to sanity-check a config before reports.create. Returns rows + totals. Money values are in cents.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"config":{"type":"object","properties":{"entity":{"type":"string","enum":["contacts","deals","tasks","emails","stage_transitions","ad_metrics","sources"],"description":"stage_transitions = deal stage-change events (one row per stage a deal entered), for 'stage moves per month' or 'deals entering each stage'. ad_metrics = daily ad performance snapshots (spend/conversions/clicks/impressions). sources = deals grouped by the linked contact's marketing source, for 'revenue won by source' (metric=sum_value, filter stageType=won) or 'deals by source'; use entity=contacts groupBy=source for lead counts instead."},"metric":{"type":"string","enum":["count","sum_value","avg_value","sum_spend","sum_conversions","sum_clicks","sum_impressions"],"description":"sum_value/avg_value = deal value, only valid for entity=deals or sources. sum_spend/sum_conversions/sum_clicks/sum_impressions = daily ad performance, only valid for entity=ad_metrics (which requires one of them)."},"windowDays":{"description":"Only rows from the last N days (created date; closed date for won/lost deals)","type":"integer","minimum":1,"maximum":730},"filters":{"description":"Equality filters. contacts: lifecycleStage(lead|marketing_qualified|sales_qualified|customer), source. deals: stageType(open|won|lost). tasks: status(open|completed|cancelled), priority(low|medium|high|urgent). emails: status(queued|sent|delivered|opened|clicked|bounced|failed). stage_transitions: toStageType(open|won|lost). ad_metrics: campaignId(an ad campaign id). sources: stageType(open|won|lost), source(a contact source value like form|webhook|manual|import|ad).","maxItems":4,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","maxLength":40},"value":{"type":"string","maxLength":60}},"required":["field","value"]}},"groupBy":{"description":"Bar/table grouping. contacts: lifecycleStage|source|month. deals: stage|stageType|month. tasks: status|priority|month. emails: status|month. stage_transitions: stage|month (stage = the stage entered). ad_metrics: campaign|month. sources: source|month. Omit for a single number.","type":"string","maxLength":40},"viz":{"type":"string","enum":["number","bar","table"],"description":"number = one big stat (no groupBy); bar/table need groupBy"}},"required":["entity","metric","viz"]}},"required":["config"]}},{"name":"crm__sources__performance","title":"Performance: sources","annotations":{"title":"Performance: sources","readOnlyHint":false,"destructiveHint":false},"description":"Marketing-source attribution: for every source (ad campaigns, UTM-tagged traffic, forms, inbound webhook, manual, import…; the webhook also accepts POST {email, outcome: qualified|customer} at /outcome so an external system of record can report when a lead pays) the full funnel (leads acquired, deals created, deals won, won revenue) plus ad spend, CPL, CAC and ROAS where spend exists. THE tool for 'which marketing source makes me the most money'. Sorted by won revenue; money values are in cents. Mirrors the Reports → Sources view.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"windowDays":{"description":"Look-back window in days; each funnel stage is windowed independently (leads created / deals opened / deals won inside it). Default 90.","type":"integer","minimum":1,"maximum":730}}}},{"name":"reports__create","title":"Create: reports","annotations":{"title":"Create: reports","readOnlyHint":false,"destructiveHint":false},"description":"Save a custom report as a live card on the user's Reports dashboard. Use when the user asks for a report or says yes to saving an analysis. The config is re-executed on every dashboard view (always live). Validates by running once; returns the saved report + current data.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Card title, e.g. 'Deals won by month'"},"description":{"type":"string","maxLength":300},"config":{"type":"object","properties":{"entity":{"type":"string","enum":["contacts","deals","tasks","emails","stage_transitions","ad_metrics","sources"],"description":"stage_transitions = deal stage-change events (one row per stage a deal entered), for 'stage moves per month' or 'deals entering each stage'. ad_metrics = daily ad performance snapshots (spend/conversions/clicks/impressions). sources = deals grouped by the linked contact's marketing source, for 'revenue won by source' (metric=sum_value, filter stageType=won) or 'deals by source'; use entity=contacts groupBy=source for lead counts instead."},"metric":{"type":"string","enum":["count","sum_value","avg_value","sum_spend","sum_conversions","sum_clicks","sum_impressions"],"description":"sum_value/avg_value = deal value, only valid for entity=deals or sources. sum_spend/sum_conversions/sum_clicks/sum_impressions = daily ad performance, only valid for entity=ad_metrics (which requires one of them)."},"windowDays":{"description":"Only rows from the last N days (created date; closed date for won/lost deals)","type":"integer","minimum":1,"maximum":730},"filters":{"description":"Equality filters. contacts: lifecycleStage(lead|marketing_qualified|sales_qualified|customer), source. deals: stageType(open|won|lost). tasks: status(open|completed|cancelled), priority(low|medium|high|urgent). emails: status(queued|sent|delivered|opened|clicked|bounced|failed). stage_transitions: toStageType(open|won|lost). ad_metrics: campaignId(an ad campaign id). sources: stageType(open|won|lost), source(a contact source value like form|webhook|manual|import|ad).","maxItems":4,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","maxLength":40},"value":{"type":"string","maxLength":60}},"required":["field","value"]}},"groupBy":{"description":"Bar/table grouping. contacts: lifecycleStage|source|month. deals: stage|stageType|month. tasks: status|priority|month. emails: status|month. stage_transitions: stage|month (stage = the stage entered). ad_metrics: campaign|month. sources: source|month. Omit for a single number.","type":"string","maxLength":40},"viz":{"type":"string","enum":["number","bar","table"],"description":"number = one big stat (no groupBy); bar/table need groupBy"}},"required":["entity","metric","viz"]}},"required":["name","config"]}},{"name":"reports__list","title":"List: reports","annotations":{"title":"List: reports","readOnlyHint":true,"destructiveHint":false},"description":"List the saved custom reports on the Reports dashboard.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"crm__notes__add","title":"Add: notes","annotations":{"title":"Add: notes","readOnlyHint":false,"destructiveHint":false},"description":"Add a freeform note attached to a contact, company, deal, or any combination. Use for 'remember that Jane prefers email over phone' style facts.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"body":{"type":"string","minLength":1,"maxLength":10000},"contactId":{"type":"string"},"companyId":{"type":"string"},"dealId":{"type":"string"}},"required":["body"]}},{"name":"crm__notes__list","title":"List: notes","annotations":{"title":"List: notes","readOnlyHint":true,"destructiveHint":false},"description":"List notes, optionally filtered by attached entity.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string"},"companyId":{"type":"string"},"dealId":{"type":"string"},"limit":{"type":"integer","minimum":1,"maximum":200}}}},{"name":"crm__lists__create","title":"Create: lists","annotations":{"title":"Create: lists","readOnlyHint":false,"destructiveHint":false},"description":"Create a contact list. Manual lists are curated by hand; dynamic lists have a filter DSL (DNF: {or:[{and:[conditions]}]}) that defines membership. For dynamic, pass `criteria`. For manual, omit it.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"type":{"type":"string","enum":["manual","dynamic"]},"description":{"type":"string","maxLength":500},"criteria":{"type":"object","properties":{"or":{"type":"array","items":{"type":"object","properties":{"and":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","minLength":1},"op":{"type":"string","minLength":1},"value":{}},"required":["field","op"]}}},"required":["and"]}}},"required":["or"]}},"required":["name","type"]}},{"name":"crm__lists__get","title":"Get: lists","annotations":{"title":"Get: lists","readOnlyHint":true,"destructiveHint":false},"description":"Fetch a single list by id.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"listId":{"type":"string","minLength":1}},"required":["listId"]}},{"name":"crm__lists__list","title":"List: lists","annotations":{"title":"List: lists","readOnlyHint":true,"destructiveHint":false},"description":"List all contact lists in this business.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"includeArchived":{"type":"boolean"}}}},{"name":"crm__lists__members","title":"Members: lists","annotations":{"title":"Members: lists","readOnlyHint":false,"destructiveHint":false},"description":"Return the contacts that are members of a list. For manual lists this returns explicit memberships; for dynamic lists it evaluates the criteria filter against contacts (lazy recompute).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"listId":{"type":"string","minLength":1},"limit":{"type":"integer","minimum":1,"maximum":500}},"required":["listId"]}},{"name":"crm__lists__add_contact","title":"Add contact: lists","annotations":{"title":"Add contact: lists","readOnlyHint":false,"destructiveHint":false},"description":"Add a contact to a MANUAL list. Idempotent.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"listId":{"type":"string","minLength":1},"contactId":{"type":"string","minLength":1}},"required":["listId","contactId"]}},{"name":"crm__lists__remove_contact","title":"Remove contact: lists","annotations":{"title":"Remove contact: lists","readOnlyHint":false,"destructiveHint":false},"description":"Remove a contact from a manual list.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"listId":{"type":"string","minLength":1},"contactId":{"type":"string","minLength":1}},"required":["listId","contactId"]}},{"name":"crm__lists__archive","title":"Archive: lists","annotations":{"title":"Archive: lists","readOnlyHint":false,"destructiveHint":false},"description":"Archive a list. Membership rows are preserved but the list is hidden from default queries.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"listId":{"type":"string","minLength":1}},"required":["listId"]}},{"name":"forms__create","title":"Create: forms","annotations":{"title":"Create: forms","readOnlyHint":false,"destructiveHint":false},"description":"Create a form. Slug becomes the URL: /forms/<slug>. Fields are JSON-defined with type matching the contact property type system (text/email/url/number/date/checkbox/select/multi_select/textarea/phone). If captureToListId is set, every submission is auto-added to that list (use crm.lists.create first to get a listId).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"slug":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z0-9][a-z0-9-]*$"},"fields":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z][a-z0-9_]*$"},"label":{"type":"string","minLength":1,"maxLength":120},"type":{"type":"string","enum":["text","email","url","number","date","checkbox","select","multi_select","textarea","phone"]},"required":{"type":"boolean"},"placeholder":{"type":"string","maxLength":200},"options":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"value":{"type":"string","minLength":1,"maxLength":120},"label":{"type":"string","minLength":1,"maxLength":120}},"required":["value","label"]}},"mapsToContactPropertyKey":{"type":"string","maxLength":60}},"required":["key","label","type"]}},"theme":{"description":"Per-form appearance; omitted values inherit the business brand","type":"object","properties":{"accentColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"imageUrl":{"description":"Banner image URL shown at the top of the hosted form","type":"string"},"description":{"description":"Intro paragraph under the form title","type":"string","maxLength":500}}},"submitButtonText":{"type":"string","maxLength":60},"thankYouMessage":{"type":"string","maxLength":500},"thankYouRedirectUrl":{"type":"string","format":"uri"},"captureToListId":{"type":"string"}},"required":["name","slug","fields"]}},{"name":"forms__update","title":"Update: forms","annotations":{"title":"Update: forms","readOnlyHint":false,"destructiveHint":false},"description":"Update a form: rename it, replace its fields, change appearance (theme), button text, thank-you behavior, or the capture list. Omitted properties keep their current value; `fields` and `theme` replace wholesale when provided, so send the COMPLETE new array/object (use forms.get first). Keep existing field `key`s so past submissions and automations stay attached. Users can also do this themselves at Forms → open the form → Edit form.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"formId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":120},"fields":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[a-z][a-z0-9_]*$"},"label":{"type":"string","minLength":1,"maxLength":120},"type":{"type":"string","enum":["text","email","url","number","date","checkbox","select","multi_select","textarea","phone"]},"required":{"type":"boolean"},"placeholder":{"type":"string","maxLength":200},"options":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"value":{"type":"string","minLength":1,"maxLength":120},"label":{"type":"string","minLength":1,"maxLength":120}},"required":["value","label"]}},"mapsToContactPropertyKey":{"type":"string","maxLength":60}},"required":["key","label","type"]}},"theme":{"description":"Replaces the whole theme; null clears it back to brand defaults","anyOf":[{"type":"object","properties":{"title":{"description":"Public heading; the internal name stays for lists","type":"string","maxLength":120},"accentColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"imageUrl":{"type":"string"},"description":{"type":"string","maxLength":500}}},{"type":"null"}]},"submitButtonText":{"type":"string","minLength":1,"maxLength":60},"thankYouMessage":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}]},"thankYouRedirectUrl":{"anyOf":[{"type":"string","format":"uri"},{"type":"null"}]},"captureToListId":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["formId"]}},{"name":"forms__get","title":"Get: forms","annotations":{"title":"Get: forms","readOnlyHint":true,"destructiveHint":false},"description":"Fetch a form by id.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"formId":{"type":"string","minLength":1}},"required":["formId"]}},{"name":"forms__list","title":"List: forms","annotations":{"title":"List: forms","readOnlyHint":true,"destructiveHint":false},"description":"List all forms in this business.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"includeArchived":{"type":"boolean"}}}},{"name":"forms__list_submissions","title":"List submissions: forms","annotations":{"title":"List submissions: forms","readOnlyHint":true,"destructiveHint":false},"description":"List submissions for a form, newest first.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"formId":{"type":"string","minLength":1},"limit":{"type":"integer","minimum":1,"maximum":500}},"required":["formId"]}},{"name":"forms__get_embed_code","title":"Get embed code: forms","annotations":{"title":"Get embed code: forms","readOnlyHint":true,"destructiveHint":false},"description":"Generate the JS embed snippet and iframe alternative for a form. Returns both; the user picks whichever fits their site.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"formId":{"type":"string","minLength":1}},"required":["formId"]}},{"name":"crm__contacts__query","title":"Query: contacts","annotations":{"title":"Query: contacts","readOnlyHint":false,"destructiveHint":false},"description":"Query contacts with a structured filter DSL. Use this for complex 'show me contacts where X and Y but not Z' queries. Simpler cases prefer crm.contacts.search (free text) or crm.contacts.list (single filter).\n\nField reference forms:\n  email, firstName, ... = stock fields\n  custom:<key> = custom property (v1 routes through text sidecar)\n  tag:<name> = has-tag-with-name (op must be exists/not_exists)\n  has:open_deal | won_deal | task_open | company = relational predicates (op must be exists/not_exists)\n\nOperator availability depends on field kind: text → equals/contains/in/...; number → gt/lt/between; date → before/after/in_last_days; etc. Invalid pairings return a compile error.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"filter":{"type":"object","properties":{"or":{"minItems":1,"type":"array","items":{"type":"object","properties":{"and":{"minItems":1,"type":"array","items":{"type":"object","properties":{"field":{"type":"string","minLength":1,"description":"Stock field (email, firstName, lifecycleStage, ...), custom:<key>, tag:<name>, or has:<predicate> (open_deal/won_deal/task_open/company)"},"op":{"type":"string","enum":["equals","not_equals","contains","not_contains","starts_with","ends_with","in","not_in","has_any_of","has_all_of","gt","lt","gte","lte","between","before","after","in_last_days","is_empty","is_not_empty","exists","not_exists"],"description":"See crm.contacts.query description for op-vs-kind compatibility."},"value":{}},"required":["field","op"]}}},"required":["and"]},"description":"DNF: outer OR of AND groups. Two-level nesting max."}},"required":["or"]},"limit":{"type":"integer","minimum":1,"maximum":500},"offset":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["filter"]}},{"name":"ads__campaign__draft","title":"Draft: campaign","annotations":{"title":"Draft: campaign","readOnlyHint":false,"destructiveHint":false},"description":"Create a draft ad campaign on Meta (Facebook and Instagram) or Google, the only two platforms Plyto can actually launch and manage. Drafts are NOT live: they require user approval and a UI launch action before spending money. Use when the user asks to create, generate, or set up a new ad campaign.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120,"description":"Campaign name shown in dashboards."},"objective":{"type":"string","enum":["OUTCOME_SALES","OUTCOME_LEADS","OUTCOME_TRAFFIC","OUTCOME_AWARENESS","OUTCOME_ENGAGEMENT"],"description":"Campaign objective. OUTCOME_SALES is the typical default for paid conversion campaigns."},"dailyBudgetCents":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991,"description":"Daily budget in US cents (e.g. 5000 = $50/day)."},"audienceDescription":{"description":"Plain-text description of the target audience, translated into platform targeting at launch. Provide EITHER this OR contactListId.","type":"string","minLength":1,"maxLength":2000},"contactListId":{"description":"Target an existing CRM list instead of a described audience: at launch the list is pushed to Meta as a hashed-email Custom Audience (find ids via crm.lists.list). Provide EITHER this OR audienceDescription.","type":"string"},"locations":{"description":"Where the ads run. For a local business list the places, e.g. [\"Denver, Colorado\"] or [\"Mesa, Arizona\", \"Gilbert, Arizona\"]. Omitted, targeting defaults to the business's own country (from its profile, else United States), shown on the draft for the user to change: tell the user when the default applied. Writing the geography into audienceDescription does NOT target it.","maxItems":25,"type":"array","items":{"type":"string","minLength":1}},"radiusMiles":{"description":"Radius around each city, in miles. Typical local service business: 15 to 25.","type":"integer","minimum":1,"maximum":100},"platforms":{"default":["meta"],"description":"Platforms to draft a campaign on; one campaign row per platform.","minItems":1,"type":"array","items":{"type":"string","enum":["meta","google"]}}},"required":["name","objective","dailyBudgetCents"]}},{"name":"ads__campaign__list","title":"List: campaign","annotations":{"title":"List: campaign","readOnlyHint":true,"destructiveHint":false},"description":"List ad campaigns for the active business, optionally filtered by status and/or platform. Use when the user asks to see their campaigns, check status, or get an overview.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"description":"Filter by status. Omit to get all statuses.","type":"array","items":{"type":"string","enum":["draft","pending_launch","launching","live","paused","archived","failed"]}},"platforms":{"description":"Filter by platforms. Omit for all.","type":"array","items":{"type":"string","enum":["meta","google","linkedin"]}},"limit":{"default":20,"type":"integer","minimum":1,"maximum":50}}}},{"name":"ads__performance__summary","title":"Summary: performance","annotations":{"title":"Summary: performance","readOnlyHint":false,"destructiveHint":false},"description":"THE authoritative source for a campaign's spend, impressions, clicks and conversions: use this for any question about how much a campaign has spent or how it is performing. Defaults to last 7 days. Numbers are aggregated from daily snapshots written by the performance poller, and `asOf` says when those snapshots were last refreshed, so report figures as of that moment. Never compute spend by summing another report: ads.search_terms.list in particular is partial by design and its totals are a small fraction of real spend.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"Plyto campaign id (returned from ads.campaign.list)."},"dateRange":{"description":"Inclusive date range in YYYY-MM-DD. Defaults to the last 7 days.","type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["from","to"]}},"required":["campaignId"]}},{"name":"ads__performance__analyze","title":"Analyze: performance","annotations":{"title":"Analyze: performance","readOnlyHint":false,"destructiveHint":false},"description":"Analyze recent performance for the active business: surface ads/ad sets that aren't getting impressions, ones spending without converting, and concentration patterns where a small slice drives most results. Also returns CRM attribution per campaign: leads captured (via stamped UTMs), cost per lead, deals those leads became, won revenue, and ROAS. Use when the user asks 'how are my ads doing', 'what should I cut', 'which campaign actually makes money'.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"description":"Limit analysis to a single campaign. Omit to analyze the whole business.","type":"string"},"dateRange":{"description":"Inclusive date range in YYYY-MM-DD. Defaults to the last 14 days.","type":"object","properties":{"from":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"to":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["from","to"]}}}},{"name":"ads__creative__draft","title":"Draft: creative","annotations":{"title":"Draft: creative","readOnlyHint":false,"destructiveHint":false},"description":"Create a draft ad creative attached to an existing ad set. Combines an uploaded image with copy + CTA + landing URL. The creative goes live with the ad set when the campaign is launched. Use after ads.campaign.draft and after the user has uploaded at least one image. PLACEMENT SIZES: a campaign needs up to 4 image sizes: 1:1 (1080x1080, REQUIRED, default/fallback for Marketplace+right column+search), 4:5 (1080x1350, Instagram feed), 9:16 (1080x1920, Stories/Reels), 16:9 (1920x1080, Facebook feed ONLY, never Marketplace or right column). The square must always exist; omit landscape rather than crop it badly.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"adSetId":{"type":"string","minLength":1,"description":"ID of an existing ad set in this business (returned from ads.campaign.draft)."},"name":{"type":"string","minLength":1,"maxLength":120,"description":"Internal name shown in the dashboard."},"headline":{"type":"string","minLength":1,"maxLength":40,"description":"Short headline for the ad. Meta limits to ~40 chars."},"body":{"type":"string","minLength":1,"maxLength":500,"description":"Primary ad copy shown above the image. Keep it under 125 characters: Facebook hides the rest behind \"See more\" and Instagram shows even less. One idea, the hook first."},"description":{"description":"Optional secondary description shown under the headline.","type":"string","maxLength":125},"ctaType":{"default":"LEARN_MORE","description":"Call-to-action button type.","type":"string","enum":["LEARN_MORE","SHOP_NOW","SIGN_UP","GET_QUOTE","CONTACT_US","SUBSCRIBE","BOOK_TRAVEL","DOWNLOAD","GET_OFFER","APPLY_NOW"]},"destinationUrl":{"type":"string","format":"uri","description":"Where the ad clicks land (your website, landing page, etc.)."},"destinationSource":{"description":"Set to \"user\" ONLY when the owner typed this exact URL in this conversation. Otherwise omit: the link must be on the website on file or a Plyto-hosted page, and the tool refuses anything else.","type":"string","enum":["user"]},"creativeAssetId":{"type":"string","minLength":1,"description":"ID of an uploaded creative asset (image) for this business. Use ads.assets.list to find one."}},"required":["adSetId","name","headline","body","destinationUrl","creativeAssetId"]}},{"name":"ads__creative__generate_images","title":"Generate images: creative","annotations":{"title":"Generate images: creative","readOnlyHint":false,"destructiveHint":false},"description":"Generate AI ad photographs from a prompt + the business's brand profile, sized for Meta placements, and save them to the creative library. For a physical business the picture is the work or the product, and a REAL photo from the library (ads.creative.compose_from_photo) comes first when one exists; generate only when nothing real is on file. If every size fails quality review, call it once more with a simpler wordless brief (the tools or materials alone, no hands, no labeled equipment) before falling back to a designed graphic. PLACEMENT SIZES: 1:1 1080x1080 (REQUIRED default/fallback: Marketplace, right column, search), 4:5 1080x1350 (Instagram feed), 9:16 1080x1920 (Stories/Reels), 16:9 1920x1080 (Facebook feed ONLY, never Marketplace or right column). Defaults to the full four-size set. Charges AI credits (~15-20¢ per image). Takes up to a minute; returns asset ids. When generating FOR a campaign's existing ads, finish the job: pair each concept with the ad whose copy it matches and attach it with ads.creative.attach_image, one call per ad. Never leave images in the library and tell the user to attach them by hand.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"prompt":{"type":"string","minLength":5,"maxLength":2000,"description":"The scene, briefed like an art director, not a category label. Brand profile (name, industry, audience, voice, colors) is added automatically; don't repeat it.\n\nSHOW THE WORK OR THE RESULT. Name the actual service and show it happening: hands on the thing, the tool mid-use, the damaged part next to the repaired part. A buyer decides in half a second whether you do what they need, and they decide from the picture.\n\nNEVER BRIEF THESE. They are what a model reaches for when a brief is vague, and they say nothing: a handshake, a crew 'greeting' or 'meeting' a customer, a team lined up smiling at camera, arms folded in front of a van, a thumbs up, someone pointing at a clipboard, a family beaming in an empty living room. If your brief contains the words greeting, meeting, welcoming, consulting or discussing, rewrite it around the work itself.\n\nNO TEXT IN THE SCENE, EVER. Never brief words, labels, captions, numbers, logos, UI screens with readable text, or labeled diagrams/infographics ('a funnel with LEAD and DEAL stages'). Painted lettering always fails quality review — three failures per concept, then the image collapses to a plain template tile. The real headline and logo are composited in actual type afterward; pass them via the headline field.\n\nBE SPECIFIC AND TRUE. Match the exact service in the campaign: siding repair is a hand fitting a board, not a roof. Include the material, the weather, the time of day, the wear on the tools. Working clothes and real mess beat clean and posed, which reads as AI and gets distrusted.\n\nTRADES SAFETY. If the scene has height or hazard, brief the correct gear, because a contractor's ad showing unsafe work is a liability image: harness and rope on a pitched roof, a footed and tied-off ladder, gloves, eye protection, hard hat on a site.\n\nGOOD: \"A siding installer kneeling on a drop cloth, fitting a fresh fibre-cement board into a gap where storm-damaged siding was cut away, nail gun in hand, the old cracked boards stacked beside him, overcast morning light.\" BAD: \"A roofing crew greeting a homeowner.\""},"name":{"description":"Base name for the set shown in the library (e.g. \"Summer promo\"). Defaults to a prompt excerpt.","type":"string","minLength":1,"maxLength":60},"style":{"description":"Visual treatment. photo (default): photorealistic scene, best for trades and local services. graphic: bold designed flat graphic with shapes and brand color, best for SaaS and offers. illustration: hand-drawn feel. Only change from photo when the user asks for a designed/graphic/illustrated look.","type":"string","enum":["photo","illustration","graphic"]},"ratios":{"description":"Placement sizes to generate. Defaults to all four. 1:1 is required and always included.","type":"array","items":{"type":"string","enum":["1:1","4:5","9:16","16:9"]}},"adCreativeId":{"description":"A draft ad to attach the generated 1:1 to automatically. When generating for a campaign's ads, call once PER AD with each ad's id so every image lands on its ad without a separate attach step. Also supplies the composited headline when you don't pass one.","type":"string"},"headline":{"description":"Headline to composite onto the image in real type (with the real logo). ALWAYS pass the ad's headline: if generation falls back to the brand template, a tile without a headline is just a colored background.","type":"string","maxLength":90},"subline":{"description":"Smaller supporting line under the headline. Optional.","type":"string","maxLength":120}},"required":["prompt"]}},{"name":"ads__creative__compose_graphic","title":"Compose graphic: creative","annotations":{"title":"Compose graphic: creative","readOnlyHint":false,"destructiveHint":false},"description":"Compose a DESIGNED ad graphic deterministically: real type, the real logo, the real product image, proof from the claims on file. No AI image model, no credits. To EDIT an existing designed graphic, pass revisionOf (its asset id) plus only the fields that change: the rest is kept, the old set is archived and the ad using it switches to the new one. Layout \"complete\" is the finished ad and the default for software and for any business with a product screen or site capture; \"offer\" is a typographic poster for a pricing or free-trial offer. For a physical business (trades, restaurants, retail, fitness, physical products) the picture is a photograph of the work or the product: their own photo through ads.creative.compose_from_photo first, ads.creative.generate_images only when they have none. Every offer, credential, timeframe and proof in the words must trace to the brand profile, the website scan, or what the person said; the tool refuses the rest. All four placement sizes by default.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"revisionOf":{"description":"EDITING an existing designed graphic: the asset id of any size in the set (the library card names it). Everything not passed is kept from it: headline, accent phrase, mechanism line, tiles, button, note, annotation, hero, layout and name. The old set is archived out of the library and any ad using it switches to the new one, so a change never leaves two versions behind. Pass only the fields that change.","type":"string"},"headline":{"description":"The line set in real type: 3 to 8 words in the buyer's own words, with one specific. Required unless revisionOf is set.","type":"string","minLength":3,"maxLength":90},"accentPhrase":{"description":"complete layout: the phrase that ENDS the headline, set in the brand accent on its own line. \"Know which ads make money.\" with accentPhrase \"make money.\"","type":"string","maxLength":40},"subline":{"description":"The mechanism line under the headline: how it works, with the concrete nouns (services, platforms, city, season). Up to 12 words.","type":"string","maxLength":110},"proofTiles":{"description":"complete layout: up to three short proof tiles for the strip (\"8+ tools in one loop\", \"Licensed and insured\", \"12 years in Bend\"). Each must come from the brand profile's claims or value proposition, the website scan, or the person's own words; the tool refuses any it cannot find on file.","maxItems":3,"type":"array","items":{"type":"string","minLength":2,"maxLength":28}},"ctaLabel":{"description":"complete layout: the painted button text. Defaults to the attached ad's own call to action so the picture and the real button agree; never a second, different action.","type":"string","maxLength":24},"ctaNote":{"description":"complete layout: one short line under the button, only from facts on file (\"No credit card. Unlimited seats.\").","type":"string","maxLength":48},"annotation":{"description":"complete layout: two to four handwritten words with an arrow at the product (\"From clicks to revenue\"). A fact from the profile, never an offer or a number.","type":"string","maxLength":30},"platformMarks":{"description":"complete layout: draw the official Meta/Google marks in the platform chips. Defaults to yes on Meta campaigns and no on Google campaigns; leave unset.","type":"boolean"},"layout":{"description":"complete: the finished ad, and the layout for EVERY ad in a set unless the person asks for something else. Headline with an accent phrase, mechanism line, the REAL product or site image in a frame (found automatically: the newest product screen or site capture in the library, or pass heroAssetId), up to three proof tiles from the claims on file, the ad's button, the real logo, on a brand-tinted field with a dark band; with no product image it becomes a headline static. Variety across a set comes from the message, the accent phrase, the tiles and the annotation, never from switching one ad to a weaker layout. screenshot: an older plain composition; do not use it unless asked by name. offer: a flat typographic poster on the solid brand color; only when the person asks for a poster, or the campaign is a price offer and the library has no product image. A mention of a free plan in the body does not make an ad an offer poster. Icon lockups are retired.","type":"string","enum":["complete","screenshot","offer"]},"heroAssetId":{"description":"complete and screenshot layouts: a library image asset (a real product screenshot, the site capture, or a real photo) to frame as the hero. Use ads.assets.list to find one; never fabricate UI.","type":"string"},"name":{"type":"string","minLength":1,"maxLength":60},"ratios":{"type":"array","items":{"type":"string","enum":["1:1","4:5","9:16","16:9"]}},"adCreativeId":{"description":"Draft ad to auto-attach the 1:1 to. The set it replaces is archived out of the library.","type":"string"}}}},{"name":"ads__creative__compose_from_photo","title":"Compose from photo: creative","annotations":{"title":"Compose from photo: creative","readOnlyHint":false,"destructiveHint":false},"description":"Turn a REAL photo from the library into a finished ad set: the photo cropped for every placement size, the headline in real type, the real logo. No image model, no credits, nothing invented. THE FIRST CHOICE for a physical business whenever the owner has uploaded photos or the website scan saved some (ads.assets.list shows which are real): a real photo of the real place beats any generated scene. One call per photo; pass adCreativeId to attach the square to that ad. Generated images are refused here; those already are ads.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"assetId":{"type":"string","minLength":1,"description":"A REAL photo from the library (ads.assets.list): uploaded by the owner or saved from their website. Never a generated image."},"headline":{"type":"string","minLength":3,"maxLength":90,"description":"Composited in real type under the photo. Always the ad's headline."},"subline":{"type":"string","maxLength":110},"name":{"type":"string","minLength":1,"maxLength":60},"ratios":{"type":"array","items":{"type":"string","enum":["1:1","4:5","9:16","16:9"]}},"adCreativeId":{"description":"Draft ad to auto-attach the 1:1 to. One call per ad.","type":"string"}},"required":["assetId","headline"]}},{"name":"ads__creative__generate_video","title":"Generate video: creative","annotations":{"title":"Generate video: creative","readOnlyHint":false,"destructiveHint":false},"description":"Render a short video clip (4, 6 or 8 seconds, vertical 9:16 or wide 16:9) for Reels, Stories, TikTok-style and feed placements. Use for ANY request for a video, promo clip, reel or animated ad. It renders in the background for a few minutes; the finished clip lands in Creative studio (Marketing, Creative) and the person gets a notification. The clip shows a scene, never on-screen text or logos: headline and CTA live in the ad copy. People described generically in the brief are FINE (a plumber at work, a customer smiling at her phone, a barista); do not refuse or reframe a brief because it has a person in it. What is refused: real, named people and celebrities, copyrighted characters, and a STARTING IMAGE that contains a face.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"prompt":{"type":"string","minLength":8,"maxLength":1200,"description":"What happens in the clip: shot type, subject, action, setting, light. One continuous moment, one subject. Describe the SCENE only. No words, captions, logos or brand names in frame: the headline and CTA come from the ad copy."},"name":{"description":"Library name for the clip.","type":"string","maxLength":60},"aspectRatio":{"default":"9:16","description":"9:16 for Reels, Stories and TikTok-style placements. 16:9 for feed and YouTube.","type":"string","enum":["9:16","16:9"]},"durationSeconds":{"default":8,"description":"Clip length. 8 suits most ads; 4 for a single beat; 6 in between.","anyOf":[{"type":"number","const":4},{"type":"number","const":6},{"type":"number","const":8}]},"hook":{"description":"The one line shown over the opening seconds of the FINISHED ad. Pass it whenever the clip is for an ad (it almost always is): the moment the clip lands, Plyto composes the finished ad with this hook, the caption and the brand end card, and that is what the person sees. Leave it out only for raw footage someone asked for by name.","type":"string","maxLength":80},"caption":{"description":"One lower-third line for the clip in the finished ad.","type":"string","maxLength":90},"cta":{"description":"Button label on the closing brand card, e.g. \"Get a quote\", \"Start free\". Default \"Learn more\".","type":"string","maxLength":24},"engine":{"description":"Leave unset: the workspace default renders. Only when the person names the engine (a side-by-side of Veo against Gemini Omni Flash).","type":"string","enum":["veo","omni"]},"fromAssetId":{"description":"A library IMAGE to open on: a real photo of the place or product beats an invented scene. When the person attached photos, pick the clearest product or place shot YOURSELF and render; never ask them which. Only this starting image is face-checked: a photo with a person's face in it is refused, so pick one of the place, the work or the product.","type":"string"}},"required":["prompt"]}},{"name":"ads__creative__compose_video","title":"Compose video: creative","annotations":{"title":"Compose video: creative","readOnlyHint":false,"destructiveHint":false},"description":"Turn library clips and images into a FINISHED video ad: a hook line over the opening seconds, an optional caption per clip, and a closing brand card with the real logo and a button, at 9:16 for Reels, Stories and TikTok (or 16:9, 1:1). A raw clip from ads.creative.generate_video is footage, not an ad; call this once the clips have landed, before attaching video to a draft. Deterministic composition, no credits, about a minute in the background; the ad lands in Creative studio with a notification and can then go on a draft with ads.creative.attach_image. Every offer or proof in the words must trace to the brand profile, the website scan or what the person said.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"assetIds":{"description":"Library clips and images IN ORDER (1 to 4). Clips play up to 12 seconds each; images hold for a few seconds with a slow push-in. Omit to use the newest generated clips (up to 3) that are not already a finished ad.","minItems":1,"maxItems":4,"type":"array","items":{"type":"string","minLength":1}},"hook":{"type":"string","minLength":3,"maxLength":80,"description":"The one line shown large over the first two to three seconds. The reason to keep watching, in the customer's words: a pain, a promise or a question. Under 60 characters reads best."},"captions":{"description":"One short caption per clip, in the same order as assetIds (null to leave a clip clean). Lower third, one idea each.","maxItems":4,"type":"array","items":{"anyOf":[{"type":"string","maxLength":90},{"type":"null"}]}},"clipSeconds":{"description":"Trim every clip to this many seconds. Default: each clip's own length, up to 12.","type":"number","minimum":2,"maximum":12},"endHeadline":{"description":"Headline on the closing brand card. Default: the hook.","type":"string","maxLength":70},"endSubline":{"description":"Supporting line on the closing card. Default: the business's value proposition.","type":"string","maxLength":110},"cta":{"description":"Button label on the closing card, e.g. \"Get a quote\", \"Book now\", \"Start free\". Default \"Learn more\".","type":"string","maxLength":24},"noEndCard":{"description":"Skip the closing brand card.","type":"boolean"},"aspect":{"default":"9:16","description":"9:16 for Reels, Stories and TikTok (the default). 16:9 for feed and YouTube. 1:1 for square feed.","type":"string","enum":["9:16","16:9","1:1"]},"muteClips":{"description":"Drop the clips' own sound.","type":"boolean"},"name":{"description":"Library name for the finished ad.","type":"string","maxLength":60}},"required":["hook"]}},{"name":"ads__ads__list","title":"List: ads","annotations":{"title":"List: ads","readOnlyHint":true,"destructiveHint":false},"description":"List every ad on a campaign with its ID, grouped by ad group. Run this BEFORE ads.creative.update or ads.creative.delete: those tools take the ad's id, never its headline text. Also the honest way to answer 'what ads does this campaign have'.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The campaign whose ads to list."}},"required":["campaignId"]}},{"name":"ads__assets__list","title":"List: assets","annotations":{"title":"List: assets","readOnlyHint":true,"destructiveHint":false},"description":"List the creative library (images, videos) for the active business, with what each asset IS: role 'site-capture' is the real picture of their website the scan saved (the hero for ads.creative.compose_graphic layout screenshot on software), role 'reference' is a real photo of the business, source 'upload' is something the owner added, generated=true is art Plyto made. Use it to pick a heroAssetId, a fromAssetId, or an asset for ads.creative.attach_image.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"type":{"description":"Filter by asset type. Omit for all.","type":"string","enum":["image","video","audio"]}}}},{"name":"ads__assets__delete","title":"Delete: assets","annotations":{"title":"Delete: assets","readOnlyHint":false,"destructiveHint":false},"description":"Permanently delete an UNUSED image or video from the creative library (the file and its record). Refused when the asset — or any of its placement-size siblings (same name, different ratio) — is attached to an ad, because launch resolves sibling sizes by name. Use for library hygiene: failed or superseded generations that nothing references, e.g. after rebinding an ad to older art.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"assetId":{"type":"string","minLength":1,"description":"The creative asset to delete permanently."}},"required":["assetId"]}},{"name":"ads__campaign__update","title":"Update: campaign","annotations":{"title":"Update: campaign","readOnlyHint":false,"destructiveHint":false},"description":"Edit an existing ad campaign: name, objective, daily budget, plain-English audience, or Google search keywords. Draft campaigns are freely editable. For LIVE campaigns only the budget can change and an increase requires the user's explicit approval first (then pass confirmSpendIncrease: true). Never launches anything. The name is the campaign's name only: never fold an ad-group or intent name into it ('Campaign · Group'); new intent groups are ads.google.ad_groups.add on the draft's id.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The campaign to edit."},"name":{"type":"string","minLength":1,"maxLength":150},"objective":{"type":"string","enum":["OUTCOME_LEADS","OUTCOME_SALES","OUTCOME_TRAFFIC","OUTCOME_AWARENESS"]},"dailyBudgetDollars":{"type":"number","minimum":1,"maximum":10000},"audienceDescription":{"description":"Plain-English audience; replaces the first ad set's audience.","type":"string","maxLength":2000},"googleKeywords":{"description":"Search keywords (google campaigns only). Cover EVERY intent in the audience description: when it names competitors or 'alternatives to X', include those alternative/competitor keywords (e.g. 'gohighlevel alternative'), not just generic category terms.","maxItems":25,"type":"array","items":{"type":"string","minLength":1,"maxLength":80}},"adSetId":{"description":"Which ad group (ad set) the audience/keyword change targets. Campaigns can have several ad groups, each with its own keyword theme; ads.campaign.get lists them with ids. Defaults to the first ad group.","type":"string"},"confirmSpendIncrease":{"description":"REQUIRED true when raising the budget of a live campaign. Only set after the user explicitly approved the new amount in chat. Never state a live campaign's current budget from memory: call without this flag first, and the reply carries the figure the ad platform reports right now.","type":"boolean"}},"required":["campaignId"]}},{"name":"ads__campaign__locations__get","title":"Get: locations","annotations":{"title":"Get: locations","readOnlyHint":true,"destructiveHint":false},"description":"Where a campaign's ads show: the location targeting as the ad platform itself reports it for a live or paused campaign (Google: the places plus whether only people physically there see the ads; Meta: the places on every ad set), or what the draft holds. Use before changing locations, when someone asks where the ads run, and when clicks arrive from countries the business does not serve.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The campaign to inspect."}},"required":["campaignId"]}},{"name":"ads__campaign__changes","title":"Changes: campaign","annotations":{"title":"Changes: campaign","readOnlyHint":false,"destructiveHint":false},"description":"The change history of a campaign: every edit made through Plyto (budget, locations, keywords, bidding, status, ads), by whom and when, plus, for a live Google campaign, Google's own change log including edits made directly in Google Ads by a person. Use before explaining why results changed, before proposing a change someone may already have made, and whenever the current settings and an earlier conversation seem to disagree. Describe changes in words; never show the internal action names.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The campaign whose history to read."},"days":{"default":30,"description":"How far back to look. Google's own history covers 30 days at most.","type":"integer","minimum":1,"maximum":90}},"required":["campaignId"]}},{"name":"ads__landing_pages__build","title":"Build: landing_pages","annotations":{"title":"Build: landing_pages","readOnlyHint":false,"destructiveHint":false},"description":"Build a landing page for every ad group in a campaign (or one ad group): a Plyto-hosted page whose headline says the ad's promise back in the visitor's words, brand-grounded bullets, proof only from the claims on file, and the business's lead form. Draft campaigns: the ads' links are set to the pages and launch uses them. Live campaigns: the pages are built and nothing that is serving changes, so show the owner the links and ask before passing repointLiveAds. Idempotent per ad group. Charges a small amount of AI credits for the copy. Use when a campaign's ads would otherwise send people to a homepage, when the owner has no website, or when Google rates ad relevance or landing page experience below average.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The campaign whose ad groups get pages."},"adSetId":{"description":"One ad group only. Omit for every ad group in the campaign.","type":"string"},"repointLiveAds":{"description":"Move the SERVING ads of a live Google campaign onto the new pages. Leave this out unless the user has read the pages and said in this conversation to send their live traffic there. Building pages is free of consequence; this spends their real ad budget on a page they have not seen.","type":"boolean"}},"required":["campaignId"]}},{"name":"ads__landing_pages__list","title":"List: landing_pages","annotations":{"title":"List: landing_pages","readOnlyHint":true,"destructiveHint":false},"description":"The landing pages Plyto hosts for this business's ads, with their links, which campaign and ad group each belongs to, and how many people have viewed each. Use before building pages (to reuse), and when someone asks where an ad sends people.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"description":"Limit to one campaign. Omit for every page in the business.","type":"string"}}}},{"name":"ads__campaign__launch","title":"Launch: campaign","annotations":{"title":"Launch: campaign","readOnlyHint":false,"destructiveHint":false},"description":"Launch a DRAFT campaign on Facebook and Instagram or Google Ads, the same way the Run it button does: checks that the ad account, payment method, Page and lead capture are in place, then starts the launch in the background. Money moves after this, so call it once WITHOUT confirmLaunch to get the exact daily budget and what will run, put that to the user, and call again with confirmLaunch: true only after they said yes. The launch itself takes a minute or two; the campaign page shows progress and a failure posts to the conversation.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The DRAFT campaign to launch."},"confirmLaunch":{"description":"REQUIRED true to launch. Set it only after the user explicitly said to launch THIS campaign at the daily budget the first call reported. Never on the same turn the campaign was drafted.","type":"boolean"}},"required":["campaignId"]}},{"name":"ads__campaign__status__set","title":"Set: status","annotations":{"title":"Set: status","readOnlyHint":false,"destructiveHint":false},"description":"Pause a live campaign or resume a paused one on the ad platform, the same path as the page's pause and resume buttons (platform call, audit row, undoable). Pausing needs no confirmation and stops spend at once. Resuming restarts spend, so it needs confirmResume: true after the user asked. To pause a single ad rather than the campaign, use ads.ads.status.set if available, or tell the user the campaign-level option.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"A LIVE or PAUSED campaign."},"to":{"type":"string","enum":["paused","live"],"description":"\"paused\" stops spend on the platform; \"live\" resumes it."},"confirmResume":{"description":"REQUIRED true when to is \"live\": resuming restarts spend. Set only after the user explicitly asked to resume this campaign.","type":"boolean"}},"required":["campaignId","to"]}},{"name":"ads__campaign__locations__set","title":"Set: locations","annotations":{"title":"Set: locations","readOnlyHint":false,"destructiveHint":false},"description":"Set or replace the location targeting on a campaign, DRAFT or LIVE: plain place names, applied to every ad group, plus (Google) whether the ads reach only people physically in those places. Use when the checklist warns about missing locations, when targeting defaulted to the business's country and the user wants somewhere else, when clicks are arriving from countries the business does not serve, or whenever the user names where ads should run. Drafts: names resolve against the platform catalog when an account is connected, otherwise they are saved as pending and resolve at launch. Live campaigns: read the current targeting with ads.campaign.locations.get first, tell the user what changes (which places stop seeing the ads), and call with confirmLiveEdit: true once they have said so; every name must resolve or nothing changes.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The campaign whose targeting to set."},"locationNames":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string","minLength":1},"description":"Plain place names: cities, regions, or countries (\"United States\", \"Toronto\", \"England\"). Replaces the campaign's current locations on every ad group."},"radiusMiles":{"description":"Optional radius around each place, for local businesses.","anyOf":[{"type":"number","minimum":1,"maximum":500},{"type":"null"}]},"presence":{"description":"Google only. \"presence\" = people in or regularly in the places (the right choice almost always: it stops searchers in other countries seeing the ads). \"presence_or_interest\" is Google's default and also reaches anyone anywhere who shows interest in the place. Leave unset to keep the campaign's current setting.","type":"string","enum":["presence","presence_or_interest"]},"confirmLiveEdit":{"description":"REQUIRED true for a LIVE or paused campaign: the change reaches Google or Meta immediately. Set true only after the user explicitly asked to change where this campaign runs.","type":"boolean"}},"required":["campaignId","locationNames"]}},{"name":"ads__creative__add","title":"Add: creative","annotations":{"title":"Add: creative","readOnlyHint":false,"destructiveHint":false},"description":"Add one ad (headline + primary text + CTA, plus a link except on a Meta leads campaign, where the instant form is the destination and no link is needed) to a campaign's ad set. On a DRAFT it is saved and launches with the campaign. On a LIVE Google campaign it publishes a new responsive search ad into the ad group immediately, an extra variant competing alongside the existing ads, no budget change. Live Meta campaigns can't take new ads yet. Write copy in the brand voice; keep Google headlines ≤30 chars and descriptions ≤90 chars; on Meta keep the primary text under 125 characters (Facebook hides the rest behind \"See more\") and the headline under 40. Call once per ad variation.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1},"headline":{"type":"string","minLength":1,"maxLength":255},"body":{"type":"string","minLength":1,"maxLength":2000},"ctaType":{"default":"LEARN_MORE","type":"string","enum":["LEARN_MORE","SIGN_UP","GET_QUOTE","SHOP_NOW","CONTACT_US"]},"destinationUrl":{"type":"string","maxLength":1000},"destinationSource":{"description":"Set to \"user\" ONLY when the owner typed this exact URL in this conversation. Otherwise omit: the link must be on the website on file or a Plyto-hosted page, and the tool refuses anything else.","type":"string","enum":["user"]},"adGroupName":{"description":"For campaigns with several ad groups: a fragment of the ad group's name to add the ad into. Defaults to the first ad group.","type":"string","maxLength":200},"confirmLiveEdit":{"description":"Required to publish an ad into a LIVE Google campaign (it reaches Google immediately). Omit for drafts. Set true only after the user explicitly asked to edit that specific live campaign.","type":"boolean"}},"required":["campaignId","headline","body"]}},{"name":"ads__creative__attach_image","title":"Attach image: creative","annotations":{"title":"Attach image: creative","readOnlyHint":false,"destructiveHint":false},"description":"Attach a creative-library image OR video clip to a draft ad (sets it as the ad's media; for image sets, launch resolves the other placement sizes by the asset's name-siblings; a video clip launches as a Meta video ad that plays in Reels, Stories and the feed). Use right after ads.creative.generate_images or once a generated clip has landed, pairing each asset with the ad whose copy it matches, one call per ad. Only draft ads; live ads change through the app.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"adCreativeId":{"type":"string","minLength":1,"description":"The ad to attach the image to (from the campaign's ads list)."},"assetId":{"type":"string","minLength":1,"description":"The creative-library image or video clip to attach (from ads.creative.generate_images, ads.creative.generate_video or the library)."}},"required":["adCreativeId","assetId"]}},{"name":"ads__audience__list","title":"List: audience","annotations":{"title":"List: audience","readOnlyHint":true,"destructiveHint":false},"description":"List the Meta custom audiences and lookalikes on the connected ad account, with their approximate sizes. Use when the user asks what audiences they have, or before targeting or building one.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"refresh":{"default":false,"description":"Re-pull from Meta instead of using the cached list. Use when the user just created an audience elsewhere.","type":"boolean"}}}},{"name":"ads__audience__create","title":"Create: audience","annotations":{"title":"Create: audience","readOnlyHint":false,"destructiveHint":false},"description":"Create a Meta audience for the business: either a custom audience from one of their Plyto contact lists, or a lookalike of an audience they already have. Use when the user wants to target their customers, or reach 'people like' their best customers. Creating an audience spends nothing and changes no running campaign, but it does send their customer list to Meta, which is not reversible by deleting the audience afterwards.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"userAskedToCreate":{"type":"boolean","description":"True only if the user explicitly asked you to build this audience on Meta. Discussing who to target, or being asked what audiences exist, is not asking you to create one. If you are unsure, it is false: ask them first."},"name":{"type":"string","minLength":2,"maxLength":100,"description":"Name for the new audience, as it will appear in Meta."},"fromListId":{"description":"Build a custom audience from this Plyto contact list (emails are hashed before upload). Use crm.list.* tools to find the id.","type":"string"},"lookalikeOfAudienceId":{"description":"Build a lookalike seeded from this existing Meta audience id (from ads.audience.list).","type":"string"},"lookalikePercent":{"default":1,"description":"Lookalike breadth as a percent of the country's population. 1 is the closest match and usually performs best; 10 is broadest.","type":"number","minimum":1,"maximum":10},"country":{"default":"US","description":"Two-letter country code the lookalike is built in.","type":"string","minLength":2,"maxLength":2}},"required":["userAskedToCreate","name"]}},{"name":"ads__audience__interests","title":"Interests: audience","annotations":{"title":"Interests: audience","readOnlyHint":false,"destructiveHint":false},"description":"Search Meta's interest and behavior catalog for detailed targeting, with approximate audience sizes. Use when the user wants to target people by interest, or asks what interests they could target. Returns ids that can be attached to an ad set.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"query":{"type":"string","minLength":2,"maxLength":80,"description":"What to search for, e.g. 'home improvement' or 'yoga'."},"limit":{"default":10,"type":"integer","minimum":1,"maximum":25}},"required":["query"]}},{"name":"ads__search_terms__list","title":"List: search_terms","annotations":{"title":"List: search_terms","readOnlyHint":true,"destructiveHint":false},"description":"Show what people actually searched for before seeing the business's Google ads, with spend, clicks and conversions per term, and which terms are wasting money (spend with zero conversions). Use when asked where the ad budget is going, why costs are high, or what to block. This report is PARTIAL: Google lags and hides low-volume terms, so its spend figures are a floor and must never be reported as the campaign's total spend. Get real spend from ads.performance.summary. IMPORTANT when reporting: each term is a query a stranger typed into Google. Describe it as 'someone searching for X', never as a customer, company, or signup name.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"description":"Limit to one Plyto campaign. Omit for the whole account.","type":"string"},"days":{"default":30,"description":"Reporting window.","anyOf":[{"type":"number","const":7},{"type":"number","const":14},{"type":"number","const":30}]},"onlyWasteful":{"default":false,"description":"Return just the zero-conversion terms worth blocking, instead of everything.","type":"boolean"}}}},{"name":"ads__negative_keywords__add","title":"Add: negative_keywords","annotations":{"title":"Add: negative_keywords","readOnlyHint":false,"destructiveHint":false},"description":"Block search terms on a Google campaign by adding them as negative keywords, so the ads stop showing for that traffic. Use after finding wasteful terms. This lowers wasted spend and never raises a budget.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","description":"The Plyto campaign to block them on."},"terms":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":2,"maxLength":80},"description":"Search terms to stop showing ads for."},"matchType":{"default":"BROAD","description":"BROAD blocks the whole family of that phrasing (right for junk traffic); EXACT blocks only that precise spelling.","type":"string","enum":["BROAD","PHRASE","EXACT"]}},"required":["campaignId","terms"]}},{"name":"ads__negative_keywords__list","title":"List: negative_keywords","annotations":{"title":"List: negative_keywords","readOnlyHint":true,"destructiveHint":false},"description":"List the negative keywords already blocking traffic on a Google campaign. Use before adding more, so nothing is added twice.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","description":"The Plyto campaign to read."}},"required":["campaignId"]}},{"name":"ads__delivery__diagnose","title":"Diagnose: delivery","annotations":{"title":"Diagnose: delivery","readOnlyHint":false,"destructiveHint":false},"description":"Explain why live campaigns are or aren't delivering: learning phase, budget too small to enter the auction, rejected ads, audience too narrow, payment problems. Use whenever the user asks why a campaign has low or zero spend, impressions, or results.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"description":"Limit to one campaign. Omit to check every live campaign.","type":"string"}}}},{"name":"ads__google_audience__list","title":"List: google_audience","annotations":{"title":"List: google_audience","readOnlyHint":true,"destructiveHint":false},"description":"List the Customer Match and remarketing audiences on the connected Google Ads account, with their sizes. Use when the user asks what Google audiences they have or before targeting one.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"ads__google_audience__create","title":"Create: google_audience","annotations":{"title":"Create: google_audience","readOnlyHint":false,"destructiveHint":false},"description":"TEMPORARILY UNAVAILABLE: Google approved Plyto for conversion reporting, and approval for customer list uploads is still pending, so this tool currently refuses with an explanation. When a user asks for a Google audience, say that plainly and offer keyword targeting on Google or the same audience on Meta instead. Normally: upload a Plyto contact list to Google as a Customer Match audience. Emails are hashed before they leave Plyto, but the upload itself cannot be taken back.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"userAskedToCreate":{"type":"boolean","description":"True only if the user explicitly asked you to upload this list to Google as an audience. Discussing targeting, or listing existing audiences, is not asking you to upload anyone. If you are unsure, it is false: ask them first."},"listId":{"type":"string","description":"Plyto contact list to push to Google as an audience."},"name":{"description":"Name for the audience in Google Ads.","type":"string","maxLength":80}},"required":["userAskedToCreate","listId"]}},{"name":"ads__lead_form__list","title":"List: lead_form","annotations":{"title":"List: lead_form","readOnlyHint":true,"destructiveHint":false},"description":"List the Facebook instant forms (lead forms) on the connected page, with how many leads each has collected. Use when the user asks about their lead forms or before pointing a campaign at one.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"ads__lead_form__create","title":"Create: lead_form","annotations":{"title":"Create: lead_form","readOnlyHint":false,"destructiveHint":false},"description":"Create a real instant form on the business's Facebook page. It becomes a live asset on their page that collects strangers' names, emails and phone numbers, and Facebook does not let you delete forms that have submissions. Submissions land in Contacts automatically.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"userAskedToCreate":{"type":"boolean","description":"True only if the user explicitly asked you to create this lead form on their Facebook page. Discussing form fields, or drafting one, is not asking you to create it on the page. If you are unsure, it is false: ask them first."},"name":{"type":"string","minLength":1,"maxLength":80,"description":"Internal name for the form."},"headline":{"type":"string","minLength":1,"maxLength":60,"description":"Shown at the top of the form, e.g. 'Get a free roof estimate'."},"description":{"description":"One short supporting line under the headline.","type":"string","maxLength":80},"fields":{"default":["FULL_NAME","PHONE"],"description":"Fields to ask for beyond email, which is always included. Every extra field costs submissions.","type":"array","items":{"type":"string","enum":["FULL_NAME","FIRST_NAME","LAST_NAME","PHONE","COMPANY_NAME","CITY","JOB_TITLE"]}},"websiteUrl":{"type":"string","format":"uri","description":"Where the thank-you screen sends people."},"privacyPolicyUrl":{"type":"string","format":"uri","description":"Required by Meta. Use the business website if there's no policy page."},"quality":{"default":"HIGHER_VOLUME","description":"HIGHER_INTENT adds a review step: fewer leads, better ones. Suggest it for high-ticket services.","type":"string","enum":["HIGHER_VOLUME","HIGHER_INTENT"]}},"required":["userAskedToCreate","name","headline","websiteUrl","privacyPolicyUrl"]}},{"name":"ads__google__conversion_tracking__repair","title":"Repair: conversion_tracking","annotations":{"title":"Repair: conversion_tracking","readOnlyHint":false,"destructiveHint":false},"description":"Set up Google conversion tracking for a business that is missing it, and move live Google campaigns from bidding on clicks to bidding on conversions. Use when a campaign launched without conversion tracking, or when the campaign page reports that setup failed. Changes how the campaign bids; it does not change any budget.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"description":"Limit the repair to one campaign. Omit to fix every live Google campaign on this business.","type":"string"}}}},{"name":"ads__destinations__sync","title":"Sync: destinations","annotations":{"title":"Sync: destinations","readOnlyHint":false,"destructiveHint":false},"description":"Push the landing page URLs shown in Plyto to the live ads on Google, for a campaign whose destinations were changed after launch. Use when ads are sending traffic somewhere other than what the campaign page shows. Changes where clicks land; it does not change budget or spend.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","description":"The live Google campaign to sync."}},"required":["campaignId"]}},{"name":"ads__google__search_partners__set","title":"Set: search_partners","annotations":{"title":"Set: search_partners","readOnlyHint":false,"destructiveHint":false},"description":"Turn Google Search Partners on or off for a live campaign. Partner sites deliver cheap clicks with high click-through rates and far worse conversion than Google Search, so turning them off is the standard fix when a campaign shows an implausible click-through rate and no results. Changes where ads show; it does not change budget.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","description":"The live Google campaign to change."},"enabled":{"type":"boolean","description":"false turns Google's partner sites off, which is the usual fix for cheap clicks that never convert. true opts back in."}},"required":["campaignId","enabled"]}},{"name":"ads__google__bidding__set","title":"Set: bidding","annotations":{"title":"Set: bidding","readOnlyHint":false,"destructiveHint":false},"description":"Switch a live Google campaign between clicks bidding and conversions bidding. Use maximize_clicks when a campaign shows zero or near-zero impressions on Google Search because it is optimizing toward conversions that never get recorded. Spends the same daily budget either way; does not raise it.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","description":"The live Google campaign to change."},"mode":{"type":"string","enum":["maximize_clicks","maximize_conversions"],"description":"maximize_clicks buys as many clicks as the budget allows, capped per click; the right mode while conversion tracking has no data. maximize_conversions hands bidding to Google's conversion signal; only sensible once conversions are actually being recorded."},"maxCpcDollars":{"description":"Ceiling per click in dollars for maximize_clicks, defaulting to 4. Ignored for maximize_conversions.","type":"number","minimum":0.5,"maximum":50}},"required":["campaignId","mode"]}},{"name":"ads__google__ads__strengthen","title":"Strengthen: ads","annotations":{"title":"Strengthen: ads","readOnlyHint":false,"destructiveHint":false},"description":"Fill every live responsive search ad in a Google campaign up to the full 15-headline / 4-description asset counts, keeping all existing lines and pins and adding brand and ad-group-themed lines. Use when Google rates the ads' Ad strength Poor: thin asset lists cap Ad strength, and Poor strength costs auctions regardless of bid. Existing copy is never removed. Honest caveats to relay: Google briefly re-reviews edited ads, the ads' learning history resets, and the Ad strength label can take hours to recompute.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","description":"The live Google campaign whose ads to strengthen."}},"required":["campaignId"]}},{"name":"ads__creative__update","title":"Update: creative","annotations":{"title":"Update: creative","readOnlyHint":false,"destructiveHint":false},"description":"Edit an existing ad's headline, description, or destination link. Drafts update in place. A live Google search ad gets a replacement version published and the old one paused, because Google does not allow editing published ad copy; destination-only changes update in place. Live Meta ads cannot be edited yet.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"creativeId":{"type":"string","description":"The ad (creative) to edit."},"headline":{"description":"New headline. Google search ads fit 30 characters.","type":"string"},"body":{"description":"New description (Google, 90 characters) or primary text (Meta).","type":"string"},"destinationUrl":{"description":"New landing page link, starting with https://","type":"string"}},"required":["creativeId"]}},{"name":"ads__creative__delete","title":"Delete: creative","annotations":{"title":"Delete: creative","readOnlyHint":false,"destructiveHint":false},"description":"Delete a DRAFT ad outright (its images stay in the library), or PAUSE a live Google search ad (published ads can't be deleted, but a paused ad stops serving immediately). Use when a pre-launch check flags an ad the user wants gone — a trademarked competitor name in a headline, off-brand copy — instead of leaving a flagged ad you cannot clear. Live Meta ads can't be changed yet.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"creativeId":{"type":"string","minLength":1,"description":"The ad (creative) to delete."}},"required":["creativeId"]}},{"name":"ads__funnel__test","title":"Test: funnel","annotations":{"title":"Test: funnel","readOnlyHint":false,"destructiveHint":false},"description":"Send a tagged test lead through a campaign's whole funnel: landing page, form, CRM contact, conversion attribution, and a dry-run Google upload check. The test lead uses an undeliverable address, is removed afterwards, and no ads or budgets change. Use when someone asks whether their tracking or funnel actually works.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","description":"The live campaign whose funnel to test."}},"required":["campaignId"]}},{"name":"ads__google__keywords__add","title":"Add: keywords","annotations":{"title":"Add: keywords","readOnlyHint":false,"destructiveHint":false},"description":"Add keywords to a live Google campaign's ad group, phrase match by default. Use to feed phrasing families that are converting, for example when winning search terms are not yet covered by the keyword list. Does not change budget; new keywords compete inside the existing daily cap.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","description":"The live Google campaign."},"adGroupName":{"type":"string","description":"Which ad group to add to, matched by name fragment."},"keywords":{"minItems":1,"maxItems":30,"type":"array","items":{"type":"string"},"description":"Keywords to add. Plain text becomes phrase match."}},"required":["campaignId","adGroupName","keywords"]}},{"name":"ads__google__ads__add_landing_variant","title":"Add landing variant: ads","annotations":{"title":"Add landing variant: ads","readOnlyHint":false,"destructiveHint":false},"description":"Start a landing page A/B test on the live Google campaign. In ad groups with room it adds a copy of the strongest ad pointing at the new URL; in groups already at Google's 3-ad cap it repoints the weakest ad instead, keeping the strongest on the incumbent page. Google's ad rotation splits the traffic and attribution records which page each signup came through. Skips ad groups that already point at that page, so it is safe to run twice. Does not change budget, bidding, or keywords.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"variantUrl":{"type":"string","format":"uri","description":"Full landing page URL for the B side, e.g. https://plyto.ai/go/new-era"},"adGroupNameContains":{"description":"Only add the variant ad to ad groups whose name contains this text (case-insensitive). Omit to cover every enabled ad group.","type":"string","maxLength":80}},"required":["variantUrl"]}},{"name":"ads__query_pages__build","title":"Build: query_pages","annotations":{"title":"Build: query_pages","readOnlyHint":false,"destructiveHint":false},"description":"The query-to-page engine: cluster what people actually searched on a live Google campaign, generate one landing page per intent cluster (headline that speaks the search back, brand-grounded copy, the business's live form embedded), and point variant ads at each page. Charges AI credits for the copy. Returns each page's URL and how many ads were routed. Use when search terms show distinct intents landing on one generic page.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"A LIVE Google campaign with recorded search terms."},"maxPages":{"default":3,"type":"integer","minimum":1,"maximum":5}},"required":["campaignId"]}},{"name":"ads__google__ad_groups__add","title":"Add: ad_groups","annotations":{"title":"Add: ad_groups","readOnlyHint":false,"destructiveHint":false},"description":"Add a complete search ad group (keywords phrase-match by default, RSA copy, a message-matched landing page) to a SPECIFIC Google campaign by id. Pass 8-12 VARIED headlines and 3-4 distinct descriptions: launch pools them (plus title-cased keywords) into one responsive search ad, and thin or repetitive lists rate Poor on Ad strength, which loses auctions regardless of bid. On a DRAFT campaign this creates draft rows only, reviewable and launchable later; nothing reaches Google. On a LIVE campaign it writes to Google immediately and is refused unless confirmLiveEdit is true, which you may set only after the user explicitly asked to edit that live campaign. While building a new campaign, always pass the new draft's id. Errors instead of duplicating when a same-named group already exists.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"REQUIRED: the exact campaign this group belongs to. A draft id adds a draft group (nothing touches Google until launch); a live id is refused unless confirmLiveEdit is set."},"confirmLiveEdit":{"description":"Set true ONLY when the user explicitly asked to edit this specific LIVE campaign. Never set it while building a new campaign.","type":"boolean"},"name":{"type":"string","minLength":2,"maxLength":60,"description":"Short intent-family name, e.g. 'Agencies'. The campaign name is prefixed automatically."},"keywords":{"minItems":2,"maxItems":20,"type":"array","items":{"type":"string","minLength":2,"maxLength":80},"description":"Search keywords for this intent. Bare strings become phrase match; [brackets] force exact."},"landingUrl":{"type":"string","format":"uri","description":"The message-matched landing page for this intent: a page on the website on file or a Plyto-hosted page. Never a guessed domain."},"destinationSource":{"description":"Set to \"user\" ONLY when the owner typed this exact URL in this conversation. Otherwise omit: the link must be on the website on file or a Plyto-hosted page, and the tool refuses anything else.","type":"string","enum":["user"]},"headlines":{"minItems":3,"maxItems":15,"type":"array","items":{"type":"string","minLength":3,"maxLength":30},"description":"RSA headlines, 30 characters max each. Aim for 10 or more so Ad strength starts healthy."},"descriptions":{"minItems":2,"maxItems":4,"type":"array","items":{"type":"string","minLength":10,"maxLength":90},"description":"RSA descriptions, 90 characters max each."}},"required":["campaignId","name","keywords","landingUrl","headlines","descriptions"]}},{"name":"ads__google__ad_groups__remove","title":"Remove: ad_groups","annotations":{"title":"Remove: ad_groups","readOnlyHint":false,"destructiveHint":false},"description":"Remove an ad group you (or the user) no longer want. Draft groups are deleted outright along with their draft ads. Groups on a LIVE campaign are PAUSED in Google instead: delivery stops immediately, history is kept, and the pause is reversible in Google Ads. Use this to clean up groups created by mistake.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The campaign the ad group belongs to."},"adSetId":{"type":"string","minLength":1,"description":"The ad group's id (from the campaign page or ads.campaign.list)."}},"required":["campaignId","adSetId"]}},{"name":"ads__campaign__delete","title":"Delete: campaign","annotations":{"title":"Delete: campaign","readOnlyHint":false,"destructiveHint":false},"description":"Delete a campaign that never launched (draft or failed): the campaign, its ad groups, and its draft ads are removed permanently; images stay in the creative library. A campaign that has reached the ad platform is archived instead of deleted, and a LIVE campaign is refused until it is paused. Two-step: first call returns a summary to confirm with the user; call again with confirm:true to delete (same as the two-step Remove in the UI).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The draft campaign to delete."},"confirm":{"description":"Must be true to actually delete. Call WITHOUT it first: the tool returns what would be deleted so you can confirm with the user. Set true only after the user has approved deleting THIS campaign by name in this conversation.","type":"boolean"}},"required":["campaignId"]}},{"name":"ads__google__extensions__generate","title":"Generate: extensions","annotations":{"title":"Generate: extensions","readOnlyHint":false,"destructiveHint":false},"description":"Draft sitelinks, callouts, and a structured snippet for a DRAFT Google campaign from the brand profile and the campaign's own keywords, and save them onto the draft. They are applied to Google at launch. URL VERIFICATION IS BUILT IN: sitelink destinations are chosen from the site's own sitemap and each is HEAD-checked live (HTTP 200) before saving, so NEVER refuse to run this out of doubt about URLs, and never leave callouts empty over URL concerns (callouts have no URLs at all). Run this on every Google draft you build; a Search campaign without sitelinks and callouts loses ad real estate to competitors who have them. Re-running replaces the previous draft set. Refuses live campaigns (extensions ship at launch).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1,"description":"The DRAFT Google campaign to draft extensions for."},"landingUrl":{"description":"Base URL for sitelinks: the website on file or a Plyto-hosted page. Defaults to the campaign's ad destination. Never a guessed domain.","type":"string","format":"uri"},"destinationSource":{"description":"Set to \"user\" ONLY when the owner typed this exact URL in this conversation. Otherwise omit: the link must be on the website on file or a Plyto-hosted page, and the tool refuses anything else.","type":"string","enum":["user"]},"sitelinks":{"description":"EXPLICIT sitelinks that override generation. Pass these whenever the user names specific pages: each URL is still live-verified, and dead ones are dropped with a note. Omit to let Plyto choose from the site's sitemap.","minItems":2,"maxItems":8,"type":"array","items":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":25,"description":"Link text, max 25 chars."},"url":{"type":"string","format":"uri","description":"Full URL on the business's site."}},"required":["text","url"]}},"callouts":{"description":"EXPLICIT callouts (max 25 chars each) that override generation, saved verbatim. Pass these to change callouts, or to pin the current ones unchanged during a sitelink edit. Omit to leave existing callouts untouched (sitelink-only write) or let Plyto draft them when none exist.","minItems":2,"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":25}}},"required":["campaignId"]}},{"name":"email__create_template","title":"Create template: email","annotations":{"title":"Create template: email","readOnlyHint":false,"destructiveHint":false},"description":"Create a reusable email template. Body and subject are plain text and support merge tags: {{contact.firstName}}, {{contact.lastName}}, {{contact.fullName}}, {{contact.email}}, {{business.name}}, with fallbacks like {{contact.firstName|there}}.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"subject":{"type":"string","minLength":1,"maxLength":200},"body":{"type":"string","minLength":1,"maxLength":20000}},"required":["name","subject","body"]}},{"name":"email__list_templates","title":"List templates: email","annotations":{"title":"List templates: email","readOnlyHint":true,"destructiveHint":false},"description":"List email templates for this business.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"includeArchived":{"type":"boolean"}}}},{"name":"email__list_sends","title":"List sends: email","annotations":{"title":"List sends: email","readOnlyHint":true,"destructiveHint":false},"description":"List recent email sends, optionally filtered to one contact or template. Shows delivery status per send.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"contactId":{"type":"string"},"templateId":{"type":"string"},"limit":{"type":"integer","minimum":1,"maximum":200}}}},{"name":"email__get_send_stats","title":"Get send stats: email","annotations":{"title":"Get send stats: email","readOnlyHint":true,"destructiveHint":false},"description":"Aggregate send counts by status (queued/sent/delivered/opened/clicked/bounced/failed), optionally for one template and/or a recent window. Use to answer 'how did that email do?'","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"templateId":{"type":"string"},"sinceDays":{"type":"integer","minimum":1,"maximum":365}}}},{"name":"email__domain__status","title":"Status: domain","annotations":{"title":"Status: domain","readOnlyHint":false,"destructiveHint":false},"description":"Check the business's custom sending domain: current status, and when it is still pending, exactly which DNS records have not been found yet (record type, host name, and value to add). Calling this also asks the email provider to re-check DNS right now, so it doubles as a manual re-verify. Use whenever someone asks why their domain is pending, whether their domain is verified, or why email still sends from the shared Plyto address. Relay the missing records concretely: name the record type and host, never just say 'check your DNS'. A background check also runs every 30 minutes and emails the owner the moment verification passes.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"email__signature__set","title":"Set: signature","annotations":{"title":"Set: signature","readOnlyHint":false,"destructiveHint":false},"description":"Set or clear the CURRENT USER's personal email signature for this business (their Gmail-style sign-off, appended when they compose one-to-one emails from a contact page). ONE-TO-ONE ONLY: marketing campaigns and automation emails never include it, by design; their sign-off is part of the campaign content. Markdown works: bold, [links](https://url), image logos. Pass an empty string to clear back to the default sign-off. Only affects the person talking to you, never teammates. They can also edit it themselves under Settings, Email signature.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"signature":{"type":"string","maxLength":2000,"description":"The full signature block, e.g. 'Best,\\nRoss Gray\\nPlyto'. Empty string clears it."}},"required":["signature"]}},{"name":"email__campaign__list","title":"List: campaign","annotations":{"title":"List: campaign","readOnlyHint":true,"destructiveHint":false},"description":"List email marketing campaigns with status, type, subject, and audience size. Use to answer 'what campaigns do we have' or find a campaign to edit.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"status":{"type":"string","enum":["draft","scheduled","sending","sent","needs_attention","cancelled","archived"]},"limit":{"type":"integer","minimum":1,"maximum":100}}}},{"name":"email__campaign__get","title":"Get: campaign","annotations":{"title":"Get: campaign","readOnlyHint":true,"destructiveHint":false},"description":"Get one email campaign's full draft: subject, preview text, body, audience, schedule, A/B test setup and (once it has sent) its per-variant results and winner, plus the current eligible-recipient breakdown. Call before editing so changes are targeted, and call it to answer 'which version won'.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1}},"required":["campaignId"]}},{"name":"email__campaign__create","title":"Create: campaign","annotations":{"title":"Create: campaign","readOnlyHint":false,"destructiveHint":false},"description":"Create a DRAFT email marketing campaign (never sends anything). Write subject/preview/body in the brand voice; the user reviews in the composer and nothing goes out without an explicit send confirmation. Returns the campaign URL to share with the user.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"campaignType":{"default":"one_time_broadcast","type":"string","enum":["one_time_broadcast","newsletter","promotion","announcement","re_engagement","educational"]},"subject":{"type":"string","maxLength":160},"previewText":{"type":"string","maxLength":200},"bodyMd":{"description":"Markdown body. Merge tags: {{contact.firstName|there}}, {{business.name}}. The unsubscribe footer is added automatically at send.","type":"string","maxLength":20000},"contactListId":{"description":"Omit to target all subscribed contacts.","type":"string"}},"required":["name"]}},{"name":"email__campaign__update","title":"Update: campaign","annotations":{"title":"Update: campaign","readOnlyHint":false,"destructiveHint":false},"description":"Edit a draft or scheduled email campaign: name, subject, preview text, body, audience list, schedule, or A/B test. Plyto DOES support A/B testing on email campaigns: set abVariable plus the matching B-side copy and every recipient is assigned a side at send time, then the campaign page reports opens and clicks per variant and names a winner. Sent campaigns can't be edited. Changes appear instantly in the user's composer and are undoable from the activity log.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"campaignId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":120},"subject":{"type":"string","maxLength":160},"previewText":{"type":"string","maxLength":200},"bodyMd":{"type":"string","maxLength":20000},"contactListId":{"description":"A list id, or \"\" to target all subscribed contacts.","type":"string"},"scheduleType":{"type":"string","enum":["send_now","scheduled"]},"scheduledAt":{"description":"ISO 8601; required when scheduleType is scheduled.","type":"string"},"abVariable":{"description":"Run an A/B test on one variable. \"subject\" tests subjectB against subject, \"body\" tests bodyMdB against bodyMd, \"off\" ends the test and sends the A side to everyone. One variable at a time: testing two at once tells you nothing about which one moved the number.","type":"string","enum":["subject","body","off"]},"subjectB":{"description":"The B-side subject line. Required when abVariable is subject.","type":"string","maxLength":160},"bodyMdB":{"description":"The B-side body. Required when abVariable is body.","type":"string","maxLength":20000},"abSplitPercent":{"description":"Percentage of the audience that receives the B side. Defaults to 50 for a straight split. Below 50 holds B back as the challenger.","type":"integer","minimum":5,"maximum":50}},"required":["campaignId"]}},{"name":"automations__create","title":"Create: automations","annotations":{"title":"Create: automations","readOnlyHint":false,"destructiveHint":false},"description":"Create an automation: a trigger + a linear chain of steps. Created in DRAFT and it stays that way. Do NOT activate it as part of building it: tell the user what it does and let them ask for it to be turned on. Steps run in order; a branch step exits contacts that don't match (its false path can be rewired later via automations.connect_steps). Example: trigger form.submitted → delay 1 day → action email.send (welcome) → wait_for_event email.opened (3 days) → branch (opened?) → action crm.tasks.create. WIN-BACKS: when the business takes bookings through Plyto, use trigger booking.lapsed (fires once when a contact's last confirmed booking passes 60 days with nothing upcoming); only fall back to a tag-based trigger when they don't use Plyto booking, and say plainly that someone must apply the tag. AUDIENCE CHECK before creating: the trigger defines who enters. contact.created = every new contact (ad leads, imports, form fills), NOT just signups; when the user means a narrower group, set trigger.filterCriteria (e.g. source equals signup) or lead with a branch, and tell the user exactly who will enter. SKIP TAGS MUST HAVE WRITERS: in Plyto's own house business ONLY, the platform writes these tags automatically: google-ads-connected, meta-connected, stripe-connected, imported-contacts, card-on-file, and activated (set when the person launches a campaign, activates an automation, or publishes a site). In every other business NO tag is platform-written. A skipIf on a tag nothing writes is inert until a human applies it, so either use a tag one of this business's own automations/forms writes, or tell the user plainly that they must apply your chosen tag by hand and when. NUDGE STEPS: any step whose copy assumes inaction ('still there?', 'if you haven't launched yet', 'just checking in') MUST carry a skipIf on the signal that would make it wrong (a tag like activated, a property, a won deal), and you must tell the user which signal you chose so they know what to set. A customer who already acted and still gets 'if you haven't started yet' reads it as spam. JOURNEY SHAPE: use per-step skipIf and automation-level exitCriteria so sequences respect what contacts already did (tags they gained, fields that changed): a nudge someone already acted on should skip, and a goal reached mid-sequence should end the journey.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"description":{"type":"string","maxLength":500},"trigger":{"type":"object","properties":{"eventType":{"type":"string","enum":["contact.created","contact.updated","tag.added_to_contact","deal.created","deal.stage_changed","deal.won","deal.lost","task.completed","form.submitted","list.joined","email.sent","email.opened","email.clicked","email.bounced","ad_conversion.created","booking.lapsed","property_value.set","stripe.customer_created","stripe.subscription_started","stripe.payment_failed","stripe.subscription_past_due","stripe.payment_recovered","stripe.subscription_canceled"]},"reEnrollmentPolicy":{"default":"once","description":"once = a contact can only ever enter this automation one time.","type":"string","enum":["once","every_match"]},"suppressionDays":{"description":"With every_match: don't re-enroll the same contact within this many days of their last entry (guards against repeat-trigger storms).","type":"integer","minimum":1,"maximum":365},"filterCriteria":{"description":"Optional: only enroll contacts matching this filter at trigger time.","type":"object","properties":{"or":{"minItems":1,"type":"array","items":{"type":"object","properties":{"and":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","minLength":1},"op":{"type":"string","minLength":1},"value":{}},"required":["field","op"]}}},"required":["and"]}}},"required":["or"]}},"required":["eventType"]},"exitCriteria":{"description":"Journey-level early exit, checked before EVERY step: a contact who matches leaves the automation immediately. Use for goal completion: billing reminders should carry {or:[{and:[{field:\"tag:card-on-file\",op:\"exists\"}]}]} so paying stops the nagging mid-sequence.","type":"object","properties":{"or":{"minItems":1,"type":"array","items":{"type":"object","properties":{"and":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","minLength":1},"op":{"type":"string","minLength":1},"value":{}},"required":["field","op"]}}},"required":["and"]}}},"required":["or"]},"steps":{"minItems":1,"maxItems":20,"type":"array","items":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"action"},"toolName":{"type":"string","minLength":1,"description":"Any registered agent tool, e.g. email.send, crm.tasks.create, crm.tags.add_to_contact. In arguments, the string \"{{contact.id}}\" resolves to the enrolled contact's id at run time."},"arguments":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Arguments for the tool."},"skipIf":{"description":"Run-time skip condition: contacts matching this filter skip the step and continue down the chain. Use it to make journeys respect what already happened, e.g. skip a \"connect your ad account\" email for {or:[{and:[{field:\"tag:google-ads-connected\",op:\"exists\"}]}]}.","type":"object","properties":{"or":{"minItems":1,"type":"array","items":{"type":"object","properties":{"and":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","minLength":1},"op":{"type":"string","minLength":1},"value":{}},"required":["field","op"]}}},"required":["and"]}}},"required":["or"]}},"required":["kind","toolName","arguments"]},{"type":"object","properties":{"kind":{"type":"string","const":"delay"},"seconds":{"type":"integer","minimum":60,"maximum":7776000,"description":"Wait this long before the next step (60s minimum, 90 days max)."}},"required":["kind","seconds"]},{"type":"object","properties":{"kind":{"type":"string","const":"branch"},"filter":{"type":"object","properties":{"or":{"minItems":1,"type":"array","items":{"type":"object","properties":{"and":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","minLength":1},"op":{"type":"string","minLength":1},"value":{}},"required":["field","op"]}}},"required":["and"]}}},"required":["or"],"description":"W3 filter DSL evaluated against the enrolled contact. Match → continue to the next step; no match → the false path (or exit if none)."}},"required":["kind","filter"]},{"type":"object","properties":{"kind":{"type":"string","const":"wait_for_event"},"eventType":{"type":"string","enum":["contact.created","contact.updated","tag.added_to_contact","deal.created","deal.stage_changed","deal.won","deal.lost","task.completed","form.submitted","list.joined","email.sent","email.opened","email.clicked","email.bounced","ad_conversion.created","booking.lapsed","property_value.set","stripe.customer_created","stripe.subscription_started","stripe.payment_failed","stripe.subscription_past_due","stripe.payment_recovered","stripe.subscription_canceled"],"description":"Bus event to wait for, matched by the enrolled contact (e.g. email.opened)."},"timeoutSeconds":{"type":"integer","minimum":60,"maximum":7776000,"description":"Give up waiting after this long. Event received → next step; timeout → the false path if wired via connect_steps (slot branch_false), otherwise also the next step."}},"required":["kind","eventType","timeoutSeconds"]}]},"description":"Linear chain, executed in order."}},"required":["name","trigger","steps"]}},{"name":"automations__pause","title":"Pause: automations","annotations":{"title":"Pause: automations","readOnlyHint":false,"destructiveHint":false},"description":"Pause an active automation: no new enrollments. In-flight enrollments finish their current step and then hold (W7 executor checks status).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"automationId":{"type":"string","minLength":1}},"required":["automationId"]}},{"name":"automations__list","title":"List: automations","annotations":{"title":"List: automations","readOnlyHint":true,"destructiveHint":false},"description":"List automations with status and trigger.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"automations__get","title":"Get: automations","annotations":{"title":"Get: automations","readOnlyHint":true,"destructiveHint":false},"description":"Fetch one automation with its full step graph (ordered), for inspection or before editing.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"automationId":{"type":"string","minLength":1}},"required":["automationId"]}},{"name":"automations__connect_steps","title":"Connect steps: automations","annotations":{"title":"Connect steps: automations","readOnlyHint":false,"destructiveHint":false},"description":"Rewire the step graph: point a step's `next` or `branch_false` slot at another step (or null to make that path exit). Use after automations.get to see current wiring.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"automationId":{"type":"string","minLength":1},"fromStepId":{"type":"string","minLength":1},"toStepId":{"anyOf":[{"type":"string"},{"type":"null"}]},"slot":{"default":"next","type":"string","enum":["next","branch_false"]}},"required":["automationId","fromStepId","toStepId"]}},{"name":"automations__list_enrollments","title":"List enrollments: automations","annotations":{"title":"List enrollments: automations","readOnlyHint":true,"destructiveHint":false},"description":"List enrollments (who's in an automation and where they are), filterable by automation and state.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"automationId":{"type":"string"},"state":{"type":"string","enum":["active","completed","exited","failed"]},"limit":{"type":"integer","minimum":1,"maximum":200}}}},{"name":"automations__get_step_runs","title":"Get step runs: automations","annotations":{"title":"Get step runs: automations","readOnlyHint":true,"destructiveHint":false},"description":"Step-by-step execution log for one enrollment: what ran, when, with what outcome/error. The debugging tool for 'why didn't Jane get the email?'","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"enrollmentId":{"type":"string","minLength":1}},"required":["enrollmentId"]}},{"name":"automations__exit_contact","title":"Exit contact: automations","annotations":{"title":"Exit contact: automations","readOnlyHint":false,"destructiveHint":false},"description":"Force-exit an active enrollment: the contact stops at the next step boundary and receives nothing further from this automation. Use when someone asks to be left alone or was enrolled by mistake.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"enrollmentId":{"type":"string","minLength":1}},"required":["enrollmentId"]}},{"name":"automations__test_run","title":"Test run: automations","annotations":{"title":"Test run: automations","readOnlyHint":false,"destructiveHint":false},"description":"Static dry-run: validates every step of an automation WITHOUT executing anything: action tools exist and accept their arguments (with templates resolved against dummy values), delays/waits are in bounds, the graph reaches an exit. Run before activate; report problems to the user.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"automationId":{"type":"string","minLength":1}},"required":["automationId"]}},{"name":"automations__restore","title":"Restore: automations","annotations":{"title":"Restore: automations","readOnlyHint":false,"destructiveHint":false},"description":"Restore an archived automation. It comes back PAUSED (never auto-active). Activate it separately to resume enrolling. Previously-exited contacts are not re-enrolled.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"automationId":{"type":"string","minLength":1}},"required":["automationId"]}},{"name":"websites__create_site","title":"Create site: websites","annotations":{"title":"Create site: websites","readOnlyHint":false,"destructiveHint":false},"description":"Create a website (single-page, block-based). YOU compose the blocks from the brand profile. Write real, specific copy in the brand voice, never lorem ipsum. A strong default: hero, features (3 items), about, faq (3-4 questions, great for SEO/GEO), cta, contact_form. FACTS ONLY: never invent operational promises the owner has not made (response times, 'we text before we arrive', guarantees, service-area neighborhood lists) and never fabricate testimonials, review counts, or years in business; omit the testimonials block when no real quotes exist. CALENDAR AND AVAILABILITY claims are operational promises too, not seasonal color: never state booking windows or months ('get on the list in February', 'late March through early May is ideal'), never claim scarcity ('the calendar fills fast'), and never describe the service model ('we handle everything on our own after the first visit') unless the profile or the user said it. Seasonal advice is fine only when it commits the owner to nothing. Keep FAQ answers to generic process facts unless specifics were supplied. CONTACT DETAILS: include phone/email/hours only when the profile or user provided them; never invent them, and after creating, ask the user for real contact details to add. Never write 'give us a call', 'call us', or any phrasing that implies a phone number unless the actual number appears on the page; point those CTAs at the contact form instead. A contact_form block is auto-wired to the business's newest live form (one is created if none exists), so every generated site can convert. It's created as a DRAFT; the user publishes it. Hosted at plyto.ai/sites/[subdomain].","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"subdomain":{"type":"string","minLength":3,"maxLength":50,"pattern":"^[a-z0-9-]+$"},"seoTitle":{"type":"string","maxLength":65},"seoDescription":{"type":"string","maxLength":160},"primaryColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"blocks":{"minItems":1,"maxItems":12,"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["hero","features","about","testimonials","cta","contact_form","faq","custom"]},"content":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Shape by kind: hero:{headline,subheadline,ctaLabel,ctaHref} features:{heading,items:[{title,body}]} about:{heading,body} testimonials:{heading,items:[{quote,author}]} cta:{headline,ctaLabel,ctaHref} contact_form:{heading,formId} faq:{heading,items:[{q,a}]} custom:{heading,body}"}},"required":["kind","content"]}}},"required":["name","subdomain","blocks"]}},{"name":"websites__list_sites","title":"List sites: websites","annotations":{"title":"List sites: websites","readOnlyHint":true,"destructiveHint":false},"description":"List this business's websites with status and URLs.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"websites__get_site","title":"Get site: websites","annotations":{"title":"Get site: websites","readOnlyHint":true,"destructiveHint":false},"description":"Get a site with all its blocks (ids, kinds, content) for editing.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"siteId":{"type":"string","minLength":1}},"required":["siteId"]}},{"name":"websites__update_site","title":"Update site: websites","annotations":{"title":"Update site: websites","readOnlyHint":false,"destructiveHint":false},"description":"Update a site's settings: name, SEO title/description, or primary color. Omitted properties keep their current value. Users can also edit these (and every section) themselves at Websites → Edit site.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"siteId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1,"maxLength":120},"seoTitle":{"anyOf":[{"type":"string","maxLength":65},{"type":"null"}]},"seoDescription":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}]},"primaryColor":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"}},"required":["siteId"]}},{"name":"websites__update_block","title":"Update block: websites","annotations":{"title":"Update block: websites","readOnlyHint":false,"destructiveHint":false},"description":"Replace a block's content (same shape rules as create). Use websites.get_site first to find block ids.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"blockId":{"type":"string","minLength":1},"content":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["blockId","content"]}},{"name":"websites__add_block","title":"Add block: websites","annotations":{"title":"Add block: websites","readOnlyHint":false,"destructiveHint":false},"description":"Append a new block to a site (or insert at position).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"siteId":{"type":"string","minLength":1},"block":{"type":"object","properties":{"kind":{"type":"string","enum":["hero","features","about","testimonials","cta","contact_form","faq","custom"]},"content":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Shape by kind: hero:{headline,subheadline,ctaLabel,ctaHref} features:{heading,items:[{title,body}]} about:{heading,body} testimonials:{heading,items:[{quote,author}]} cta:{headline,ctaLabel,ctaHref} contact_form:{heading,formId} faq:{heading,items:[{q,a}]} custom:{heading,body}"}},"required":["kind","content"]},"position":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["siteId","block"]}},{"name":"websites__remove_block","title":"Remove block: websites","annotations":{"title":"Remove block: websites","readOnlyHint":false,"destructiveHint":false},"description":"Remove a block from a site. Confirm with the user first.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"blockId":{"type":"string","minLength":1}},"required":["blockId"]}},{"name":"websites__reorder_blocks","title":"Reorder blocks: websites","annotations":{"title":"Reorder blocks: websites","readOnlyHint":false,"destructiveHint":false},"description":"Reorder a site's sections. Pass the COMPLETE list of block ids in the new top-to-bottom order (websites.get_site returns them). Partial lists are rejected so blocks can't be silently dropped.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"siteId":{"type":"string","minLength":1},"blockIds":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"string","minLength":1}}},"required":["siteId","blockIds"]}},{"name":"websites__analyze_website","title":"Analyze website: websites","annotations":{"title":"Analyze website: websites","readOnlyHint":false,"destructiveHint":false},"description":"Fetch and analyze an existing website (theirs or a competitor's). Returns title, description, headings, navigation links, and the page's visible text so YOU can rebuild it as a much better Plyto site. Keep the real business facts (services, hours, locations, offers), upgrade the copy and structure. Call this FIRST when the user wants their current site redesigned, then compose websites.create_site from what you learn plus the brand profile.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"type":"string","minLength":4}},"required":["url"]}},{"name":"websites__check_custom_domain","title":"Check custom domain: websites","annotations":{"title":"Check custom domain: websites","readOnlyHint":true,"destructiveHint":false},"description":"Verify a site's custom domain DNS. If the records point at us, the domain goes active and the site serves on it (TLS is automatic).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"siteId":{"type":"string","minLength":1}},"required":["siteId"]}},{"name":"seo__run_audit","title":"Run audit: seo","annotations":{"title":"Run audit: seo","readOnlyHint":false,"destructiveHint":false},"description":"Run an SEO + GEO (generative engine optimization) audit on a URL. Fetches the page, robots.txt, sitemap.xml and llms.txt, runs ~15 deterministic checks (title, meta description, headings, alt text, structured data, FAQ content, llms.txt, mobile viewport…), stores the result, and returns the scored checklist. If no URL given, use the business profile's website.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"url":{"description":"Defaults to the business website.","type":"string","minLength":4}}}},{"name":"seo__list_audits","title":"List audits: seo","annotations":{"title":"List audits: seo","readOnlyHint":true,"destructiveHint":false},"description":"List recent SEO/GEO audits with scores.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":20}}}},{"name":"seo__blog__create_draft","title":"Create draft: blog","annotations":{"title":"Create draft: blog","readOnlyHint":false,"destructiveHint":false},"description":"Save a blog post draft. YOU write the post (600-1200 words of genuinely useful, non-generic content in the brand voice, markdown with ## headings) and pass it here. Include a meta description (<=155 chars) and 3-6 keywords.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"title":{"type":"string","minLength":5,"maxLength":200},"contentMd":{"type":"string","minLength":200,"maxLength":50000},"excerpt":{"type":"string","maxLength":300},"seoDescription":{"type":"string","maxLength":200},"keywords":{"maxItems":10,"type":"array","items":{"type":"string"}}},"required":["title","contentMd"]}},{"name":"seo__blog__list","title":"List: blog","annotations":{"title":"List: blog","readOnlyHint":true,"destructiveHint":false},"description":"List blog posts (drafts and published).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":50}}}},{"name":"seo__blog__update_settings","title":"Update settings: blog","annotations":{"title":"Update settings: blog","readOnlyHint":false,"destructiveHint":false},"description":"Configure the automatic blog writer: posts per week (0 disables, max 3), topic list to rotate through, and whether posts auto-publish or wait as drafts. HARD CEILING: 3 posts per week and 10 per day, enforced by the system, no exceptions. When someone asks for more (\"120 a week\"), do NOT attempt it and do NOT promise it. Set 3, and explain that the cap is deliberate: search engines reward steady cadence and punish content flooding, so 3/week outperforms a burst. After changing settings, mention they can also adjust cadence and publishing mode anytime on the Blog page's Auto-writer card.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"postsPerWeek":{"type":"integer","minimum":0,"maximum":3,"description":"0 to 3 only. 0 pauses. Requests above 3 must be clamped to 3 with an explanation, never passed through."},"topics":{"maxItems":20,"type":"array","items":{"type":"string","minLength":3,"maxLength":200}},"autopublish":{"type":"boolean"}},"required":["postsPerWeek"]}},{"name":"booking__pages__list","title":"List: pages","annotations":{"title":"List: pages","readOnlyHint":true,"destructiveHint":false},"description":"List the business's booking pages with their public URLs, durations, and weekly availability. A booking page is where a lead picks a call time; a booked call automatically qualifies the lead (sales_qualified) and, when the lead came from an ad, reports that qualification back to the ad platform.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"booking__pages__create","title":"Create: pages","annotations":{"title":"Create: pages","readOnlyHint":false,"destructiveHint":false},"description":"Create a booking page and return its public URL, ready to share, link from the website, or put in a form's thank-you message. Availability is weekly windows in 24h HH:MM, keyed by weekday number (0=Sunday..6=Saturday). Times are in the page timezone (default America/Los_Angeles). Example availability: {\"1\":[[\"09:00\",\"17:00\"]],\"3\":[[\"09:00\",\"12:00\"]]}.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"name":{"type":"string","minLength":2,"maxLength":80,"description":"Shown to the booker, e.g. \"Intro call\""},"description":{"type":"string","maxLength":300},"durationMinutes":{"default":30,"type":"integer","minimum":10,"maximum":240},"bufferMinutes":{"default":0,"type":"integer","minimum":0,"maximum":120},"minNoticeHours":{"default":12,"type":"integer","minimum":0,"maximum":168},"maxDaysAhead":{"default":21,"type":"integer","minimum":1,"maximum":60},"timezone":{"type":"string","maxLength":64},"availability":{"type":"object","propertyNames":{"type":"string","enum":["0","1","2","3","4","5","6"]},"additionalProperties":{"maxItems":4,"type":"array","items":{"type":"array","prefixItems":[{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"}]}},"required":["0","1","2","3","4","5","6"],"description":"Weekly windows; at least one weekday required."}},"required":["name","availability"]}},{"name":"booking__bookings__list","title":"List: bookings","annotations":{"title":"List: bookings","readOnlyHint":true,"destructiveHint":false},"description":"Upcoming booked calls: who, when, their note, and the show-up task. Use for questions like \"what calls do I have\" or \"who booked this week\".","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"includePast":{"default":false,"type":"boolean"}}}},{"name":"pitches__create","title":"Create: pitches","annotations":{"title":"Create: pitches","readOnlyHint":false,"destructiveHint":false},"description":"Generate a branded pitch deck: a full slide presentation built from the brand kit (colors, logo, voice, approved claims) and, when a deal/company/contact id is given, grounded in that CRM record — who they are, what the deal is worth. Returns a hosted share link the user sends to their prospect. Takes one to two minutes and charges AI credits (it is a large generation). Growth plan and up. Use when someone wants a pitch, proposal deck, sales presentation, or slides for a specific prospect or deal.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"brief":{"type":"string","minLength":10,"maxLength":2000,"description":"What the pitch should accomplish and for whom, in plain language: the offer, the angle, anything the user said about the audience or meeting. The deck grounds itself in the brand kit and approved claims automatically; when a dealId/companyId/contactId is given it also grounds in that CRM record. Never invent facts the brief and CRM don't contain."},"title":{"description":"Deck title. Defaults to the deal/company pairing.","type":"string","maxLength":160},"dealId":{"description":"Ground the pitch in this deal (pulls its company, contact, and value).","type":"string"},"companyId":{"description":"Ground the pitch in this CRM company.","type":"string"},"contactId":{"description":"Ground the pitch in this contact.","type":"string"}},"required":["brief"]}},{"name":"pitches__list","title":"List: pitches","annotations":{"title":"List: pitches","readOnlyHint":true,"destructiveHint":false},"description":"List this business's pitch decks with status, share links, and view counts. The honest source for 'has anyone opened the deck'. Status 'generating' means the deck is still building; 'failed' means the build errored and it should be recreated.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{}}},{"name":"pitches__edit_slide","title":"Edit slide: pitches","annotations":{"title":"Edit slide: pitches","readOnlyHint":false,"destructiveHint":false},"description":"Edit one slide of an existing pitch deck with an AI rewrite that keeps the deck's design. The share link updates instantly. Use when the user wants to tweak a deck: change wording, adjust a slide's angle, fix an error. Charges a small AI credit amount.","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"deckId":{"type":"string","minLength":1,"description":"The pitch deck to edit."},"slideNumber":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"Which slide to change, 1-based. Get the order from pitches.list or the deck itself."},"instruction":{"type":"string","minLength":5,"maxLength":1500,"description":"What to change on that slide, in plain language (rewrite the headline, soften the pricing framing, swap the layout emphasis). One slide per call. Never instruct it to add statistics or testimonials that aren't in the CRM or approved claims."}},"required":["deckId","slideNumber","instruction"]}},{"name":"pitches__delete","title":"Delete: pitches","annotations":{"title":"Delete: pitches","readOnlyHint":false,"destructiveHint":false},"description":"Delete a pitch deck permanently. Its share link stops working immediately. Two-step: first call returns the deck title to confirm with the user; call again with confirm:true to delete (the same two-step as deleting a deck in the UI).","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"deckId":{"type":"string","minLength":1,"description":"The pitch deck to delete permanently."},"confirm":{"description":"Must be true to actually delete. Call WITHOUT it first: the tool returns the deck's title so you can confirm with the user. Set true only after the user approved deleting THIS deck by name in this conversation.","type":"boolean"}},"required":["deckId"]}}],"resources":[],"prompts":[]}