Skip to content

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

FieldTypeDescription
reviews:readscopeRead reviews, replies and proof type
score:readscopeRead ProofScore and rating breakdown
replies:writescopeReply to reviews as your business
invitations:writescopeCreate and cancel invitations
invitations:readscopeRead invitation status
products:writescopeRead, create and update products
webhooks:managescopeManage 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.

POST/oauth/token
FieldTypeDescription
grant_typerequiredstringAlways client_credentials
client_id + client_secretrequiredBasic / formYour client ID and the key, as HTTP Basic (preferred) or as client_id and client_secret form fields
scopestringOptional: space-separated permissions, a subset of the key's
access_tokenstringThe token (pwt_…); send it as Bearer
expires_inintegerSeconds until it expires (at most 3600)
errorstringinvalid_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.json
GET /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"
Token endpoint
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."
}

Cookie settings

EssentialLog in, security and fraud checks
PreferencesRemember your country
AnalyticsNot used. We'll ask before we ever add any.