A workload identity defines which external OpenID Connect tokens a workload can use and which Flint scopes it can receive. The identity stores the trusted issuer, audience, subject, and claim conditions. Creating one returns its configuration, with no secret to store. Use workload identity token exchange to exchange a matching issuer token for a short-lived Flint access token.
Manage identities#
Listing and retrieving identities requires accounts.workload_identities.read. Creating, updating, and archiving requires accounts.workload_identities.write. These scopes are available in test and live mode. They are separate from accounts.api_keys.read and accounts.api_keys.write, and partner apps cannot request them.
An identity belongs to the authenticated environment. A test credential manages identities only in its own sandbox; a live credential manages live identities. Send no mode or sandbox_id in the request. An ID outside that environment returns 404 RESOURCE_NOT_FOUND with param: "workload_identity_id", the same as an unknown ID.
API keys and CLI OAuth callers can grant only scopes they already hold in that environment. Trying to grant more returns 403 INSUFFICIENT_SCOPE; details[0].missing_scopes names the extra scopes. Dashboard owners and admins can manage identities. Creating, reactivating, or widening a live identity through the Dashboard requires fresh identity verification. If verification is needed, the request returns 403 IDENTITY_VERIFICATION_REQUIRED.
curl -X POST https://api.withflintpay.com/v1/workload-identities \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub deployment",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:example/store:ref:refs/heads/main",
"claim_conditions": [
{"claim": "repository_owner_id", "value": "12345678", "match": "exact"},
{"claim": "repository_id", "value": "87654321", "match": "exact"}
],
"scopes": ["commerce.orders.read"]
}'
Flint checks the issuer's discovery document when creating the identity. Each merchant can have at most 100 non-archived identities across all environments.
GET /v1/workload-identities returns a cursor-paginated list. Set page_size from 1 to 100, with 20 returned by default, and pass next_page_token as the next request's page_token. Use status to filter by active, inactive, or archived; the default excludes archived identities. The timestamp filters are created_after, created_before, updated_after, and updated_before, with inclusive lower bounds and exclusive upper bounds. Sort with sort_by=created_at, last_used_at, or name, and sort_direction=asc or desc. The default is created_at descending, with workload_identity_id descending to break ties.
The identity resource#
| Field | Behavior |
|---|---|
workload_identity_id | The identity's ID, beginning with wid_. |
name | Your label, from 1 to 100 characters. |
status | active, inactive, or archived. New identities are active. |
mode | test or live, from the authenticated environment. Read-only. |
sandbox_id | The test identity's sandbox. Omitted on live identities. Read-only. |
platform | A computed, extensible description of the issuer's platform. Treat unfamiliar values as valid. Read-only. |
issuer | The exact trusted issuer URL. Immutable after creation. |
audience | The audience the external token must have. Defaults to Flint's issuer URL, https://api.withflintpay.com on the production API. Immutable after creation. |
subject | The required subject or subject pattern, from 1 to 1,024 characters. |
subject_match | exact by default, or glob. |
claim_conditions | Up to 10 distinct conditions. Every condition has claim, value, and match; match defaults to exact and also accepts glob. All conditions must match. |
scopes | At least one scope the identity can grant. Replacing this array replaces the whole set. |
created_by | A reference with resource_type (user, api_key, or workload_identity) and resource_id, identifying who created the identity. Read-only. |
last_used_at | When the identity was last used. Omitted if it has never been used. |
created_at, updated_at | Creation and last-change times, as RFC 3339 timestamps. |
archived_at | The archive time. Omitted before the identity is archived. |
version | The current version. Send it as expected_version to require the version you reviewed. |
Issuer and claim matching#
The issuer must be an HTTPS URL with a DNS host, at most 255 characters long. Only port 443 is allowed; omit credentials, query parameters, and fragments. Flint compares the issuer exactly. The issuer must expose public OpenID Connect discovery and signing keys; private network addresses and redirects are rejected.
Claim names refer to literal top-level scalar claims. Conditions cannot use the registered JWT claims iss, sub, aud, exp, nbf, iat, or jti; configure the issuer, subject, and audience through their own fields.
In a glob match, * matches characters other than :. A pattern can contain at most three * characters and cannot start with one. If the subject or any claim condition uses a glob, include at least one exact claim condition.
Shared issuers also require an exact condition that pins the workload to your tenant:
| Issuer | Required exact condition |
|---|---|
| GitHub Actions | repository_owner_id |
| GitLab.com | namespace_id |
| Buildkite | organization_id |
| Vercel | owner_id |
| CircleCI | oidc.circleci.com/org-id |
For Google Cloud's https://accounts.google.com issuer, use an exact numeric subject and no glob conditions. Microsoft issuers must identify a specific tenant; common, organizations, and consumers issuers are rejected.
For GitHub Actions, also pin repository_id to keep a renamed or recreated repository from matching only by its name.
Update and archive#
PATCH /v1/workload-identities/{workload_identity_id} accepts name, status, subject, subject_match, claim_conditions, and scopes. Omitted fields keep their current values. Arrays replace their current contents; send "claim_conditions": [] to remove all conditions when the resulting matching policy allows it. Include the current expected_version whenever you send claim_conditions or scopes. It is optional for scalar-only changes. An update that changes nothing preserves version and updated_at.
Set status: "inactive" to deactivate an identity, or status: "active" to reactivate it. A stale expected_version returns 409 VERSION_CONFLICT; retrieve the current identity, review the changes, and send its version with your next update.
curl -X PATCH https://api.withflintpay.com/v1/workload-identities/wid_01KWJ93G11C7MF8REX91MDS0CD \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expected_version": 1, "status": "inactive"}'
DELETE /v1/workload-identities/{workload_identity_id} archives the identity and returns the resource with status: "archived" and archived_at set. You can pass expected_version as a query parameter to require the version you reviewed. Archiving is permanent; create a new identity if you need to replace it. An archived identity no longer counts toward the merchant's limit. Archived identities remain readable by ID and appear in lists with status=archived.
