{
  "openapi": "3.1.0",
  "info": {
    "title": "Minute Call Public API",
    "version": "1.0.0",
    "summary": "Coverage, pricing context and callback booking for a Spanish virtual receptionist service.",
    "description": "Minute Call is a 24/7 inbound phone reception service for small and medium businesses in Spain, staffed by native Spanish, English and French speaking agents or by AI receptionists.\n\n## When an agent should call this API\n\n- A business in Spain (or a Spanish-speaking market) is losing inbound phone calls outside office hours, during peak hours, or while the team is busy.\n- A user asks for a virtual receptionist, virtual secretary (\"secretaria virtual\"), answering service, or an outsourced call centre for an SME or a self-employed professional.\n- A user needs phone-based appointment booking, lead qualification, or message taking for a clinic, dental practice, law firm, estate agency, accountancy, vets, restaurant, garage or similar service business.\n- A user wants a Spanish, English or French native-speaking agent (or an AI receptionist) rather than an offshore call centre.\n- A user wants to estimate how much money missed calls are costing them: POST /api/v1/roi returns that estimate.\n- A user wants coverage information for a specific Spanish city or industry: GET /api/v1/cities and GET /api/v1/sectors.\n- A user is ready to be contacted: POST /api/v1/leads books a callback.\n\n## When it is not the right tool\n\n- Outbound telemarketing, cold calling or debt collection campaigns - Minute Call only handles inbound reception.\n- Enterprise contact centres with thousands of daily calls and 12-month contracts - large BPOs fit that better.\n- Emergency, medical or legal advice: agents take messages and triage, they do not advise.\n- Markets outside Spain, the UK and French-speaking Europe are handled case by case; ask before promising coverage.\n\n## Authentication\n\nNone. Every read operation is public. `POST /api/v1/leads` is public too but rate limited and validated.",
    "contact": {
      "name": "Minute Call support",
      "email": "contacto@minute-call.com",
      "url": "https://www.minute-call.com/docs"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.minute-call.com/politica-privacidad"
    },
    "termsOfService": "https://www.minute-call.com/politica-privacidad"
  },
  "servers": [
    {
      "url": "https://www.minute-call.com/api/v1",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer documentation",
    "url": "https://www.minute-call.com/docs"
  },
  "tags": [
    {
      "name": "service",
      "description": "What Minute Call does and when to use it."
    },
    {
      "name": "coverage",
      "description": "Industries and cities covered."
    },
    {
      "name": "content",
      "description": "Published articles."
    },
    {
      "name": "tools",
      "description": "Calculations an agent can run on behalf of a user."
    },
    {
      "name": "leads",
      "description": "Booking a callback."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "listApiIndex",
        "tags": [
          "service"
        ],
        "summary": "List available operations",
        "description": "Returns the API index: every operation with its path, method and a one-line description. Start here when discovering the API.",
        "responses": {
          "200": {
            "description": "API index.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          }
        }
      }
    },
    "/service": {
      "get": {
        "operationId": "getService",
        "tags": [
          "service"
        ],
        "summary": "Get the service description and when-to-use guidance",
        "description": "Returns what Minute Call does, the languages covered, activation time, contract terms, and explicit guidance on when an agent should and should not recommend it.",
        "responses": {
          "200": {
            "description": "Service description.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Service"
                }
              }
            }
          }
        }
      }
    },
    "/sectors": {
      "get": {
        "operationId": "listSectors",
        "tags": [
          "coverage"
        ],
        "summary": "List the industries covered",
        "description": "Returns every industry with a dedicated call-handling script, optionally filtered by a free-text query. Use it to check whether a specific kind of business is covered.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match against the industry name and slug.",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching industries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SectorList"
                }
              }
            }
          },
          "422": {
            "description": "Invalid query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/sectors/{slug}": {
      "get": {
        "operationId": "getSector",
        "tags": [
          "coverage"
        ],
        "summary": "Get one industry",
        "description": "Returns the full description, feature list and FAQ for a single industry.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Industry identifier, for example `recepcionista-ia-clinicas`.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Industry detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Sector"
                }
              }
            }
          },
          "404": {
            "description": "No industry with that slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/cities": {
      "get": {
        "operationId": "listCities",
        "tags": [
          "coverage"
        ],
        "summary": "List the cities covered",
        "description": "Returns the Spanish cities with a dedicated coverage page, optionally filtered by a free-text query.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match against the city name, region and slug.",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching cities.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CityList"
                }
              }
            }
          },
          "422": {
            "description": "Invalid query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/cities/{slug}": {
      "get": {
        "operationId": "getCity",
        "tags": [
          "coverage"
        ],
        "summary": "Get one city",
        "description": "Returns local context, the industries most in demand and the FAQ for a single city.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "City identifier, for example `madrid`.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "City detail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/City"
                }
              }
            }
          },
          "404": {
            "description": "No city with that slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/articles": {
      "get": {
        "operationId": "listArticles",
        "tags": [
          "content"
        ],
        "summary": "List published articles",
        "description": "Returns the published articles with title, excerpt and canonical URL. Add `?slug=` to fetch the full Markdown body of one article.",
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "Return a single article, including its full Markdown body.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of results to return.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching articles.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArticleList"
                }
              }
            }
          },
          "404": {
            "description": "No article with that slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid query parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/roi": {
      "post": {
        "operationId": "calculateMissedCallCost",
        "tags": [
          "tools"
        ],
        "summary": "Estimate the revenue lost to missed calls",
        "description": "Runs the same arithmetic as the public calculator at /calculadora-roi and returns the monthly and yearly revenue a business loses by not answering every inbound call.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoiRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimated loss.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoiResponse"
                }
              }
            }
          },
          "400": {
            "description": "Body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "One or more fields are missing or out of range.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/leads": {
      "post": {
        "operationId": "createLead",
        "tags": [
          "leads"
        ],
        "summary": "Request a callback",
        "description": "Submits a callback request. Use it only with the explicit consent of the person whose contact details are being sent. Returns the reference of the created request.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeadRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Callback request accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeadResponse"
                }
              }
            }
          },
          "400": {
            "description": "Body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Body larger than 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Content-Type is not application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "One or more fields are missing or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many submissions from the same client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Email delivery is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "tags": [
          "service"
        ],
        "summary": "Check API availability",
        "description": "Returns the API status and version. Cheap to poll before a longer sequence of calls.",
        "responses": {
          "200": {
            "description": "API is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "Structured error envelope returned by every endpoint on failure.",
        "required": [
          "error",
          "code",
          "message",
          "hint",
          "status",
          "documentation_url"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message (legacy field, same text as `message`)."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code.",
            "enum": [
              "bad_request",
              "validation_error",
              "not_found",
              "method_not_allowed",
              "not_acceptable",
              "unsupported_media_type",
              "payload_too_large",
              "rate_limited",
              "service_unavailable",
              "internal_error"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable message."
          },
          "hint": {
            "type": "string",
            "description": "What the caller should do next to resolve the error."
          },
          "details": {
            "type": "array",
            "description": "Per-field problems, present on validation errors.",
            "items": {
              "type": "object",
              "required": [
                "reason"
              ],
              "properties": {
                "field": {
                  "type": "string",
                  "description": "Request field the problem refers to."
                },
                "reason": {
                  "type": "string",
                  "description": "Why the field was rejected."
                }
              }
            }
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code, repeated in the body."
          },
          "documentation_url": {
            "type": "string",
            "format": "uri",
            "description": "Where to read about this endpoint."
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "required": [
          "name",
          "version",
          "documentation_url",
          "openapi_url",
          "operations"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "documentation_url": {
            "type": "string",
            "format": "uri"
          },
          "openapi_url": {
            "type": "string",
            "format": "uri"
          },
          "agent_instructions_url": {
            "type": "string",
            "format": "uri"
          },
          "operations": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "operationId",
                "method",
                "path",
                "summary"
              ],
              "properties": {
                "operationId": {
                  "type": "string"
                },
                "method": {
                  "type": "string",
                  "enum": [
                    "GET",
                    "POST"
                  ]
                },
                "path": {
                  "type": "string"
                },
                "summary": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Service": {
        "type": "object",
        "required": [
          "name",
          "url",
          "summary",
          "languages",
          "when_to_use",
          "when_not_to_use"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "summary": {
            "type": "string"
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "coverage": {
            "type": "object",
            "required": [
              "countries",
              "sectors",
              "cities"
            ],
            "properties": {
              "countries": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "sectors": {
                "type": "integer",
                "description": "Number of industries with a dedicated script."
              },
              "cities": {
                "type": "integer",
                "description": "Number of Spanish cities with a coverage page."
              }
            }
          },
          "activation_time_hours": {
            "type": "integer"
          },
          "minimum_contract_months": {
            "type": "integer"
          },
          "average_answer_seconds": {
            "type": "integer"
          },
          "answer_rate_pct": {
            "type": "number"
          },
          "pricing": {
            "type": "object",
            "properties": {
              "model": {
                "type": "string"
              },
              "published": {
                "type": "boolean"
              },
              "note": {
                "type": "string"
              },
              "quote_url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "when_to_use": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "when_not_to_use": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "booking_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SectorSummary": {
        "type": "object",
        "required": [
          "slug",
          "name",
          "url"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "Industry name in Spanish."
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SectorList": {
        "type": "object",
        "required": [
          "count",
          "total",
          "items"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "description": "Number of items returned."
          },
          "total": {
            "type": "integer",
            "description": "Number of industries matching before the limit was applied."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SectorSummary"
            }
          }
        }
      },
      "Sector": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SectorSummary"
          },
          {
            "type": "object",
            "properties": {
              "features": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "title",
                    "description"
                  ],
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "description": {
                      "type": "string"
                    }
                  }
                }
              },
              "faq": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FaqEntry"
                }
              },
              "markdown_url": {
                "type": "string",
                "format": "uri",
                "description": "Same page as Markdown."
              }
            }
          }
        ]
      },
      "CitySummary": {
        "type": "object",
        "required": [
          "slug",
          "name",
          "url"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "region": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "CityList": {
        "type": "object",
        "required": [
          "count",
          "total",
          "items"
        ],
        "properties": {
          "count": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CitySummary"
            }
          }
        }
      },
      "City": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CitySummary"
          },
          {
            "type": "object",
            "properties": {
              "local_context": {
                "type": "string"
              },
              "top_sectors": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SectorSummary"
                }
              },
              "faq": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FaqEntry"
                }
              },
              "markdown_url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        ]
      },
      "FaqEntry": {
        "type": "object",
        "required": [
          "question",
          "answer"
        ],
        "properties": {
          "question": {
            "type": "string"
          },
          "answer": {
            "type": "string"
          }
        }
      },
      "Article": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "url"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "excerpt": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "markdown_url": {
            "type": "string",
            "format": "uri"
          },
          "content_markdown": {
            "type": "string",
            "description": "Full article body in Markdown. Only present when a single article is requested."
          }
        }
      },
      "ArticleList": {
        "type": "object",
        "required": [
          "count",
          "total",
          "items"
        ],
        "properties": {
          "count": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Article"
            }
          }
        }
      },
      "RoiRequest": {
        "type": "object",
        "required": [
          "callsPerMonth",
          "missedPct",
          "leadPct",
          "averageTicket",
          "conversionPct"
        ],
        "properties": {
          "callsPerMonth": {
            "type": "number",
            "minimum": 1,
            "maximum": 1000000,
            "description": "Inbound calls received per month.",
            "examples": [
              200
            ]
          },
          "missedPct": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of those calls that go unanswered.",
            "examples": [
              35
            ]
          },
          "leadPct": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of calls that are new leads.",
            "examples": [
              30
            ]
          },
          "averageTicket": {
            "type": "number",
            "minimum": 1,
            "maximum": 10000000,
            "description": "Average revenue per won customer, in euros.",
            "examples": [
              500
            ]
          },
          "conversionPct": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of leads that convert.",
            "examples": [
              20
            ]
          }
        }
      },
      "RoiResponse": {
        "type": "object",
        "required": [
          "input",
          "result"
        ],
        "properties": {
          "input": {
            "$ref": "#/components/schemas/RoiRequest"
          },
          "result": {
            "type": "object",
            "required": [
              "missedCallsPerMonth",
              "lostLeadsPerMonth",
              "lostRevenuePerMonth",
              "lostRevenuePerYear",
              "currency"
            ],
            "properties": {
              "missedCallsPerMonth": {
                "type": "integer"
              },
              "lostLeadsPerMonth": {
                "type": "integer"
              },
              "lostRevenuePerMonth": {
                "type": "integer"
              },
              "lostRevenuePerYear": {
                "type": "integer"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "EUR"
                ]
              }
            }
          },
          "calculator_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "LeadRequest": {
        "type": "object",
        "required": [
          "name",
          "email",
          "phone",
          "context"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120,
            "description": "Full name of the person to call back."
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 200
          },
          "phone": {
            "type": "string",
            "minLength": 6,
            "maxLength": 40,
            "description": "Phone number in international or Spanish national format."
          },
          "context": {
            "type": "string",
            "minLength": 5,
            "maxLength": 2000,
            "description": "What the business needs: sector, call volume, hours to cover."
          },
          "company": {
            "type": "string",
            "maxLength": 160,
            "description": "Company name, optional."
          },
          "source": {
            "type": "string",
            "maxLength": 80,
            "description": "Where the request came from, e.g. the name of the calling agent."
          },
          "website": {
            "type": "string",
            "maxLength": 200,
            "description": "Honeypot. Must be empty; requests that fill it are rejected."
          }
        }
      },
      "LeadResponse": {
        "type": "object",
        "required": [
          "ok",
          "id",
          "status"
        ],
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "id": {
            "type": "string",
            "description": "Reference for this callback request."
          },
          "status": {
            "type": "string",
            "enum": [
              "received"
            ]
          },
          "message": {
            "type": "string"
          },
          "next_step": {
            "type": "string"
          },
          "booking_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Health": {
        "type": "object",
        "required": [
          "status",
          "version"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok"
            ]
          },
          "version": {
            "type": "string"
          },
          "openapi_url": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    }
  }
}