Authentication
Every API call carries an access token in the Authorization header:
Authorization: Bearer ACCESS_TOKENTokens 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.
Who can sign in
Section titled “Who can sign in”| 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.
The machine client
Section titled “The machine client”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 signed assertion
Section titled “The signed assertion”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. |
Requesting a token
Section titled “Requesting a token”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:metadataadds 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.1Content-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.
Scopes
Section titled “Scopes”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 ForbiddenContent-Type: application/problem+jsonWWW-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.
Rotating and revoking keys
Section titled “Rotating and revoking keys”To replace a key without an outage:
- Add a second public key with
addMachineClientKey. A machine client holds one or two keys, and two only while a key is being replaced. - Check that the client can get a token with the new key’s
keyId. - 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.
Errors
Section titled “Errors”| 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.
Keeping credentials safe
Section titled “Keeping credentials safe”- 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.