Authentication
Every request needs an API key or an OAuth 2.0 access token with the right permission, sent as a Bearer token in the Authorization header.
API keys
Every request needs a key with the right permission. Keys belong to one company and can be limited to some of its businesses. The full key is shown once when you create it; we keep only a hash. Rotating a key gives you a new one and the old one keeps working for 24 hours.
Permissions
| Field | Type | Description |
|---|---|---|
| reviews:read | scope | Read reviews, replies and proof type |
| score:read | scope | Read ProofScore and rating breakdown |
| replies:write | scope | Reply to reviews as your business |
| invitations:write | scope | Create and cancel invitations |
| invitations:read | scope | Read invitation status |
| products:write | scope | Read, create and update products |
| webhooks:manage | scope | Manage webhook endpoints |
OAuth 2.0 access tokens
Prefer short-lived tokens? Every key is also an OAuth 2.0 client (client credentials, RFC 6749). POST your client ID (shown next to the key, pwc_…) and the key as client secret to /oauth/token. You get an access token that works like the key for 60 minutes; send it as a Bearer token. Ask for fewer permissions with scope (space-separated). Tokens stop when the key is revoked, blocked, rotated or expires.
| Field | Type | Description |
|---|---|---|
| grant_typerequired | string | Always client_credentials |
| client_id + client_secretrequired | Basic / form | Your client ID and the key, as HTTP Basic (preferred) or as client_id and client_secret form fields |
| scope | string | Optional: space-separated permissions, a subset of the key's |
| access_token | string | The token (pwt_…); send it as Bearer |
| expires_in | integer | Seconds until it expires (at most 3600) |
| error | string | invalid_client (401), invalid_request, unsupported_grant_type, invalid_scope, unauthorized_client (400) |
IP allowlist
A key can be limited to your servers' IP addresses or CIDR ranges (up to 20) in Settings → Developers. Requests and token requests from any other address are refused with 403 ip_not_allowed.
Key expiry
New keys expire after 365 days (or 90 days); only the company Owner can choose a key that never expires. We email the person who created the key 14 days before. An expired key gets 401 key_expired.
Security
- Never put keys in website or app code that people can see: call the API from your server.
- Send the key or token only in the Authorization header. The API never accepts cookies, or keys in the address or the body.
- Give each system its own key with only the permissions it needs, and revoke keys you no longer use.
- Rotate keys at least once a year; new keys expire after a year anyway.
- Public widgets don't need a key.
- Credentials in the address or the request body are refused (400 credentials_in_url). If that happened, rotate the key.
- 20 failed authentications from one address in a minute block that address for the rest of the minute (429).
- Send JSON bodies with Content-Type: application/json, up to 64 KB. Browsers can't call the API (no CORS): call it from your server.
OpenAPI 3.1
The whole API as an OpenAPI 3.1 document: every endpoint, parameter, response, error and webhook event. Import it into Postman, Insomnia or a code generator.
Download openapi.jsonGET /v1/reviews HTTP/1.1
Host: api.proofwell.io
Authorization: Bearer pk_live_4f…a91c
# Missing or wrong key
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Proofwell API", error="invalid_token"
{"error":{"code":"invalid_key","message":"API key not recognised","request_id":"req_4Hq1x2"}}curl -X POST "https://api.proofwell.io/v1/oauth/token" \
-u "$PROOFWELL_CLIENT_ID:$PROOFWELL_KEY" \
-d grant_type=client_credentials \
--data-urlencode "scope=reviews:read score:read"
# → {"access_token":"pwt_…","token_type":"Bearer","expires_in":3600,"scope":"…"}
curl "https://api.proofwell.io/v1/reviews" -H "Authorization: Bearer $ACCESS_TOKEN"POST /v1/oauth/token HTTP/1.1
Host: api.proofwell.io
Authorization: Basic cHdjXzhmM2E…
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=reviews%3Aread
HTTP/1.1 200 OK
Cache-Control: no-store
{
"access_token": "pwt_Qm9…Xw",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "reviews:read"
}
# Wrong client ID or key
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Proofwell API"
{
"error": "invalid_client",
"error_description": "Client authentication failed."
}