Start free
Developers

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:

Shell
export ONEFORM_API_KEY="of_…"

Check the key

GET /me answers with the firm and key the call was made with.

curl
curl https://app.oneform.si/api/v1/me \  -H "Authorization: Bearer $ONEFORM_API_KEY"
Node
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();
Python
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
<?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);
Go
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))}
Ruby
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
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"}'
Node
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();
Python
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
<?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);
Go
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))}
Ruby
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_… or X-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

CredentialForBelongs toMade in
API key of_…The REST API and ZapierThe firm (acts for the admin who made it)Settings → API keys
Agent token ofp_…The MCP serverOne person in one firmSettings → AI agents
OAuth tokens ofa_… / ofr_…The MCP server, for AI tools that sign inOne person in one firmSign in with OneForm

API keys don't work on the MCP server, and agent tokens don't work on the REST API.

Questions about the API, webhooks or embeds? Ask a developer.