API Authentication
Overview
Section titled “Overview”The Fencer REST API takes a bearer token in the Authorization header:
Authorization: Bearer <token>Three kinds of token work:
| Token | Looks like | Acts as | Best for |
|---|---|---|---|
| Service-account token | fcr_… | The account’s role, in its own organization | CI and unattended automation |
| Personal API key | fcr_… | You, in each organization that approved the key | Your own scripts |
| OAuth 2.0 access token | opaque | You, limited by the token’s scopes | Local tools and MCP clients |
In the browser, the Fencer web app uses your sign-in session instead. HTTP Basic authentication is not supported.
Prerequisites
Section titled “Prerequisites”- A Fencer account with membership in the organization you want to call.
- For a service-account token or a personal API key: the token, created as described in API Access.
Service-account tokens and personal API keys
Section titled “Service-account tokens and personal API keys”Create the token under Settings > Service Accounts (service accounts) or User settings > API keys (personal API keys); see API Access. Then send it as a bearer token:
curl -H "Authorization: Bearer fcr_..." \ "https://app.fencer.dev/api/v1/org/acme/vulnerabilities/?page_size=10"- A service-account token can only reach its own organization, with the permissions of the role you gave the account.
- A personal API key acts with your own role, and only in organizations whose admins approved it.
It only works on organization-scoped URLs (
/api/v1/org/<organization_slug>/…) and on the remote MCP endpoint. - Neither kind of token can create or revoke credentials, or approve personal API key grants. Those need you signed in to the web app.
OAuth 2.0 with PKCE
Section titled “OAuth 2.0 with PKCE”fencer login and MCP clients use OAuth 2.0 Authorization Code with PKCE. Use it when you build a
local tool that signs a person in through their browser. Clients are public (no client secret), and
PKCE with S256 is required.
Step 1: Discover the endpoints
Section titled “Step 1: Discover the endpoints”curl https://app.fencer.dev/.well-known/oauth-authorization-serverThe response lists the authorization_endpoint (/oauth/authorize/), token_endpoint
(/oauth/token/), revocation_endpoint (/oauth/revoke_token/), registration_endpoint
(/oauth/register/), and scopes_supported.
Step 2: Register a client
Section titled “Step 2: Register a client”Register with dynamic client registration (RFC 7591):
curl -X POST https://app.fencer.dev/oauth/register/ \ -H "Content-Type: application/json" \ -d '{ "client_name": "My Tool", "redirect_uris": ["http://127.0.0.1:3334/callback"], "grant_types": ["authorization_code", "refresh_token"], "response_types": ["code"], "token_endpoint_auth_method": "none" }'Save the client_id from the response. A redirect URI must be a loopback address over HTTP
(127.0.0.1, [::1], or localhost, any port), a native app’s private-use scheme (such as
cursor://…), or an approved MCP client host. Other https:// redirect URIs are rejected with
invalid_redirect_uri.
Step 3: Create a PKCE verifier and challenge
Section titled “Step 3: Create a PKCE verifier and challenge”import base64import hashlibimport secrets
code_verifier = secrets.token_urlsafe(32)code_challenge = base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest()).rstrip(b"=").decode()Step 4: Send the user to authorize
Section titled “Step 4: Send the user to authorize”Open this URL in the user’s browser (one line, shown wrapped):
https://app.fencer.dev/oauth/authorize/?response_type=code &client_id=<client_id> &redirect_uri=http://127.0.0.1:3334/callback &scope=cli:api:read cli:api:write &state=<random_state> &code_challenge=<code_challenge> &code_challenge_method=S256URL-encode each value. After the user signs in and approves, Fencer redirects to your
redirect_uri with code and state. Check that state matches what you sent.
The scopes are:
| Scope | Allows |
|---|---|
cli:api:read | Read requests to the REST API |
cli:api:write | Read and write requests to the REST API |
mcp:vulnerabilities:read | The remote MCP endpoint only |
Whatever the scopes, the token can never do more than the signed-in user’s role allows.
Step 5: Exchange the code for tokens
Section titled “Step 5: Exchange the code for tokens”curl -X POST https://app.fencer.dev/oauth/token/ \ -d grant_type=authorization_code \ -d client_id=<client_id> \ -d code=<code> \ -d code_verifier=<code_verifier> \ -d redirect_uri=http://127.0.0.1:3334/callbackThe response includes an access_token (valid for 15 minutes, see expires_in) and a
refresh_token (valid for 30 days).
Step 6: Call the API
Section titled “Step 6: Call the API”curl -H "Authorization: Bearer <access_token>" \ "https://app.fencer.dev/api/v1/org/acme/vulnerabilities/"Refresh and revoke
Section titled “Refresh and revoke”When the access token expires, exchange the refresh token for a new pair:
curl -X POST https://app.fencer.dev/oauth/token/ \ -d grant_type=refresh_token \ -d client_id=<client_id> \ -d refresh_token=<refresh_token>Refresh tokens rotate: store the new refresh_token from each response, because the old one stops
working.
To sign out, revoke the token:
curl -X POST https://app.fencer.dev/oauth/revoke_token/ \ -d token=<access_token> \ -d client_id=<client_id>Troubleshooting
Section titled “Troubleshooting”A missing or rejected credential returns 403 Forbidden with a detail message:
Authentication credentials were not provided.(with"code": "not_authenticated") — the request had no token Fencer recognised. Check theAuthorization: Bearerheader, and refresh an expired OAuth access token.Invalid service account token.,Service account token has expired.,Service account token has been revoked., orService account is inactive.— create a new service account and use its token.Personal API key has expired.orPersonal API key has been revoked.— create a new key.Personal API keys can only authenticate organization-scoped requests.— call a URL under/api/v1/org/<organization_slug>/.404with a personal API key — the key’s grant for that organization is not approved yet, or was rejected or revoked.The provided token does not include the scope required for this request.— the OAuth token lackscli:api:writefor a write, or was granted onlymcp:vulnerabilities:read. Authorize again with the scopes you need.This operation requires an interactive session.— the action creates or approves credentials; do it in the web app.invalid_granton the token exchange — thecode_verifierdoes not match the challenge, theredirect_uridiffers from the one you authorized with, or the code was already used.429 Too Many Requestson/oauth/register/— client registration is limited to 30 requests per minute. Register once and reuse theclient_id.