{
  "openapi": "3.1.0",
  "info": {
    "title": "Makeform Headless submissions",
    "version": "1.0.0",
    "description": "Submit answers from your own UI or server. The returned preferred URL is https://submit.makeform.ai/f/{endpointId}; this specification uses its same-origin fallback route on www.makeform.ai. Strict endpoints use each Form’s published field schema. Headless-native loose endpoints can learn fields. Creation/configuration use Makeform MCP. Backend keys are endpoint-scoped and are not Makeform management API credentials."
  },
  "servers": [
    {
      "url": "https://www.makeform.ai"
    }
  ],
  "paths": {
    "/api/headless/f/{endpointId}": {
      "parameters": [
        {
          "name": "endpointId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string",
            "pattern": "^hf_[A-Za-z0-9_-]{16,40}$"
          }
        }
      ],
      "post": {
        "operationId": "submitHeadlessForm",
        "summary": "Submit a completed response",
        "description": "With an Origin header, the origin must match allowed_origins (including scheme and port); browser requests do not use a secret key. Without Origin, Authorization: Bearer <backend key> is required. The endpoint must be enabled and the Form published. Backend submissions cannot satisfy respondent email OTP. Do not expose backend keys in client components. Send fields at the top level or inside responses; unknown fields fail in strict mode. _gotcha must be empty. Browser HTML may use _redirect to an allowed URL and receives 303 on success. Payload limit: 256 KiB. An enabled endpoint is not proof of stored responses; retain submissionId receipts. Page Form endpoints require strict mode; hosted CAPTCHA on a page Form rejects Headless submissions. Use the hosted page or a separate Headless-native Form for that case.",
        "security": [
          {
            "backendKey": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "Origin",
            "in": "header",
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "description": "Browser-set origin. If present, it must be allowed even if a backend key is also sent."
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            },
            "description": "Create once per submission intent; reuse the same key and body for retries. A conflicting reuse fails with 409."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "description": "Field names and value types come from the Form’s published schema. Reserved transport keys include _gotcha, _redirect, _session_id, _attribution."
              },
              "example": {
                "email": "person@example.com",
                "name": "Ada",
                "company_size": "1–10",
                "_gotcha": ""
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "description": "Field names and value types come from the Form’s published schema. Reserved transport keys include _gotcha, _redirect, _session_id, _attribution."
              },
              "example": {
                "email": "person@example.com",
                "name": "Ada",
                "company_size": "1–10",
                "_gotcha": ""
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "additionalProperties": true,
                "description": "Field names and value types come from the Form’s published schema. Reserved transport keys include _gotcha, _redirect, _session_id, _attribution."
              },
              "example": {
                "email": "person@example.com",
                "name": "Ada",
                "company_size": "1–10",
                "_gotcha": ""
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted, or intentional honeypot success. A stored response includes submissionId; honeypot success does not. Keyed retries return the original receipt.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    },
                    "submissionId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "endPageId": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "303": {
            "description": "HTML browser submission succeeded; navigate to an allowed redirect.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "400": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "options": {
        "operationId": "headlessPreflight",
        "summary": "Browser CORS preflight",
        "responses": {
          "204": {
            "description": "Origin allowed; methods POST/OPTIONS and Content-Type, X-Makeform-Session, Idempotency-Key headers."
          },
          "403": {
            "description": "Origin not allowed."
          },
          "404": {
            "description": "Endpoint unavailable."
          },
          "429": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Request rejected. Preserve answers; use code/fieldErrors and Retry-After when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/forms/{formId}/headless/schema": {
      "get": {
        "operationId": "readHeadlessSchema",
        "summary": "Read the published schema as a signed-in owner or authorized collaborator",
        "description": "This is the existing authenticated management route, not a public schema URL. Requires a signed-in Makeform session with canView on the Form. A Headless backend key does not authorize this route. Page Forms need an existing Headless endpoint. No API keys or Turnstile secrets are returned. Version is the Form updated_at timestamp.",
        "security": [
          {
            "ownerSession": []
          }
        ],
        "parameters": [
          {
            "name": "formId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Published field contract. Unpublished Forms can return an empty field list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "version",
                    "mode",
                    "empty",
                    "learningLimited",
                    "fields"
                  ],
                  "properties": {
                    "version": {
                      "type": "string"
                    },
                    "mode": {
                      "enum": [
                        "strict",
                        "loose"
                      ]
                    },
                    "empty": {
                      "type": "boolean"
                    },
                    "learningLimited": {
                      "type": "boolean"
                    },
                    "fields": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Field"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sign-in required."
          },
          "403": {
            "description": "No Form view permission."
          },
          "404": {
            "description": "Form or endpoint unavailable."
          },
          "500": {
            "description": "Could not read schema."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "backendKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Endpoint backend key (mfh_live_…). Create it in Headless settings. Keep it server-side; it is shown once."
      },
      "ownerSession": {
        "type": "apiKey",
        "in": "header",
        "name": "Cookie",
        "description": "Existing Supabase session cookies from a signed-in Makeform browser session; supplied by the browser with credentials. Cookie names and chunking are deployment-specific. This is not the Headless backend key."
      }
    },
    "schemas": {
      "Field": {
        "type": "object",
        "required": [
          "name",
          "label",
          "type",
          "valueType",
          "required",
          "hidden",
          "learned",
          "supported"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "description": "Makeform field type, e.g. email_input."
          },
          "valueType": {
            "enum": [
              "string",
              "number",
              "boolean",
              "string[]",
              "object"
            ]
          },
          "required": {
            "type": "boolean"
          },
          "hidden": {
            "type": "boolean"
          },
          "learned": {
            "type": "boolean"
          },
          "supported": {
            "type": "boolean"
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "constraints": {
            "type": "object",
            "additionalProperties": true
          },
          "conditional": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "const": false
          },
          "error": {
            "type": "string"
          },
          "code": {
            "enum": [
              "endpoint",
              "auth",
              "rate",
              "validation",
              "quota",
              "captcha",
              "email_verification",
              "schema_changed",
              "idempotency_conflict",
              "idempotency_unavailable",
              "unsupported",
              "service",
              "idempotency_key_invalid"
            ]
          },
          "fieldErrors": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "retryAfter": {
            "type": "number"
          }
        },
        "additionalProperties": true
      }
    }
  }
}
