Skip to main content

Authentication

The bearer header​

Every request under /v1 carries an API key in the Authorization header:

curl -sS -H "Authorization: Bearer hop_sk_..." https://api.hopforms.com/v1/me

There is no other way in: no cookies, no session, no query-string token.

Keys and where they come from​

Mint a key from your organisation's settings, on the API page (/app/settings/api). The secret is shown once, at minting; Hop does not store it and cannot show it to you again. If you lose it, revoke the key and mint a new one.

A key's permissions are not fixed for its life. An admin who already holds a permission can grant it to an existing key, or take it away, from the same API settings screen; a grant can never exceed what the admin making it holds. The change applies on the key's next request, since Hop checks a key's scopes fresh on every request rather than caching them.

Scopes​

The settings screen groups permissions by resource, not by the names the API uses below. Choose "Full access" for every scope, or "Only select permissions..." to pick a level per resource.

ScreenScopeAllows
Responses, readsubmissions:readList and read submissions and their attachments, and list groups.
Responses, read and writesubmissions:writeThe above, plus POST /v1/flows/{flow_id}/submissions to push a submission in.
Registers, readregisters:readList registers and their records.
Flows, readflows:readList flows, and read a flow, its schema, its source and its versions.
Flows, read and writeflows:writeThe above, plus create, update, publish, unpublish and delete flows, create a version, and set or clear a flow's record action.

Weigh flows:write before you grant it. It can make a flow public, which claims that flow's keywords in the one public keyword space every Hop organisation shares, and it can delete a flow together with every submission collected through it. There is no undo.

What a refusal looks like​

A key that is missing, malformed, revoked or expired gets the same 401, with a WWW-Authenticate: Bearer realm="hop" header:

curl -sS -i https://api.hopforms.com/v1/me

The body is application/problem+json, the same shape every /v1 error uses:

{
"type": "https://docs.hopforms.com/api/errors#unauthorized",
"title": "Unauthorized",
"status": 401,
"code": "unauthorized",
"detail": "Missing, malformed, unknown, revoked, or expired API key.",
"request_id": "req_example_0001"
}

A key that is valid but lacks the scope an endpoint needs gets a 403; the body and the WWW-Authenticate header both name the missing scope. A key that is over its allowance gets a 429, with Retry-After in seconds. See Errors for the full shape and every code.

Rate limits​

Each key gets 300 reads and 60 writes a minute. A request that carries no valid key gets 20 requests a minute per client address, so an unauthenticated probe cannot exhaust a key's own allowance. Every response the API itself answers, successful or not, carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset, so you can watch the allowance without waiting for a 429. A request refused before it reaches the API, such as a body over the size limit or one whose body does not parse as JSON, carries none.

On a 429, wait the number of seconds in Retry-After, then retry the exact same request. Need more for a legitimate integration? Contact us and we raise the allowance on that key.

Key hygiene​

Keep the key in a curl config file, not on the command line. A key typed as an argument lands in your shell history and in ps output for other users on the machine.

umask 077
cat > ~/.hop-api.conf <<'EOF'
header = "Authorization: Bearer hop_sk_..."
EOF

Use curl --config ~/.hop-api.conf from there on.

Never add -L to a call against an endpoint that answers with a redirect, such as an attachment or a flow's source. A current curl drops Authorization when a redirect crosses to another host, but older releases (before the fix for CVE-2018-1000007) did not, --location-trusted turns the dropping off, and not every HTTP client library strips it. A signed object-store URL is not a host that should ever see your bearer key, so make the two-step fetch the habit; it protects you whatever client sends the request. Fetch the redirecting path first, read Location out of the response, then fetch that URL on its own with no Authorization header:

curl -sS -i --config ~/.hop-api.conf \
"https://api.hopforms.com/v1/flows/{flow_id}/submissions/{submission_id}/attachments/{attachment_id}"
# read Location from the response, then:
curl -sS -o attachment "$LOCATION"

A key belongs on a server, never in a browser tab or a mobile app bundle. Anything that ships to a device an end user holds can be read back out of it. Rotate a key by minting a new one, switching your systems over, then revoking the old one, so nothing goes dark in between. If you suspect a key has leaked, do it in the other order: revoke first, mint after. An expiry date is optional at minting; the screen defaults to one year and labels "No expiry" as not recommended, and a key with none is valid until you revoke it. The API settings screen keeps an audit trail for each key: when it was minted, its scopes, and its last use.