{
  "openapi": "3.1.0",
  "info": {
    "title": "FormsReach Submit API",
    "version": "1.0.0",
    "summary": "Public form backend for HTML and JSON website submissions.",
    "description": "FormsReach accepts website form submissions without your own server. POST JSON, urlencoded, or multipart to /submit with api_key. Delivery is email, WhatsApp (Pro/Agency), webhooks, and other channels, metered from a monthly credit pool. Authenticated dashboard MCP tools live at https://app.formsreach.com/api/mcp and are documented at https://formsreach.com/mcp/tools.json.",
    "contact": {
      "name": "FormsReach",
      "email": "hello@formsreach.com",
      "url": "https://formsreach.com/developers/"
    },
    "license": {
      "name": "Terms of Service",
      "url": "https://formsreach.com/terms/"
    }
  },
  "servers": [
    {
      "url": "https://api.formsreach.com",
      "description": "Public submit host (rewrites to /api/v1/submit)."
    }
  ],
  "tags": [
    {
      "name": "Submit",
      "description": "Public website form endpoint. No Bearer token."
    }
  ],
  "paths": {
    "/submit": {
      "post": {
        "operationId": "submitForm",
        "tags": [
          "Submit"
        ],
        "summary": "Accept a website form submission",
        "description": "Creates a submission for the form identified by api_key. Extra body fields become submission data. Reserved anti-spam fields: _gotcha (honeypot, must be empty) and _ts (epoch ms time trap). Browser HTML posts may receive a redirect when a thank-you URL is configured; programmatic clients should send Accept: application/json.",
        "parameters": [
          {
            "name": "Accept",
            "in": "header",
            "required": false,
            "description": "Prefer application/json for a JSON envelope. Browsers posting HTML forms may receive text/html or a 302.",
            "schema": {
              "type": "string",
              "default": "application/json"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitJsonBody"
              },
              "example": {
                "api_key": "YOUR_API_KEY",
                "name": "Ada Lovelace",
                "email": "ada@example.com",
                "message": "Hello from JSON"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/SubmitFormBody"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/SubmitMultipartBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submission accepted. JSON clients receive a success envelope; HTML clients may receive HTML.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmitSuccess"
                }
              }
            }
          },
          "302": {
            "description": "Browser POST redirected to the form thank-you URL.",
            "headers": {
              "Location": {
                "description": "Thank-you URL configured on the form.",
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              },
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "402": {
            "description": "Not enough monthly credits to accept the submission.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FailureEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Domain allowlist rejected the request origin.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FailureEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "Unknown or inactive api_key.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FailureEnvelope"
                }
              }
            }
          },
          "413": {
            "description": "Payload or file too large.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FailureEnvelope"
                }
              }
            }
          },
          "415": {
            "description": "Unsupported Content-Type.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FailureEnvelope"
                }
              }
            }
          },
          "422": {
            "description": "Validation failure (missing api_key or invalid fields).",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FailureEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Honor Retry-After and RateLimit.",
            "headers": {
              "RateLimit": {
                "description": "IETF RateLimit header (RFC 9331). Example: \"100;w=60\". Agents should throttle before this budget hits 0.",
                "schema": {
                  "type": "string"
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy for this form or IP, as advertised by the API.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Sent on 429 responses.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FailureEnvelope"
                }
              }
            }
          }
        }
      },
      "options": {
        "operationId": "submitFormCorsPreflight",
        "tags": [
          "Submit"
        ],
        "summary": "CORS preflight for submit",
        "description": "Allows browser clients to POST from other origins. Allowed methods: POST, OPTIONS. Allowed headers: Content-Type.",
        "responses": {
          "204": {
            "description": "Preflight succeeded.",
            "headers": {
              "Access-Control-Allow-Methods": {
                "schema": {
                  "type": "string",
                  "example": "POST, OPTIONS"
                }
              },
              "Access-Control-Allow-Headers": {
                "schema": {
                  "type": "string",
                  "example": "Content-Type"
                }
              },
              "Access-Control-Max-Age": {
                "schema": {
                  "type": "string",
                  "example": "86400"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SubmitJsonBody": {
        "type": "object",
        "required": [
          "api_key"
        ],
        "additionalProperties": true,
        "properties": {
          "api_key": {
            "type": "string",
            "description": "Public form API key from the FormsReach dashboard."
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "message": {
            "type": "string"
          },
          "_gotcha": {
            "type": "string",
            "description": "Honeypot. Leave empty. Non-empty values are treated as spam."
          },
          "_ts": {
            "type": "string",
            "description": "Time trap: Date.now() at page render. Very fast posts may be classified as spam."
          }
        }
      },
      "SubmitFormBody": {
        "type": "object",
        "required": [
          "api_key"
        ],
        "additionalProperties": {
          "type": "string"
        },
        "properties": {
          "api_key": {
            "type": "string"
          },
          "_gotcha": {
            "type": "string"
          },
          "_ts": {
            "type": "string"
          }
        }
      },
      "SubmitMultipartBody": {
        "type": "object",
        "required": [
          "api_key"
        ],
        "additionalProperties": true,
        "properties": {
          "api_key": {
            "type": "string"
          },
          "attachment": {
            "type": "string",
            "format": "binary",
            "description": "Optional file field. Any file input name is accepted subject to size and type limits."
          }
        }
      },
      "SubmitSuccess": {
        "type": "object",
        "description": "Success envelope. SDK helpers expose id and optional redirectUrl.",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "Submission id."
              },
              "redirectUrl": {
                "type": "string",
                "format": "uri",
                "description": "Thank-you URL when the form has a custom redirect."
              }
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "success"
            ]
          },
          "meta": {
            "type": "object",
            "properties": {
              "requestId": {
                "type": "string"
              }
            }
          }
        }
      },
      "FailureEnvelope": {
        "type": "object",
        "required": [
          "data",
          "status",
          "error"
        ],
        "properties": {
          "data": {
            "nullable": true,
            "example": null
          },
          "status": {
            "type": "string",
            "enum": [
              "failure"
            ]
          },
          "error": {
            "type": "object",
            "required": [
              "type",
              "title",
              "status",
              "code",
              "detail"
            ],
            "properties": {
              "type": {
                "type": "string",
                "format": "uri",
                "description": "Canonical error page on the marketing site.",
                "example": "https://formsreach.com/errors/missing_api_key"
              },
              "title": {
                "type": "string",
                "example": "Unprocessable Entity"
              },
              "status": {
                "type": "integer",
                "example": 422
              },
              "code": {
                "type": "string",
                "enum": [
                  "missing_api_key",
                  "form_not_found",
                  "domain_denied",
                  "validation_error",
                  "payload_too_large",
                  "unsupported_media_type",
                  "insufficient_credits",
                  "rate_limited",
                  "not_found"
                ],
                "example": "missing_api_key"
              },
              "detail": {
                "type": "string",
                "example": "Field api_key is required."
              },
              "hint": {
                "type": "string",
                "description": "How to resolve the error. Present on marketing error pages; may also appear on API responses."
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "requestId": {
                "type": "string",
                "nullable": true,
                "example": "req_e115bc18-5ba9-46f7-b6f7-f2b6f14e322b"
              },
              "docs": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  },
  "externalDocs": {
    "description": "FormsReach developer portal",
    "url": "https://formsreach.com/developers/"
  }
}