Skip to content

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

Getting started

This guide takes a program from nothing to a first API call. The program signs in as a machine client: a service identity that calls the API for one organization, with a key pair instead of a password. Examples use curl and openssl.

Applications that act for users on their own are not supported yet. The API accepts user tokens only from Fedwright’s own clients. A program that runs by itself, on behalf of one organization, uses a machine client.

Setting Value
API base URL https://api-staging.fedwright.com/v1
Interactive docs https://api-staging.fedwright.com/docs/
Issuer (sign-in) https://auth-staging.fedwright.com
Environment’s scope openid urn:zitadel:iam:org:project:id:394731229330538643:aud

Every path in these guides is relative to the API base URL. The scope names the API as the audience of your token. Its project ID is the same for every client in this environment, so use the scope as it is shown.

  • An organization, and a person with the owner role in it. Only an owner creates machine clients.
  • curl, openssl, and jq.

You keep the private key. Fedwright stores only the public key. The key must be RSA, 2048 bits or longer.

Terminal window
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out client-private.pem
openssl pkey -in client-private.pem -pubout -out client-public.pem

Keep client-private.pem out of source control and logs.

An organization owner creates the machine client with createMachineClient. The request needs:

  • The owner’s user token. A machine client can’t call this operation. It answers 403 with the Problem type client-not-allowed.
  • Proof of a second factor with the owner’s sign-in. Without it, the answer is 403 with mfa-required.
  • An Idempotency-Key header. See Idempotency.

Choose the scopes the client may use from opportunities:read, org:read, searches:write, matches:read, and ai:use. Authentication says what each allows.

The example grants three: opportunities:read for the first call below, org:read for the conditional request samples, and searches:write for the saved-search samples there and in Idempotency. Grant only what your program needs.

Create the machine client
export API_BASE='https://api-staging.fedwright.com/v1'
export ORG_ID='YOUR_ORGANIZATION_ID'
export OWNER_TOKEN='OWNER_USER_ACCESS_TOKEN'
curl -sS "$API_BASE/orgs/$ORG_ID/machine-clients" \
-H "Authorization: Bearer $OWNER_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg pem "$(cat client-public.pem)" \
'{name: "Nightly import", scopes: ["opportunities:read", "org:read", "searches:write"], publicKeyPem: $pem}')"

The answer is 201 with the machine client. Keep two values from it:

  • subject: the identity your token requests name as iss and sub. It is not the client’s id.
  • keys[0].keyId: the kid you put in the header of each signed assertion.

The id is the client’s identifier in the API’s own paths, such as /orgs/{orgId}/machine-clients/{id}.

The client proves its identity by signing a short JWT, the assertion, with its private key, and exchanging it for an access token. Authentication explains each field. The script below builds the assertion with openssl and asks for a token.

Exchange a signed assertion for a token
export ISSUER='https://auth-staging.fedwright.com'
export SUBJECT='SUBJECT_FROM_THE_CREATE_RESPONSE'
export KEY_ID='KEY_ID_FROM_THE_CREATE_RESPONSE'
export SCOPE='openid urn:zitadel:iam:org:project:id:394731229330538643:aud urn:zitadel:iam:user:metadata'
enc() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
header=$(jq -nc --arg kid "$KEY_ID" '{alg: "RS256", typ: "JWT", kid: $kid}' | enc)
now=$(date +%s)
claims=$(jq -nc --arg sub "$SUBJECT" --arg aud "$ISSUER" --arg jti "$(openssl rand -hex 16)" \
--argjson now "$now" \
'{iss: $sub, sub: $sub, aud: $aud, iat: $now, exp: ($now + 300), jti: $jti}' | enc)
signature=$(printf '%s' "$header.$claims" \
| openssl dgst -sha256 -sign client-private.pem -binary | enc)
token=$(curl -fsS "$ISSUER/oauth/v2/token" \
-d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
--data-urlencode "scope=$SCOPE" \
--data-urlencode "assertion=$header.$claims.$signature" | jq -r .access_token)

The token request asks for the API’s audience scope and for urn:zitadel:iam:user:metadata. Without the second scope the token still works, but it carries no organization or plan claim, so rate limits can’t be applied to your organization’s plan.

Send the token as a bearer token. This call lists two notices and needs opportunities:read.

List notices
curl -sS "https://api-staging.fedwright.com/v1/notices?limit=2" \
-H "Authorization: Bearer $token"

The answer is a page of results, { "items": [...], "nextCursor": "..." }. Follow nextCursor for the next page. See Pagination.

When a call fails, the body says why. See Errors.