{
  "openapi": "3.1.0",
  "info": {
    "title": "MadeYouViral public API",
    "version": "1.0.0",
    "description": "Service list, price estimates and project requests for MadeYouViral. No authentication. Estimates store nothing; project requests are sent to the team, so only submit them when a person has asked you to.",
    "contact": {
      "name": "MadeYouViral",
      "email": "project@madeyouviral.com",
      "url": "https://madeyouviral.com"
    }
  },
  "servers": [
    {
      "url": "https://madeyouviral.com"
    }
  ],
  "externalDocs": {
    "description": "Human-readable API guide",
    "url": "https://madeyouviral.com/docs/api"
  },
  "paths": {
    "/api/services": {
      "get": {
        "operationId": "listServices",
        "summary": "List services, starting prices and quote question ids",
        "responses": {
          "200": {
            "description": "Service list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/quote/estimate": {
      "post": {
        "operationId": "estimatePrice",
        "summary": "Calculate a price range. Read-only; nothing is sent to the team.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EstimateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimate in the requested currency",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed. `fields` maps field paths to messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Wait a few minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "sendProjectRequest",
        "summary": "Send a project request to the team. Use only on a person's request, with their own details.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request received",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Accepted"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed. `fields` maps field paths to messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Wait a few minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/quote": {
      "post": {
        "operationId": "createQuote",
        "summary": "Create a full quote with contact details, as the quote page does.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote created and sent to the team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed. `fields` maps field paths to messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Wait a few minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/quote/feedback": {
      "post": {
        "operationId": "sendQuoteFeedback",
        "summary": "Tell the team how a quoted price felt, optionally with a budget.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FeedbackRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Feedback received",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Accepted"
                }
              }
            }
          },
          "422": {
            "description": "Validation failed. `fields` maps field paths to messages.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests. Wait a few minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "EstimateRequest": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "services": {
            "minItems": 1,
            "maxItems": 6,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "websites",
                "web-apps",
                "mobile-apps",
                "ai-automation",
                "meta-ads",
                "video-editing"
              ]
            }
          },
          "answers": {
            "type": "object",
            "propertyNames": {
              "type": "string",
              "maxLength": 64
            },
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 64
                },
                {
                  "maxItems": 12,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "maxLength": 64
                  }
                }
              ]
            }
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD",
              "GBP",
              "EUR"
            ]
          }
        },
        "required": [
          "services"
        ]
      },
      "ContactRequest": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "email": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "phone": {
            "type": "string",
            "maxLength": 25
          },
          "company": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "string",
                "const": ""
              }
            ]
          },
          "services": {
            "maxItems": 6,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "websites",
                "web-apps",
                "mobile-apps",
                "ai-automation",
                "meta-ads",
                "video-editing"
              ]
            }
          },
          "budget": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "under-2k",
                  "2k-5k",
                  "5k-15k",
                  "15k-50k",
                  "50k-plus",
                  "unsure"
                ]
              },
              {
                "type": "string",
                "const": ""
              }
            ]
          },
          "message": {
            "type": "string",
            "minLength": 10,
            "maxLength": 4000
          },
          "source": {
            "type": "string",
            "enum": [
              "contact",
              "webmcp",
              "api"
            ]
          },
          "consent": {
            "type": "boolean"
          },
          "startedAt": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "honeypot": {
            "type": "string",
            "maxLength": 500
          },
          "turnstileToken": {
            "type": "string",
            "maxLength": 4096
          },
          "attribution": {
            "type": "object",
            "propertyNames": {
              "type": "string",
              "maxLength": 40
            },
            "additionalProperties": {
              "type": "string",
              "maxLength": 300
            }
          },
          "marketingConsent": {
            "type": "boolean"
          },
          "eventId": {
            "type": "string",
            "maxLength": 80
          },
          "page": {
            "type": "string",
            "maxLength": 300
          }
        },
        "required": [
          "name",
          "email",
          "message"
        ]
      },
      "QuoteRequest": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^MYV-[A-HJ-NP-Z2-9]{6}$"
          },
          "offer": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "maxLength": 32
              },
              "claimedAt": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "code",
              "claimedAt"
            ]
          },
          "services": {
            "minItems": 1,
            "maxItems": 6,
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "websites",
                "web-apps",
                "mobile-apps",
                "ai-automation",
                "meta-ads",
                "video-editing"
              ]
            }
          },
          "answers": {
            "type": "object",
            "propertyNames": {
              "type": "string",
              "maxLength": 64
            },
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 64
                },
                {
                  "maxItems": 12,
                  "type": "array",
                  "items": {
                    "type": "string",
                    "maxLength": 64
                  }
                }
              ]
            }
          },
          "contact": {
            "type": "object",
            "properties": {
              "firstName": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60
              },
              "email": {
                "type": "string",
                "minLength": 1,
                "maxLength": 160
              },
              "phone": {
                "type": "string",
                "maxLength": 25
              },
              "company": {
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 100
                  },
                  {
                    "type": "string",
                    "const": ""
                  }
                ]
              }
            },
            "required": [
              "firstName",
              "email"
            ]
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD",
              "GBP",
              "EUR"
            ]
          },
          "startedAt": {
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "honeypot": {
            "type": "string",
            "maxLength": 500
          },
          "turnstileToken": {
            "type": "string",
            "maxLength": 4096
          },
          "attribution": {
            "type": "object",
            "propertyNames": {
              "type": "string",
              "maxLength": 40
            },
            "additionalProperties": {
              "type": "string",
              "maxLength": 300
            }
          },
          "marketingConsent": {
            "type": "boolean"
          },
          "eventId": {
            "type": "string",
            "maxLength": 80
          },
          "page": {
            "type": "string",
            "maxLength": 300
          }
        },
        "required": [
          "services",
          "answers",
          "contact",
          "currency"
        ]
      },
      "FeedbackRequest": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^MYV-[A-HJ-NP-Z2-9]{6}$"
          },
          "email": {
            "type": "string",
            "minLength": 1,
            "maxLength": 160
          },
          "feedback": {
            "type": "string",
            "enum": [
              "good",
              "stretch",
              "high"
            ]
          },
          "budget": {
            "type": "number",
            "minimum": 0,
            "maximum": 100000000
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD",
              "GBP",
              "EUR"
            ]
          },
          "note": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 600
              },
              {
                "type": "string",
                "const": ""
              }
            ]
          }
        },
        "required": [
          "id",
          "email",
          "feedback"
        ]
      },
      "Accepted": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "id": {
            "type": "string"
          },
          "delivered": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        },
        "required": [
          "ok",
          "error"
        ]
      }
    }
  }
}