{
  "openapi": "3.1.0",
  "info": {
    "title": "OneForm API",
    "version": "1",
    "summary": "Forms, prefilled links, form invites and REST-hook subscriptions for one firm.",
    "description": "The OneForm public API. A firm's owner or admin makes an API key in Settings → API keys; the key decides the firm, and everything it reads or changes is that firm's.\n\nRate limits: 120 calls a minute per key, shared across every server; more than 20 calls a minute with a wrong key from one address also get 429. A 429 carries Retry-After in seconds.\n\nEvents reach subscribers as webhooks (see `webhooks`), signed with the firm's webhook signing secret. Retried up to 8 tries; two 410 Gone answers in a row (2) remove the subscription.\n\nGuides: https://oneform.si/developers/api",
    "contact": {
      "name": "OneForm",
      "url": "https://oneform.si/developers",
      "email": "hello@oneform.si"
    }
  },
  "externalDocs": {
    "description": "Developer docs",
    "url": "https://oneform.si/developers"
  },
  "servers": [
    {
      "url": "https://app.oneform.si/api/v1",
      "description": "OneForm"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Which firm and key a call is made with."
    },
    {
      "name": "Triggers",
      "description": "The events a subscription can listen to, and sample payloads."
    },
    {
      "name": "Forms",
      "description": "The firm's forms, prefilled links and form invites."
    },
    {
      "name": "Hooks",
      "description": "REST-hook subscriptions: OneForm POSTs each event to the target URL."
    },
    {
      "name": "Meta",
      "description": "This document."
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "summary": "Test the key",
        "description": "Which firm and key this is. Use it to test a connection and to label it.",
        "responses": {
          "200": {
            "description": "The firm and the key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                },
                "example": {
                  "firm": {
                    "id": "00000000-0000-4000-8000-0000000000aa",
                    "name": "Lee & Partners CPA"
                  },
                  "key": {
                    "id": "00000000-0000-4000-8000-0000000000bb",
                    "name": "Zapier",
                    "prefix": "of_AbC123"
                  }
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/triggers": {
      "get": {
        "operationId": "listTriggers",
        "tags": [
          "Triggers"
        ],
        "summary": "List triggers",
        "description": "Every trigger a subscription can listen to: the workflow triggers that can fire, as the workflow builder offers them.",
        "responses": {
          "200": {
            "description": "The triggers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Trigger"
                  }
                },
                "example": [
                  {
                    "key": "form_submitted",
                    "label": "Form is submitted",
                    "hint": "Runs as soon as someone finishes the form.",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/form_submitted/sample"
                  },
                  {
                    "key": "partial_submission",
                    "label": "Form is left unfinished",
                    "hint": "Runs once when someone answers some questions, then stops for an hour without submitting. Stops if they finish later.",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/partial_submission/sample"
                  },
                  {
                    "key": "report_approved",
                    "label": "Report is approved",
                    "hint": "Runs when a reviewer approves the report.",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/report_approved/sample"
                  },
                  {
                    "key": "report_sent",
                    "label": "Report is sent",
                    "hint": "Runs after the report email goes out.",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/report_sent/sample"
                  },
                  {
                    "key": "report_opened",
                    "label": "Report is opened",
                    "hint": "Runs the first time the client opens the report email or their report page (once per report).",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/report_opened/sample"
                  },
                  {
                    "key": "payment_received",
                    "label": "Payment received",
                    "hint": "Runs when someone pays for a report or a consultation (Stripe).",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/payment_received/sample"
                  },
                  {
                    "key": "booking_made",
                    "label": "Call is booked",
                    "hint": "Runs when someone books a call on the thank-you screen (once per submission).",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/booking_made/sample"
                  },
                  {
                    "key": "booking_rescheduled",
                    "label": "Call is rescheduled",
                    "hint": "Runs each time a booked call moves to a new time, by the client or your team.",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/booking_rescheduled/sample"
                  },
                  {
                    "key": "booking_cancelled",
                    "label": "Call is cancelled",
                    "hint": "Runs when a booked call is cancelled, by the client or your team.",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/booking_cancelled/sample"
                  },
                  {
                    "key": "lead_hot",
                    "label": "Lead becomes Hot",
                    "hint": "Runs once per person, the first time their lead score reaches Hot (checked when they submit a form, or open or click an email).",
                    "sample_url": "https://app.oneform.si/api/v1/triggers/lead_hot/sample"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/triggers/{trigger}/sample": {
      "get": {
        "operationId": "getTriggerSample",
        "tags": [
          "Triggers"
        ],
        "summary": "Sample event",
        "description": "One sample event, as a one-item array: the firm's latest submission (of form_id, when given) shaped as that trigger, or made-up data when there is none yet.",
        "parameters": [
          {
            "name": "trigger",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "form_submitted",
                "partial_submission",
                "report_approved",
                "report_sent",
                "report_opened",
                "payment_received",
                "booking_made",
                "booking_rescheduled",
                "booking_cancelled",
                "lead_hot"
              ]
            }
          },
          {
            "name": "form_id",
            "in": "query",
            "required": false,
            "description": "Only this form's submissions.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A one-item array.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EventPayload"
                  },
                  "minItems": 1,
                  "maxItems": 1
                },
                "example": [
                  {
                    "event": "form_submitted",
                    "form": {
                      "id": "00000000-0000-4000-8000-000000000001",
                      "title": "Tax check-up",
                      "url": "https://app.oneform.si/f/tax-check-up"
                    },
                    "response": {
                      "id": "00000000-0000-4000-8000-000000000002",
                      "submitted_at": "2026-10-01T15:04:05.000Z",
                      "device": "mobile",
                      "lang": "en",
                      "ending": "default"
                    },
                    "respondent": {
                      "name": "Maya Lee",
                      "first_name": "Maya",
                      "last_name": "Lee",
                      "email": "maya@example.com",
                      "phone": "+1 555 0100",
                      "company": ""
                    },
                    "answers": [
                      {
                        "id": "contact",
                        "question": "Your details",
                        "type": "contact_group",
                        "answer": "First name: Maya · Last name: Lee · Email: maya@example.com · Phone: +1 555 0100"
                      },
                      {
                        "id": "income",
                        "question": "Roughly what was your income last year?",
                        "type": "short_text",
                        "answer": "$120,000"
                      },
                      {
                        "id": "goal",
                        "question": "What would you like help with?",
                        "type": "long_text",
                        "answer": "Lower my taxes next year."
                      }
                    ],
                    "fields": {
                      "contact": "First name: Maya · Last name: Lee · Email: maya@example.com · Phone: +1 555 0100",
                      "income": "$120,000",
                      "goal": "Lower my taxes next year."
                    },
                    "variables": {},
                    "hidden": {
                      "utm_source": "newsletter"
                    },
                    "report": null
                  }
                ]
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "404": {
            "description": "`form_not_found`: No form with that id in this firm (another firm's ids look the same as missing ones). `unknown_trigger`: GET /triggers/{trigger}/sample for a trigger that doesn't exist or isn't available.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No form with that id in this firm (another firm's ids look the same as missing ones).",
                  "code": "form_not_found"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/forms": {
      "get": {
        "operationId": "listForms",
        "tags": [
          "Forms"
        ],
        "summary": "List forms",
        "description": "The firm's forms, newest first, at most 500. There is no pagination in v1.",
        "responses": {
          "200": {
            "description": "The forms.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Form"
                  },
                  "maxItems": 500
                },
                "example": [
                  {
                    "id": "00000000-0000-4000-8000-000000000001",
                    "title": "Tax check-up",
                    "status": "published",
                    "created_at": "2026-10-01T09:00:00.000Z",
                    "slug": "tax-check-up",
                    "url": "https://app.oneform.si/f/tax-check-up",
                    "hidden_fields": [
                      {
                        "key": "client_id",
                        "label": "Client ID"
                      }
                    ]
                  }
                ]
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/forms/{id}": {
      "get": {
        "operationId": "getForm",
        "tags": [
          "Forms"
        ],
        "summary": "Get a form",
        "description": "One form, with the hidden fields its link accepts.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The form's id (a UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The form.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Form"
                },
                "example": {
                  "id": "00000000-0000-4000-8000-000000000001",
                  "title": "Tax check-up",
                  "status": "published",
                  "created_at": "2026-10-01T09:00:00.000Z",
                  "slug": "tax-check-up",
                  "url": "https://app.oneform.si/f/tax-check-up",
                  "hidden_fields": [
                    {
                      "key": "client_id",
                      "label": "Client ID"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "404": {
            "description": "`form_not_found`: No form with that id in this firm (another firm's ids look the same as missing ones).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No form with that id in this firm (another firm's ids look the same as missing ones).",
                  "code": "form_not_found"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/forms/{id}/links": {
      "post": {
        "operationId": "createPrefilledLink",
        "tags": [
          "Forms"
        ],
        "summary": "Create a prefilled link",
        "description": "A link to the form with hidden values (and the language) filled in. Nothing is stored and nothing is sent.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The form's id (a UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hidden": {
                    "type": "object",
                    "description": "Hidden-field values: only the keys the form declares (hidden_fields) and utm_source, utm_medium, utm_campaign, utm_term, utm_content are kept; values are cut to 300 characters. Numbers and booleans are sent as text.",
                    "additionalProperties": {
                      "type": [
                        "string",
                        "number",
                        "boolean",
                        "null"
                      ],
                      "maxLength": 2000
                    },
                    "propertyNames": {
                      "maxLength": 100
                    }
                  },
                  "lang": {
                    "type": "string",
                    "maxLength": 10,
                    "description": "The form's language: en, fr, es. Other values are ignored."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "hidden": {
                  "client_id": "TD-1042",
                  "utm_source": "taxdome"
                },
                "lang": "en"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The link. `ignored` lists the keys the form doesn't take.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PrefilledLink"
                },
                "example": {
                  "url": "https://app.oneform.si/f/tax-check-up?client_id=TD-1042&utm_source=taxdome&lang=en",
                  "form_id": "00000000-0000-4000-8000-000000000001",
                  "published": true,
                  "hidden": {
                    "client_id": "TD-1042",
                    "utm_source": "taxdome"
                  },
                  "ignored": []
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: The body isn't the JSON the endpoint expects.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The body isn't the JSON the endpoint expects.",
                  "code": "invalid_request"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "404": {
            "description": "`form_not_found`: No form with that id in this firm (another firm's ids look the same as missing ones).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No form with that id in this firm (another firm's ids look the same as missing ones).",
                  "code": "form_not_found"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/forms/{id}/send": {
      "post": {
        "operationId": "sendForm",
        "tags": [
          "Forms"
        ],
        "summary": "Email a form",
        "description": "Emails someone a link to the form, in the firm's branding. The form must be published. At most 300 invites a day per firm; one person gets a given form at most once a day. Unsubscribed or bounced addresses are skipped, not errors.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The form's id (a UUID).",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 320
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Full name; the first word is used in the greeting when first_name is missing."
                  },
                  "first_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "hidden": {
                    "type": "object",
                    "description": "Hidden-field values: only the keys the form declares (hidden_fields) and utm_source, utm_medium, utm_campaign, utm_term, utm_content are kept; values are cut to 300 characters. Numbers and booleans are sent as text.",
                    "additionalProperties": {
                      "type": [
                        "string",
                        "number",
                        "boolean",
                        "null"
                      ],
                      "maxLength": 2000
                    },
                    "propertyNames": {
                      "maxLength": 100
                    }
                  },
                  "lang": {
                    "type": "string",
                    "maxLength": 10,
                    "description": "The form's language: en, fr, es. Other values are ignored."
                  },
                  "message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000,
                    "description": "A short note shown in the email."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "email": "maya@example.com",
                "first_name": "Maya",
                "hidden": {
                  "client_id": "TD-1042"
                },
                "message": "Here's the short check-up we talked about."
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Sent, or skipped and why.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormInvite"
                },
                "example": {
                  "ok": true,
                  "status": "sent",
                  "email_id": "00000000-0000-4000-8000-0000000000cc",
                  "url": "https://app.oneform.si/f/tax-check-up?client_id=TD-1042",
                  "ignored": []
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: The body isn't the JSON the endpoint expects.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The body isn't the JSON the endpoint expects.",
                  "code": "invalid_request"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "404": {
            "description": "`form_not_found`: No form with that id in this firm (another firm's ids look the same as missing ones).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No form with that id in this firm (another firm's ids look the same as missing ones).",
                  "code": "form_not_found"
                }
              }
            }
          },
          "409": {
            "description": "`form_not_published`: POST /forms/{id}/send for a form that isn't published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "POST /forms/{id}/send for a form that isn't published.",
                  "code": "form_not_published"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After. `invite_limit_reached`: Over the firm's form invites for the day. Retry-After says when it resets (midnight UTC).",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          },
          "502": {
            "description": "`send_failed`: The invite email couldn't be sent. Try again in a few minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The invite email couldn't be sent. Try again in a few minutes.",
                  "code": "send_failed"
                }
              }
            }
          }
        }
      }
    },
    "/hooks": {
      "get": {
        "operationId": "listHooks",
        "tags": [
          "Hooks"
        ],
        "summary": "List subscriptions",
        "description": "The subscriptions made with this key, newest first.",
        "responses": {
          "200": {
            "description": "The subscriptions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Hook"
                  }
                },
                "example": [
                  {
                    "id": "00000000-0000-4000-8000-000000000005",
                    "trigger": "form_submitted",
                    "target_url": "https://example.com/oneform/webhook",
                    "form_id": null,
                    "created_at": "2026-10-01T09:00:00.000Z"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createHook",
        "tags": [
          "Hooks"
        ],
        "summary": "Subscribe",
        "description": "Subscribes a URL to a trigger, for one form or all of them. At most 100 subscriptions per firm.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "target_url"
                ],
                "properties": {
                  "target_url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 2000,
                    "description": "A public https URL. Its address is checked again at every delivery."
                  },
                  "trigger": {
                    "type": "string",
                    "enum": [
                      "form_submitted",
                      "partial_submission",
                      "report_approved",
                      "report_sent",
                      "report_opened",
                      "payment_received",
                      "booking_made",
                      "booking_rescheduled",
                      "booking_cancelled",
                      "lead_hot"
                    ]
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "form_submitted",
                      "partial_submission",
                      "report_approved",
                      "report_sent",
                      "report_opened",
                      "payment_received",
                      "booking_made",
                      "booking_rescheduled",
                      "booking_cancelled",
                      "lead_hot"
                    ],
                    "description": "Accepted in place of trigger."
                  },
                  "form_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "Only this form's events. Leave out for every form."
                  }
                }
              },
              "example": {
                "target_url": "https://example.com/oneform/webhook",
                "trigger": "form_submitted"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The subscription. Keep its id to unsubscribe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hook"
                },
                "example": {
                  "id": "00000000-0000-4000-8000-000000000005",
                  "trigger": "form_submitted",
                  "target_url": "https://example.com/oneform/webhook",
                  "form_id": null,
                  "created_at": "2026-10-01T09:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_request`: The body isn't the JSON the endpoint expects. `unknown_trigger`: POST /hooks with a trigger that doesn't exist or isn't available. `invalid_target`: target_url isn't a public https URL of at most 2,000 characters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The body isn't the JSON the endpoint expects.",
                  "code": "invalid_request"
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "404": {
            "description": "`form_not_found`: No form with that id in this firm (another firm's ids look the same as missing ones).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No form with that id in this firm (another firm's ids look the same as missing ones).",
                  "code": "form_not_found"
                }
              }
            }
          },
          "409": {
            "description": "`too_many_hooks`: The firm already has the most subscriptions it can have.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm already has the most subscriptions it can have.",
                  "code": "too_many_hooks"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/hooks/{id}": {
      "delete": {
        "operationId": "deleteHook",
        "tags": [
          "Hooks"
        ],
        "summary": "Unsubscribe",
        "description": "Removes a subscription made with this key, with its queued deliveries.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "const": true
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "401": {
            "description": "`unauthorized`: No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No key, a wrong key, or a revoked key (also when the person who made the key no longer manages the firm's integrations).",
                  "code": "unauthorized"
                }
              }
            }
          },
          "403": {
            "description": "`firm_inactive`: The firm's account is suspended or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The firm's account is suspended or closed.",
                  "code": "firm_inactive"
                }
              }
            }
          },
          "404": {
            "description": "`not_found`: No subscription with that id made with this key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No subscription with that id made with this key.",
                  "code": "not_found"
                }
              }
            }
          },
          "429": {
            "description": "`rate_limited`: Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Over the key's calls per minute, or too many calls with a wrong key from one address. See Retry-After.",
                  "code": "rate_limited"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "Meta"
        ],
        "summary": "This document",
        "description": "The OpenAPI description of this API. No key needed.",
        "security": [],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "event": {
      "post": {
        "operationId": "receiveEvent",
        "summary": "An event, sent to a subscription's target_url",
        "description": "OneForm POSTs each event to every matching subscription. Verify X-OneForm-Signature against the raw body before parsing it, and dedupe on X-OneForm-Delivery: a delivery can arrive twice.\n\nNetwork errors, timeouts, 408, 425, 429 and 5xx answers are retried after 1, 2, 4… minutes (at most an hour apart; Retry-After is honoured), up to 8 tries. Any other 4xx fails at once. 2 answers of 410 Gone in a row remove the subscription.",
        "parameters": [
          {
            "name": "X-OneForm-Event",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "form_submitted",
                "partial_submission",
                "report_approved",
                "report_sent",
                "report_opened",
                "payment_received",
                "booking_made",
                "booking_rescheduled",
                "booking_cancelled",
                "lead_hot"
              ]
            }
          },
          {
            "name": "X-OneForm-Delivery",
            "in": "header",
            "required": true,
            "description": "Unique per delivery; also sent as Idempotency-Key.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-OneForm-Hook",
            "in": "header",
            "required": true,
            "description": "The subscription's id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-OneForm-Signature",
            "in": "header",
            "required": true,
            "description": "t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with the firm's webhook signing secret>",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventPayload"
              },
              "example": {
                "event": "form_submitted",
                "form": {
                  "id": "00000000-0000-4000-8000-000000000001",
                  "title": "Tax check-up",
                  "url": "https://app.oneform.si/f/tax-check-up"
                },
                "response": {
                  "id": "00000000-0000-4000-8000-000000000002",
                  "submitted_at": "2026-10-01T15:04:05.000Z",
                  "device": "mobile",
                  "lang": "en",
                  "ending": "default"
                },
                "respondent": {
                  "name": "Maya Lee",
                  "first_name": "Maya",
                  "last_name": "Lee",
                  "email": "maya@example.com",
                  "phone": "+1 555 0100",
                  "company": ""
                },
                "answers": [
                  {
                    "id": "contact",
                    "question": "Your details",
                    "type": "contact_group",
                    "answer": "First name: Maya · Last name: Lee · Email: maya@example.com · Phone: +1 555 0100"
                  },
                  {
                    "id": "income",
                    "question": "Roughly what was your income last year?",
                    "type": "short_text",
                    "answer": "$120,000"
                  },
                  {
                    "id": "goal",
                    "question": "What would you like help with?",
                    "type": "long_text",
                    "answer": "Lower my taxes next year."
                  }
                ],
                "fields": {
                  "contact": "First name: Maya · Last name: Lee · Email: maya@example.com · Phone: +1 555 0100",
                  "income": "$120,000",
                  "goal": "Lower my taxes next year."
                },
                "variables": {},
                "hidden": {
                  "utm_source": "newsletter"
                },
                "report": null
              }
            }
          }
        },
        "responses": {
          "410": {
            "description": "The subscription is over: after 2 in a row it is removed."
          },
          "2XX": {
            "description": "Received. Any 2xx counts as delivered."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer of_… (an API key from Settings → API keys)."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "The same API key, in X-API-Key."
      }
    },
    "headers": {
      "RetryAfter": {
        "description": "Seconds to wait before trying again.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "code"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "A sentence to show a person."
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_request",
              "unknown_trigger",
              "invalid_target",
              "unauthorized",
              "firm_inactive",
              "not_found",
              "form_not_found",
              "too_many_hooks",
              "form_not_published",
              "rate_limited",
              "invite_limit_reached",
              "send_failed"
            ],
            "description": "Stable; branch on this."
          }
        }
      },
      "Me": {
        "type": "object",
        "required": [
          "firm",
          "key"
        ],
        "properties": {
          "firm": {
            "type": "object",
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "key": {
            "type": "object",
            "required": [
              "id",
              "name",
              "prefix"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "prefix": {
                "type": "string",
                "description": "The key's first characters, to tell keys apart."
              }
            }
          }
        }
      },
      "Trigger": {
        "type": "object",
        "required": [
          "key",
          "label",
          "hint",
          "sample_url"
        ],
        "properties": {
          "key": {
            "type": "string",
            "enum": [
              "form_submitted",
              "partial_submission",
              "report_approved",
              "report_sent",
              "report_opened",
              "payment_received",
              "booking_made",
              "booking_rescheduled",
              "booking_cancelled",
              "lead_hot"
            ]
          },
          "label": {
            "type": "string"
          },
          "hint": {
            "type": "string"
          },
          "sample_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Form": {
        "type": "object",
        "required": [
          "id",
          "title",
          "status",
          "created_at",
          "slug",
          "url",
          "hidden_fields"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "slug": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The form's public link."
          },
          "hidden_fields": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "key",
                "label"
              ],
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "PrefilledLink": {
        "type": "object",
        "required": [
          "url",
          "form_id",
          "published",
          "hidden",
          "ignored"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "form_id": {
            "type": "string",
            "format": "uuid"
          },
          "published": {
            "type": "boolean",
            "description": "Only published forms load for visitors."
          },
          "hidden": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "The values the link carries."
          },
          "ignored": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Keys that were dropped because the form doesn't declare them."
          }
        }
      },
      "FormInvite": {
        "type": "object",
        "required": [
          "ok",
          "status",
          "url",
          "ignored"
        ],
        "properties": {
          "ok": {
            "const": true
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "skipped"
            ]
          },
          "reason": {
            "type": "string",
            "description": "Why it was skipped (unsubscribed, bounced, already sent today, the email is switched off)."
          },
          "email_id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "ignored": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Hook": {
        "type": "object",
        "required": [
          "id",
          "trigger",
          "target_url",
          "form_id",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "trigger": {
            "type": "string",
            "enum": [
              "form_submitted",
              "partial_submission",
              "report_approved",
              "report_sent",
              "report_opened",
              "payment_received",
              "booking_made",
              "booking_rescheduled",
              "booking_cancelled",
              "lead_hot"
            ]
          },
          "target_url": {
            "type": "string",
            "format": "uri"
          },
          "form_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EventPayload": {
        "type": "object",
        "description": "The same JSON a workflow's Send a webhook step sends with no custom body. report is null when there is none; booking appears only with a booked call, partial only for partial_submission. A sold report that isn't paid for has summary null and highlights [].",
        "required": [
          "event",
          "form",
          "response",
          "respondent",
          "answers",
          "fields",
          "variables",
          "hidden",
          "report"
        ],
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "form_submitted",
              "partial_submission",
              "report_approved",
              "report_sent",
              "report_opened",
              "payment_received",
              "booking_made",
              "booking_rescheduled",
              "booking_cancelled",
              "lead_hot"
            ]
          },
          "form": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "response": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "submitted_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "device": {
                "type": "string"
              },
              "lang": {
                "type": "string"
              },
              "ending": {
                "type": "string"
              }
            }
          },
          "respondent": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "first_name": {
                "type": "string"
              },
              "last_name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "company": {
                "type": "string"
              }
            }
          },
          "answers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "question": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "answer": {
                  "type": "string"
                }
              }
            }
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Answers by question id."
          },
          "variables": {
            "type": "object",
            "additionalProperties": true
          },
          "hidden": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "report": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "summary": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "highlights": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "review_url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "booking": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "starts_at": {
                "type": "string",
                "format": "date-time"
              },
              "ends_at": {
                "type": "string",
                "format": "date-time"
              },
              "duration_min": {
                "type": "integer"
              },
              "time_zone": {
                "type": "string"
              },
              "location": {
                "type": "string"
              },
              "join_url": {
                "type": "string"
              },
              "manage_url": {
                "type": "string"
              },
              "invitee": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "time_zone": {
                    "type": "string"
                  }
                }
              },
              "cancel_reason": {
                "type": "string"
              },
              "changed_by": {
                "type": "string",
                "enum": [
                  "client",
                  "team"
                ]
              },
              "previous_starts_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "partial": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "last_answer_at": {
                "type": "string",
                "format": "date-time"
              },
              "last_question": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
