AnimationFunnel

Authentication

The AnimationFunnel REST API uses bearer tokens. Generate a key once, scope it appropriately, send it in the Authorization header on every request, and rotate it periodically. This page covers the full lifecycle — creating, scoping, using, testing, rotating, and revoking keys — plus the security pitfalls worth avoiding.

Overview

The AnimationFunnel REST API is the same API the dashboard itself uses. Anything you can do in the UI, you can do from code: create forms, publish snapshots, query submissions, re-score leads, dispatch integrations, manage team members.

Authentication is via bearer tokens — opaque API keys you generate in the dashboard and send on every request. No OAuth dance is required for the common case; OAuth is available on Enterprise for agencies that build on top of AnimationFunnel.

At a glance:

  • Auth schemeAuthorization: Bearer <key>
  • Protocol — HTTPS only. Plain HTTP is refused.
  • Format — JSON request bodies, JSON responses, RFC 7807 error bodies.
  • Keys are environment-scoped live vs. test.
  • Keys are permission-scoped — read-only, read-write, admin.
  • Keys are optionally IP-allowlisted.

Base URL & versioning

Every API call goes to:

text
https://api.animationfunnel.com/v1/...

The version prefix (/v1) is part of the URL. Breaking changes go into a new version; within a version, only backwards-compatible additions happen. When /v2 ships, /v1 continues to work for at least 24 months with a deprecation warning header.

Self-hosting? Replace api.animationfunnel.com with your instance's API hostname. Everything else on this page is identical.

API key types

Every key combines three axes: environment, scope, and (optionally) form attachment.

Environment — live vs. test

  • af_live_... — operates against production data. Real submissions, real integrations, real billing impact.
  • af_test_... — operates against a sandboxed workspace clone. Safe to use in CI and staging — no real submissions are created, no real integrations fire, no billing meters tick.

Scope — read-only, read-write, admin

  • read — GET requests only. Can read forms, submissions, analytics, integration logs. Cannot mutate anything.
  • read_write — full CRUD on forms, submissions, integrations. Cannot manage billing, API keys, or team members.
  • admin — full workspace control including keys, team, and billing. Treat as dangerous.

Attachment — workspace vs. form-scoped

  • Workspace keys — can access every form in the workspace. Default.
  • Form-scoped keys (Business+) — restricted to a specific form or folder. Useful for agencies running a single integration per client without risk of cross-access.

Create an API key

  1. Open Settings → API keys in your workspace.
  2. Click New key.
  3. Give it a descriptive name — production-backend, staging-ci, zapier-import. Names are mandatory because they show up in the audit log; future-you will thank present-you.
  4. Pick an environment (live or test).
  5. Pick a scope (read, read_write, admin). Use the least scope that works.
  6. Optionally restrict to a form or folder (Business+).
  7. Optionally set an expiration date — the key is auto-revoked on hit.
  8. Optionally add an IP allowlist (see IP allowlist).
  9. Hit Create. The key is shown once — copy it immediately.
The full key value is shown only once. If you lose it, there's no way to recover — generate a new one, migrate, and revoke the old one. Only the first and last 4 characters of the key are displayed afterward for identification.

Using the key

Send the key in the Authorization header on every request, using the Bearer scheme:

bash
curl https://api.animationfunnel.com/v1/forms \
  -H "Authorization: Bearer af_live_..."

A successful response

json
{
  "object": "list",
  "data": [
    {
      "id":          "frm_01HX...",
      "object":      "form",
      "slug":        "paid-search-lead",
      "status":      "published",
      "version":     12,
      "created_at":  "2026-03-01T10:00:00Z",
      "updated_at":  "2026-04-15T14:32:11Z"
    }
  ],
  "has_more": false
}

Authentication errors

Missing, malformed, or invalid keys return 401 Unauthorized:

json
{
  "type":    "authentication_error",
  "code":    "invalid_api_key",
  "message": "The API key provided is invalid or has been revoked.",
  "request_id": "req_01HZK9..."
}

Keys that exist but lack the required scope return 403 Forbidden with insufficient_scope.

Code examples

The same request — GET /v1/forms — across common languages:

Node.js (fetch)

js
const res = await fetch("https://api.animationfunnel.com/v1/forms", {
  headers: {
    Authorization: `Bearer ${process.env.ANIMATIONFUNNAL_API_KEY}`,
  },
});

if (!res.ok) {
  throw new Error(`API error: ${res.status} ${await res.text()}`);
}

const { data } = await res.json();
console.log(`Fetched ${data.length} forms`);

Python (requests)

python
import os
import requests

resp = requests.get(
    "https://api.animationfunnel.com/v1/forms",
    headers={"Authorization": f"Bearer {os.environ['ANIMATIONFUNNAL_API_KEY']}"},
    timeout=10,
)
resp.raise_for_status()

forms = resp.json()["data"]
print(f"Fetched {len(forms)} forms")

Go

go
req, _ := http.NewRequest(
    "GET",
    "https://api.animationfunnel.com/v1/forms",
    nil,
)
req.Header.Set("Authorization", "Bearer "+os.Getenv("ANIMATIONFUNNAL_API_KEY"))

resp, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

// ... decode JSON

Ruby

ruby
require "net/http"
require "json"

uri = URI("https://api.animationfunnel.com/v1/forms")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV['ANIMATIONFUNNAL_API_KEY']}"

res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
raise res.body unless res.is_a?(Net::HTTPSuccess)

puts JSON.parse(res.body)["data"].length

PHP

php
<?php
$ch = curl_init("https://api.animationfunnel.com/v1/forms");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Authorization: Bearer " . getenv("ANIMATIONFUNNAL_API_KEY"),
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($status !== 200) {
    throw new Exception("API error: $status $body");
}

$forms = json_decode($body, true)["data"];

Scopes & permissions

Each endpoint documents the minimum scope it requires. A rough map:

  • GET /v1/forms, GET /v1/submissions, GET /v1/analytics read
  • POST /v1/forms, PATCH /v1/forms/{id}, POST /v1/submissions/{id}/rescore read_write
  • POST /v1/api_keys, DELETE /v1/api_keys/{id}, POST /v1/team/invites admin

Call an endpoint without the needed scope and you'll get a 403 with code: insufficient_scope.

Test mode

Test-mode keys (af_test_...) are the safe way to integrate against AnimationFunnel from staging, CI, or local dev without risk of polluting production data.

  • Test keys operate against a sandboxed clone of your workspace with identical form definitions.
  • Submissions created in test mode are flagged as test: true and are segregated in the submissions view.
  • Integrations in test mode fire against mock endpoints — no real webhooks, no real Pipedrive deals, no real OpenRouter cost.
  • Rate limits in test mode are the same as production so limit-handling code can be exercised.
  • You can delete all test-mode data from Settings → Data & privacy → Reset test data.
Treat test keys as almost-as-sensitive as live keys. They can read your form definitions, so a leaked test key still reveals business logic. Don't commit either.

IP allowlist

Every key can optionally be restricted to a list of IPv4 or IPv6 addresses (or CIDR ranges). Requests from any other address return 403 Forbidden with code: ip_not_allowed.

text
Allow list examples:
  203.0.113.42            # single IP
  203.0.113.0/24          # subnet
  2001:db8::/32           # IPv6 subnet

Particularly valuable for server-side integrations where the caller's IP is stable. Set from the key's edit drawer in the dashboard.

Error responses

All errors follow RFC 7807 ("Problem Details for HTTP APIs") with a stable JSON shape:

json
{
  "type":       "<machine-readable error type>",
  "code":       "<specific error code>",
  "message":    "Human-readable description.",
  "request_id": "req_01HZK9...",
  "details":    { /* optional, endpoint-specific */ }
}

Error types

  • authentication_error (401) — missing, invalid, expired, or revoked key.
  • permission_error (403) — valid key with insufficient scope or wrong form attachment.
  • not_found (404) — resource doesn't exist (or isn't visible to this key).
  • validation_error (400 / 422) — request body failed validation. details lists per-field issues.
  • rate_limit_error (429) — rate limit hit. Honor the Retry-After header.
  • server_error (500–599) — something broke on our side. Retry with backoff.

Using the request ID

Every response (success and error) includes a request_id field and an X-Request-Id header. When contacting support, including the request ID lets us find the exact log entry in seconds. Log it on your side for every request so correlating issues is trivial.

Rate limits

Rate limits apply per key. Every response includes the current limit state as headers:

  • X-RateLimit-Limit — total requests allowed in the current window.
  • X-RateLimit-Remaining — how many you have left.
  • X-RateLimit-Reset — Unix timestamp of when the window resets.
  • Retry-After — on 429 responses, seconds to wait before retrying.

Default limits: 100 req/s per key, 10k req/min per workspace. Enterprise plans can raise both. See the dedicated rate limits guide for the full policy, including burst behavior.

Webhook signatures vs. API auth

A common point of confusion: the API uses bearer tokens for authentication, but webhooks use HMAC signatures for the opposite direction (AnimationFunnel → your server). These are two different mechanisms:

  • API keys — used when you call us. Sent in the Authorization header. Authenticates your identity.
  • Webhook signatures — used when we call you. Sent in the X-AnimationFunnel-Signature header. Proves our identity and payload integrity.

See the webhooks guide for how to verify a webhook signature. Never use your API key to verify a webhook — they serve different purposes.

OAuth (Enterprise)

For partners building integrations on top of AnimationFunnel — agencies that want to offer form-building to their clients without handling customer API keys — OAuth 2.0 is available on Enterprise plans.

The flow is standard three-legged OAuth with PKCE:

  1. Partner app redirects the user to https://animationfunnel.com/oauth/authorize?client_id=...&scope=....
  2. User grants consent; AnimationFunnel redirects back with a temporary code.
  3. Partner exchanges the code for an access token (expires) and a refresh token (long-lived).
  4. Partner calls the API with the access token as a bearer token, exactly like an API key.

Contact Enterprise sales to enroll as an OAuth partner.

Key rotation

Rotate long-lived keys at least once a quarter, and immediately if:

  • A laptop with access is lost, stolen, or decommissioned.
  • A team member with access leaves.
  • A dependency that handled the key is retired or compromised.
  • The key is accidentally committed to a repo, pasted into Slack, or logged somewhere indexable.

Zero-downtime rotation

  1. Create a new key alongside the old one, with the same scope.
  2. Deploy the new key to every consumer. AnimationFunnel accepts both during overlap.
  3. Watch the Last usedtimestamp on the old key until it's stale for 24 hours.
  4. Revoke the old key. The dashboard audit log records who revoked it and when.

Revocation

Click the trash icon next to any key in the API keys list. Revocation is immediate — the next request using that key returns 401 within seconds. Revoked keys cannot be restored; create a new one instead.

Every revocation is written to the workspace audit log with the revoking user, the key name, and a free-text reason prompt. Filling in the reason is optional but recommended — it makes future security reviews dramatically easier.

Security best practices

  • Never commit keys to git.Use environment variables, a secrets manager (Doppler, Vault, AWS Secrets Manager), or your platform's built-in secrets (Vercel, Netlify, Heroku config vars). A leaked key is a full breach.
  • Use the least scope that works. A script that only reads submissions should not have admin. Downgrade aggressively.
  • Never use a live key in client-side code. Bearer tokens belong server-side. If you need the browser to talk to AnimationFunnel, proxy through your own backend so the key never reaches the client.
  • Rotate quarterly. Treat keys like passwords. Stale keys are attack surface.
  • Use IP allowlists for stable-IP consumers. Server-to-server is usually stable; an allowlist makes key theft nearly useless to an attacker.
  • Use test keys in staging. Separate environments prevent staging bugs from touching production.
  • Log the request ID on every call.When something goes wrong, you'll want it.
  • Audit the keys list monthly.Stale keys (> 90 days unused) should be revoked; keys you don't recognize should be investigated immediately.
  • Never email a key. Use a secrets-sharing tool (1Password, Bitwarden Send) or a dedicated onboarding flow.
  • Revoke on exit. When a team member leaves, review every key they created and rotate or revoke. The audit log makes this mechanical.

FAQ

How many API keys can I have?

No hard limit. Create as many as you need — one per service, environment, or integration is a clean pattern. The value is in the name, scope, and last-used timestamp; those make audit possible.

I accidentally committed a key. What do I do?

  1. Revoke the key nowfrom the dashboard. Don't wait to force-push — the key is in the commit history, and crawlers scan public repos in seconds.
  2. Create a new key and deploy it.
  3. Rewrite git history to remove the commit, force-push, and invalidate any cached clones.
  4. Check the key's last-used log entries — if an IP or time looks unfamiliar, your data may have been accessed. Contact support.

Can I share a key with a teammate?

Technically yes; practically no. Shared keys break audit (you can't tell who did what). Instead, add your teammate to the workspace with an appropriate role; they then generate their own key.

I'm transferring a form to another workspace. What happens to keys?

Keys are workspace-scoped. After transfer, the receiving workspace's keys can access the form; the origin workspace's keys can no longer reach it. Form-scoped keys attached to the transferred form are automatically revoked; create new ones in the destination.

How do I debug an auth failure?

  1. Copy the failing curl command. Run it against a known-good endpoint (GET /v1/me) to isolate auth from endpoint-specific issues.
  2. Check the code field in the response body — invalid_api_key vs. insufficient_scope vs. ip_not_allowed each have different causes.
  3. Confirm the key value. A trailing newline from copy-paste is the most common culprit.
  4. Confirm the Authorization: Bearer prefix. The scheme name is case-sensitive.
  5. Log the X-Request-Idheader and send it to support if the above doesn't reveal the issue.

Next steps

Was this page helpful?