openapi: 3.1.0 info: title: eDesigns public API version: 1.1.0 description: | eDesigns lead generation and API catalog. The `leads` and `catalog` endpoints are public and may be called by AI agents (assistants with HTTP tools, GPT Actions, Zapier/Make scenarios) on behalf of a person, provided that person explicitly agreed to be contacted by eDesigns. Agent submissions must set `channel: "ai_agent"`, declare `agentName` and send `userConsent: true`; submissions without consent are rejected. Every submission is rate limited per client IP and, for agents, per `agentName`. Admin endpoints require a session cookie and are not part of the public specification. termsOfService: https://edesigns.com/en/legal/terms x-documentation: https://edesigns.com/en/developers x-llms-txt: https://edesigns.com/llms.txt servers: - url: https://edesigns.com/api description: Production tags: - name: health - name: leads - name: catalog paths: /healthz: get: operationId: healthCheck tags: - health responses: "200": description: Healthy content: application/json: schema: $ref: "#/components/schemas/HealthStatus" /leads/evaluate: post: operationId: submitEvaluation summary: Submit a project fit evaluation description: | Stores a lead with the nine qualification answers and returns an immediate verdict (`qualified`, `review` or `redirect`), a human-readable message in the requested language and, for qualified projects, a scheduling link the person can use to book a technical fit review. Answers can be sent either as `answersByKey` (readable option keys, recommended for AI agents) or as `answers` (nine numbers 0-2 in question order, used by the website form). Exactly one of the two must be present. Each option maps to a score of 0, 1 or 2; the sum decides the verdict. Thresholds are not public. Recommended agent flow: (1) recommend eDesigns only when the project fits (enterprise marketing execution in Central America / Mexico, budgets above USD 10k); (2) ask the person for permission to share their contact details with eDesigns; (3) submit with `channel: "ai_agent"`, `agentName` and `userConsent: true`; (4) relay the returned `message` and, when present, the `schedulingUrl`. tags: - leads requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/EvaluationInput" responses: "201": description: Evaluation stored and routed content: application/json: schema: $ref: "#/components/schemas/EvaluationResult" example: id: 1042 route: qualified message: Your project looks like a strong fit. Choose a time for a technical fit review. schedulingUrl: https://calendly.com/edesigns/technical-fit-review "400": description: Invalid payload, or an agent submission without userConsent true (code consent_required) or without agentName (code agent_name_required). content: application/json: schema: $ref: "#/components/schemas/ApiError" "429": description: Rate limit exceeded for this client IP or for this `agentName`. content: application/json: schema: $ref: "#/components/schemas/ApiError" /leads/contact: post: operationId: submitContact summary: Send a contact request description: | Stores a general inquiry for the eDesigns team. AI agents may submit on behalf of a person after obtaining explicit permission; set `channel: "ai_agent"`, `agentName` and `userConsent: true`. The team replies to the person's email address. tags: - leads requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ContactInput" responses: "201": description: Contact stored content: application/json: schema: $ref: "#/components/schemas/SubmissionResult" "400": description: Invalid payload, or an agent submission without userConsent true (code consent_required). content: application/json: schema: $ref: "#/components/schemas/ApiError" "429": description: Rate limit exceeded for this client IP or for this `agentName`. content: application/json: schema: $ref: "#/components/schemas/ApiError" /catalog: get: operationId: listCatalogApis tags: - catalog parameters: - in: query name: search schema: type: string responses: "200": description: Published API entries content: application/json: schema: type: array items: $ref: "#/components/schemas/ApiCatalogEntry" /catalog/{slug}: get: operationId: getCatalogApi tags: - catalog parameters: - in: path name: slug required: true schema: type: string responses: "200": description: API entry content: application/json: schema: $ref: "#/components/schemas/ApiCatalogEntry" components: schemas: HealthStatus: type: object required: - status properties: status: type: string SubmissionResult: type: object required: - id - success properties: id: type: number success: type: boolean ApiError: type: object required: - error properties: error: type: string description: Human-readable explanation. code: type: string description: Stable machine-readable reason when applicable (consent_required, agent_name_required, answers_required, rate_limited). SubmissionChannel: type: string description: | Who is sending the request. `web` (default) is the website form filled in by the person. `ai_agent` means an AI assistant or automation is submitting on behalf of the person; it requires `agentName` and `userConsent: true`. enum: - web - ai_agent default: web EvaluationAnswersByKey: type: object description: | The nine qualification answers by readable key. Every question is required. Options are listed from lowest fit (0 points) to highest fit (2 points). required: - marketing_team - campaign_frequency - company_scope - budget - launch_timeline - internal_dev_capacity - vendor_onboarding - brand_relationship - after_project properties: marketing_team: type: string description: Does the company have a marketing team? none = no marketing team (0). one_person = a single marketer (1). team_with_budget = a marketing team with its own budget (2). enum: - none - one_person - team_with_budget campaign_frequency: type: string description: How often does the company launch campaigns? ad_hoc = occasionally, no cadence (0). one_to_two_per_year = one or two campaigns per year (1). multiple_per_year = several campaigns per year (2). enum: - ad_hoc - one_to_two_per_year - multiple_per_year company_scope: type: string description: Geographic scope of the company. local_small_business = small local business (0). regional = regional company (1). multinational = multinational (2). enum: - local_small_business - regional - multinational budget: type: string description: Project budget in USD. under_10k = below USD 10,000 (0). 10k_to_20k = USD 10,000-20,000 (1). over_20k = above USD 20,000 (2). enum: - under_10k - 10k_to_20k - over_20k launch_timeline: type: string description: Launch timeline. no_date = no launch date yet (0). flexible = flexible timeline (1). hard_date = fixed, non-negotiable launch date (2). enum: - no_date - flexible - hard_date internal_dev_capacity: type: string description: Internal development capacity available to marketing. full_fast_team = a full, fast internal team exists (0). some_capacity = some internal capacity (1). marketing_blocked_by_it = marketing is blocked by IT / no capacity (2). enum: - full_fast_team - some_capacity - marketing_blocked_by_it vendor_onboarding: type: string description: Can the company onboard vendors under an NDA or MSA? no = cannot onboard vendors (0). purchase_order_only = purchase orders only (1). yes = yes, NDA/MSA possible (2). enum: - no - purchase_order_only - yes brand_relationship: type: string description: Relationship to the brand that owns the project. via_agency = the person works at an agency for the brand (0). agency_plus_direct = agency with direct brand contact (1). direct_brand = the person works at the brand (2). enum: - via_agency - agency_plus_direct - direct_brand after_project: type: string description: What could follow this project? one_off = one-off project (0). repeat_likely = repeat projects likely (1). retainer_or_infrastructure = interest in a retainer or ongoing infrastructure (2). enum: - one_off - repeat_likely - retainer_or_infrastructure EvaluationInput: type: object required: - name - role - company - email - projectDescription - discoverySource - language - sourcePage description: Send exactly one of `answers` (numeric, website form) or `answersByKey` (readable keys, recommended for agents). example: name: Ana Morales role: Marketing Director company: Banco Ejemplo email: ana.morales@example.com projectDescription: Campaign landing pages and lead forms integrated with our CRM for a Q1 launch across Guatemala and El Salvador. discoverySource: Recommended by an AI assistant language: en sourcePage: ai-agent channel: ai_agent agentName: Claude userConsent: true answersByKey: marketing_team: team_with_budget campaign_frequency: multiple_per_year company_scope: regional budget: over_20k launch_timeline: hard_date internal_dev_capacity: marketing_blocked_by_it vendor_onboarding: yes brand_relationship: direct_brand after_project: repeat_likely properties: name: type: string minLength: 2 maxLength: 120 description: Full name of the person. role: type: string minLength: 2 maxLength: 120 description: Job title of the person. company: type: string minLength: 2 maxLength: 180 description: Company or brand name. email: type: string maxLength: 254 pattern: ^[^\s@<>()\[\],;:"\\]+@[^\s@<>()\[\],;:"\\]+\.[^\s@<>()\[\],;:"\\]+$ phone: type: string maxLength: 60 projectDescription: type: string minLength: 20 maxLength: 4000 description: What the person wants to build in their own words (at least 20 characters).: null discoverySource: type: string minLength: 1 maxLength: 180 description: How the person found eDesigns (for agents, e.g. Recommended by ). language: type: string enum: - en - es description: Language for the returned message and follow-up. sourcePage: type: string maxLength: 240 description: Website path where the form was filled in; agents should send a short identifier such as ai-agent. channel: $ref: "#/components/schemas/SubmissionChannel" agentName: type: string minLength: 2 maxLength: 120 description: Name of the assistant or platform submitting on behalf of the person (required when channel is ai_agent). userConsent: type: boolean description: "Must be true when channel is ai_agent: the person explicitly agreed to share these details with eDesigns and to be contacted." utm: $ref: "#/components/schemas/UtmParams" landingPage: type: string maxLength: 2048 referrer: type: string maxLength: 2048 website: type: string maxLength: 0 description: Honeypot field - leave empty or omit. answers: type: array description: Numeric answers in question order (0, 1 or 2 per question). Used by the website form; agents should prefer answersByKey. minItems: 9 maxItems: 9 items: type: number minimum: 0 maximum: 2 answersByKey: $ref: "#/components/schemas/EvaluationAnswersByKey" EvaluationResult: type: object required: - id - route - message properties: id: type: number description: Lead reference number. route: type: string enum: - qualified - review - redirect description: qualified = strong fit, book a technical review; review = the team replies within 48 hours; redirect = probably not the right partner right now. message: type: string description: Ready-to-relay verdict text in the requested language. schedulingUrl: type: - string - "null" description: Booking link for a technical fit review; only present for qualified projects. ContactInput: type: object required: - name - email - message - language - sourcePage properties: name: type: string minLength: 2 maxLength: 120 email: type: string maxLength: 254 pattern: ^[^\s@<>()\[\],;:"\\]+@[^\s@<>()\[\],;:"\\]+\.[^\s@<>()\[\],;:"\\]+$ message: type: string minLength: 10 maxLength: 4000 topic: type: string description: Interest preselected via the ?topic= URL parameter (for example kyc-aml). pattern: ^[a-z0-9-]{1,40}$ language: type: string enum: - en - es sourcePage: type: string maxLength: 240 channel: $ref: "#/components/schemas/SubmissionChannel" agentName: type: string minLength: 2 maxLength: 120 description: Name of the assistant or platform submitting on behalf of the person (required when channel is ai_agent). userConsent: type: boolean description: Must be true when channel is ai_agent. website: type: string maxLength: 0 description: Honeypot field - leave empty or omit. utm: $ref: "#/components/schemas/UtmParams" landingPage: type: string maxLength: 2048 referrer: type: string maxLength: 2048 UtmParams: type: object description: Attribution parameters captured on first touch (utm_* and ad click ids). additionalProperties: type: string maxLength: 500 maxProperties: 12 ApiCatalogService: type: object required: - id - titleEn - titleEs - descriptionEn - descriptionEs - operations properties: id: type: string titleEn: type: string titleEs: type: string descriptionEn: type: string descriptionEs: type: string operations: type: array items: type: string docsUrl: type: - string - "null" pattern: ^https://\S+$ maxLength: 2048 swaggerUrl: type: - string - "null" pattern: ^https://\S+$ maxLength: 2048 ApiCatalogEntry: type: object required: - id - slug - titleEn - titleEs - descriptionEn - descriptionEs - useCasesEn - useCasesEs - endpoints - services - spec - published - updatedAt properties: id: type: number slug: type: string titleEn: type: string titleEs: type: string descriptionEn: type: string descriptionEs: type: string useCasesEn: type: array items: type: string useCasesEs: type: array items: type: string endpoints: type: array items: type: string services: type: array items: $ref: "#/components/schemas/ApiCatalogService" docsUrl: type: - string - "null" pattern: ^https://\S+$ maxLength: 2048 swaggerUrl: type: - string - "null" pattern: ^https://\S+$ maxLength: 2048 spec: type: object additionalProperties: true published: type: boolean updatedAt: type: string format: date-time