Skip to content

API Authentication

The Fencer REST API takes a bearer token in the Authorization header:

Authorization: Bearer <token>

Three kinds of token work:

TokenLooks likeActs asBest for
Service-account tokenfcr_…The account’s role, in its own organizationCI and unattended automation
Personal API keyfcr_…You, in each organization that approved the keyYour own scripts
OAuth 2.0 access tokenopaqueYou, limited by the token’s scopesLocal tools and MCP clients

In the browser, the Fencer web app uses your sign-in session instead. HTTP Basic authentication is not supported.

  • 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:

Terminal window
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.

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.

Terminal window
curl https://app.fencer.dev/.well-known/oauth-authorization-server

The response lists the authorization_endpoint (/oauth/authorize/), token_endpoint (/oauth/token/), revocation_endpoint (/oauth/revoke_token/), registration_endpoint (/oauth/register/), and scopes_supported.

Register with dynamic client registration (RFC 7591):

Terminal window
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 base64
import hashlib
import secrets
code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode()).digest()).rstrip(b"=").decode()

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=S256

URL-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:

ScopeAllows
cli:api:readRead requests to the REST API
cli:api:writeRead and write requests to the REST API
mcp:vulnerabilities:readThe remote MCP endpoint only

Whatever the scopes, the token can never do more than the signed-in user’s role allows.

Terminal window
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/callback

The response includes an access_token (valid for 15 minutes, see expires_in) and a refresh_token (valid for 30 days).

Terminal window
curl -H "Authorization: Bearer <access_token>" \
"https://app.fencer.dev/api/v1/org/acme/vulnerabilities/"

When the access token expires, exchange the refresh token for a new pair:

Terminal window
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:

Terminal window
curl -X POST https://app.fencer.dev/oauth/revoke_token/ \
-d token=<access_token> \
-d client_id=<client_id>

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 the Authorization: Bearer header, and refresh an expired OAuth access token.
  • Invalid service account token., Service account token has expired., Service account token has been revoked., or Service account is inactive. — create a new service account and use its token.
  • Personal API key has expired. or Personal 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>/.
  • 404 with 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 lacks cli:api:write for a write, or was granted only mcp: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_grant on the token exchange — the code_verifier does not match the challenge, the redirect_uri differs from the one you authorized with, or the code was already used.
  • 429 Too Many Requests on /oauth/register/ — client registration is limited to 30 requests per minute. Register once and reuse the client_id.