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.
| Screen | Scope | Allows |
|---|---|---|
| Responses, read | submissions:read | List and read submissions and their attachments, and list groups. |
| Responses, read and write | submissions:write | The above, plus POST /v1/flows/{flow_id}/submissions to push a submission in. |
| Registers, read | registers:read | List registers and their records. |
| Flows, read | flows:read | List flows, and read a flow, its schema, its source and its versions. |
| Flows, read and write | flows:write | The 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.