Skip to content

Developers

The form backend you don’t have to build

Validation, conditional logic, file handling, scoring, storage and delivery — behind one clean REST API.

Scoped API keys

forms:read, forms:write, responses:read, responses:write. Keys are shown once and stored as keyed hashes.

Forms as JSON

Version-controlled definitions: sections, items, conditions and approval rules in one readable document.

Signed webhooks

response.created, response.approval_changed and response.deleted — HMAC-SHA256 signed, retried with backoff.

Headless forms

Fetch a public form schema and render it in your own UI; submit to the same validation engine.

Predictable limits

Per-key rate limits with Retry-After headers and clear 402 errors when a plan limit is reached.

Safe by default

Strict input validation, encrypted storage and no browser access for API keys (CORS-restricted).

1. Create

Create a form from a template or JSON

bash
curl -X POST https://create.abaworks.dev/api/v1/forms \
  -H "Authorization: Bearer $CAF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "template": "contact-form", "title": "Website contact" }'

2. Read

List approved responses

python
import requests

API = "https://create.abaworks.dev/api/v1"
headers = {"Authorization": f"Bearer {API_KEY}"}

page = requests.get(f"{API}/forms/{FORM_ID}/responses", headers=headers,
                    params={"approval": "approved", "limit": 100}).json()
for r in page["data"]:
    print(r["id"], r["answers"]["email"], r["approval"]["percent"])

3. React

Verify webhook signatures in a few lines

javascript
import crypto from "node:crypto";

// Verify a webhook from Create a Form (Express example)
app.post("/webhooks/caf", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("x-createaform-signature") ?? "";
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = crypto
    .createHmac("sha256", process.env.CAF_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`)
    .digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  if (!fresh || !crypto.timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(expected, "hex"))) {
    return res.sendStatus(400);
  }
  const event = JSON.parse(req.body);
  console.log(event.type, event.data.response.approval?.status);
  res.sendStatus(200);
});

Ship your next form in an afternoon.

Free plan includes API access. No credit card required.