Skip to content

This is the staging environment's documentation. It is a draft and may change at any time.

Authentication

Every API call carries an access token in the Authorization header:

Authorization: Bearer ACCESS_TOKEN

Tokens are issued by the OpenID Connect provider at https://auth-staging.fedwright.com. A client should read the provider’s endpoints from its discovery document at /.well-known/openid-configuration on the issuer, rather than build them.

Client How it signs in Where it is covered
Fedwright’s own web and mobile apps Authorization code flow with PKCE Not covered here
Machine clients (your own programs) JWT bearer grant (RFC 7523) with a private key This page

Third-party applications acting for users aren’t supported yet. The API accepts user tokens only from Fedwright’s own clients. A program you write signs in as a machine client for one organization.

Machine clients use no client secret. They hold a private key, and Fedwright holds the matching public key.

An organization owner creates a machine client with a name, the scopes it may use, and an RSA public key. Getting started shows the request. The response has two values the client needs:

Field Use
subject The identity that signs in. It is iss and sub of the assertion.
keys[].keyId The kid in the assertion’s header, for the key that signs it.

The machine client’s id is not its subject. Use subject in the assertion.

A machine client acts for the one organization it was created in. A request for another organization’s resource answers 404, the same as a resource that doesn’t exist.

A machine client’s status is provisioning, active, or revoked. Only an active client can sign in.

The client builds a JWT and signs it with its private key. The header:

Field Value
alg RS256
kid The keyId of the key that signs
typ JWT

The claims:

Claim Value
iss The machine client’s subject
sub The machine client’s subject
aud The issuer, https://auth-staging.fedwright.com
iat The current time, in seconds since the Unix epoch
exp At most 5 minutes after iat
jti A unique identifier for this assertion. Use a new random value each time.

Send the assertion to the issuer’s token endpoint. In this environment that is

https://auth-staging.fedwright.com/oauth/v2/token. The request is a form post:

Parameter Value
grant_type urn:ietf:params:oauth:grant-type:jwt-bearer
assertion The signed JWT
scope Space-separated scopes, below

The scope for this environment is:

openid urn:zitadel:iam:org:project:id:394731229330538643:aud urn:zitadel:iam:user:metadata
  • The first part is the scope that makes the token valid for the API. Use it as it is shown. Its project ID is the same for every client in this environment.
  • urn:zitadel:iam:user:metadata adds the organization and plan claims to the token. Always ask for it. Without it the token still works, but rate limits can’t be matched to your plan. A change of plan reaches a client when its next access token is issued, within 10 minutes.
POST /oauth/v2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=SIGNED_JWT&scope=...

The answer is a JSON object with access_token, token_type, and expires_in (seconds). It has no refresh token. When the token is about to expire, sign a new assertion and request a new one. Tokens last about 10 minutes in this environment; use expires_in rather than a fixed number, and renew a little before the end.

A rejected request gets an OAuth error response (RFC 6749, section 5.2) from the issuer, not a Problem Details body.

An operation requires one scope. The machine client’s scopes are the ones its owner chose when creating it. Tokens don’t carry scopes. The API looks up the client’s scopes for each request.

A machine client can hold only these scopes:

Scope Allows
ai:use The AI endpoints
matches:read Reading matches, follows, and outcomes
opportunities:read Notices, threads, changes, awards, search, and reference data
org:read Reading the organization, and its profile
searches:write Saved searches

Other scopes, such as org:manage, are for signed-in users. Operations that need them are not available to a machine client, and answer 403 with the Problem type client-not-allowed. The reference says which operations accept machine clients.

A call that needs a scope the client doesn’t hold answers 403 with the Problem type insufficient-scope. The WWW-Authenticate header names the missing scopes:

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
WWW-Authenticate: Bearer error="insufficient_scope", scope="searches:write"

An owner changes a client’s scopes with updateMachineClient, which needs If-Match. See Conditional requests. The change applies within 60 seconds.

To replace a key without an outage:

  1. Add a second public key with addMachineClientKey. A machine client holds one or two keys, and two only while a key is being replaced.
  2. Check that the client can get a token with the new key’s keyId.
  3. Remove the old key with removeMachineClientKey. At least one working key always stays.

revokeMachineClient revokes a client for good and deactivates its identity. The API stops accepting the client’s earlier tokens within 60 seconds.

These operations are for organization owners and take the owner’s user token, which must come with proof of a second factor. Without it the answer is 403 with the Problem type mfa-required. A machine client can’t call them.

Answer Meaning
401, Problem type unauthorized The token is missing, expired, or invalid, or the client was revoked or disabled.
403, insufficient-scope The client lacks the operation’s scope.
403, client-not-allowed The operation isn’t open to machine clients.
404, not-found The resource doesn’t exist, or belongs to another organization.

On a 401, request a new token and send the call again once. If it fails again, stop and check the client’s status and its key. Every error’s format is in Errors.

  • Keep the private key in a secret store. Don’t commit it, print it, or send it anywhere.
  • Don’t log access tokens or assertions.
  • Use one machine client per program, so you can revoke one without touching the others.