Build on OneForm.
Connect OneForm to the rest of your firm's tools. Read forms and send them to clients over a REST API, get every submission, report and booking as a signed webhook, put a form on any website, and let AI agents work in OneForm on your behalf.
Quick start
Three steps from nothing to receiving every new submission at your own URL.
Make an API key
In OneForm, open Settings → API keys and make a key (owners and admins can). It starts with of_ and is shown once, so keep it somewhere safe, such as an environment variable:
export ONEFORM_API_KEY="of_…"Check the key
GET /me answers with the firm and key the call was made with.
curl https://app.oneform.si/api/v1/me \ -H "Authorization: Bearer $ONEFORM_API_KEY"const res = await fetch("https://app.oneform.si/api/v1/me", { headers: { Authorization: `Bearer ${process.env.ONEFORM_API_KEY}`, },});if (!res.ok) throw new Error(`OneForm answered ${res.status}: ${await res.text()}`);const data = await res.json();import osimport requests res = requests.get( "https://app.oneform.si/api/v1/me", headers={"Authorization": f"Bearer {os.environ['ONEFORM_API_KEY']}"}, timeout=30,)res.raise_for_status()data = res.json()<?php$ch = curl_init("https://app.oneform.si/api/v1/me");curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "GET", CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv("ONEFORM_API_KEY"), ], CURLOPT_RETURNTRANSFER => true,]);$data = json_decode(curl_exec($ch), true);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);package main import ( "fmt" "io" "net/http" "os") func main() { req, err := http.NewRequest("GET", "https://app.oneform.si/api/v1/me", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("ONEFORM_API_KEY")) res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() out, _ := io.ReadAll(res.Body) fmt.Println(res.Status, string(out))}require "net/http"require "json" uri = URI("https://app.oneform.si/api/v1/me")req = Net::HTTP::Get.new(uri)req["Authorization"] = "Bearer #{ENV.fetch('ONEFORM_API_KEY')}"res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }data = JSON.parse(res.body)Subscribe to an event
Give OneForm a public https URL and a trigger. From then on, every matching event is POSTed to it as JSON, signed with your firm's webhook secret.
curl -X POST https://app.oneform.si/api/v1/hooks \ -H "Authorization: Bearer $ONEFORM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target_url": "https://example.com/oneform/webhook", "trigger": "form_submitted"}'const res = await fetch("https://app.oneform.si/api/v1/hooks", { method: "POST", headers: { Authorization: `Bearer ${process.env.ONEFORM_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ "target_url": "https://example.com/oneform/webhook", "trigger": "form_submitted" }),});if (!res.ok) throw new Error(`OneForm answered ${res.status}: ${await res.text()}`);const data = await res.json();import osimport requests res = requests.post( "https://app.oneform.si/api/v1/hooks", headers={"Authorization": f"Bearer {os.environ['ONEFORM_API_KEY']}"}, json={ "target_url": "https://example.com/oneform/webhook", "trigger": "form_submitted", }, timeout=30,)res.raise_for_status()data = res.json()<?php$ch = curl_init("https://app.oneform.si/api/v1/hooks");curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_HTTPHEADER => [ "Authorization: Bearer " . getenv("ONEFORM_API_KEY"), "Content-Type: application/json", ], CURLOPT_POSTFIELDS => json_encode([ "target_url" => "https://example.com/oneform/webhook", "trigger" => "form_submitted", ]), CURLOPT_RETURNTRANSFER => true,]);$data = json_decode(curl_exec($ch), true);$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);package main import ( "fmt" "io" "net/http" "os" "strings") func main() { body := strings.NewReader(`{ "target_url": "https://example.com/oneform/webhook", "trigger": "form_submitted" }`) req, err := http.NewRequest("POST", "https://app.oneform.si/api/v1/hooks", body) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("ONEFORM_API_KEY")) req.Header.Set("Content-Type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer res.Body.Close() out, _ := io.ReadAll(res.Body) fmt.Println(res.Status, string(out))}require "net/http"require "json" uri = URI("https://app.oneform.si/api/v1/hooks")req = Net::HTTP::Post.new(uri)req["Authorization"] = "Bearer #{ENV.fetch('ONEFORM_API_KEY')}"req["Content-Type"] = "application/json"req.body = JSON.generate({ "target_url" => "https://example.com/oneform/webhook", "trigger" => "form_submitted",})res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }data = JSON.parse(res.body)Next: verify the signature, then see every endpoint.
What you can build with
At a glance
- Base URL
https://app.oneform.si/api/v1- Authentication
Authorization: Bearer of_…orX-API-Key- Rate limit
- 120 calls a minute per key; 429 with Retry-After over it
- Format
- JSON over HTTPS. Errors are { error, code }
- Version
- v1, in the path. OpenAPI 3.1
- Webhooks
- Signed with HMAC-SHA256, retried up to 8 tries
- Limits per firm
- 25 API keys, 100 subscriptions, 300 form invites a day
- AI agents
- MCP at
https://app.oneform.si/mcp
Which credential to use
| Credential | For | Belongs to | Made in |
|---|---|---|---|
API key of_… | The REST API and Zapier | The firm (acts for the admin who made it) | Settings → API keys |
Agent token ofp_… | The MCP server | One person in one firm | Settings → AI agents |
OAuth tokens ofa_… / ofr_… | The MCP server, for AI tools that sign in | One person in one firm | Sign in with OneForm |
API keys don't work on the MCP server, and agent tokens don't work on the REST API.