Ana içeriğe geç

API Clients Management API

Overview​

An API client is a machine credential owned by a consumer or an application — never a subject in its own right. API Clients covers the concept, the Manager screen, and the client-secret storage modes; this page documents the Management API surface for the same object.

Every address on this page is under /apiops/projects/{projectName} and follows the shared Common Contract: the stable error envelope, correlation IDs, bounded pagination, and Idempotency-Key on every create. A caller outside the project's scope always gets 404 resourceNotFound, never 403. API clients are project-owned, so the admin (platform) project has no clients of its own. The one exception is the client of a PORTAL application, which is filed under the administration project: see Administration path.

References on this surface are portable. A client is addressed by its apiClientKey — a public, installation-wide identifier, distinct from the Mongo id and from the gateway-facing clientId a token request presents. Its owner travels as {type, key}: type is CONSUMER or APPLICATION, and key is that owner's own public name — a consumer's username or an application's name — the same reference a limit assignment's subject.key already uses, so one client's owner.key matches its owner's rows on the sibling surfaces. An organization travels as its code.

No reveal endpoint

A generated secret is returned exactly once, by POST .../secret-versions, under no-store. There is no endpoint on this surface to show a stored secret again — that is a Manager-screen operation only: it demands a written reason, writes an audit row, and is rate-limited, none of which has a meaning for an unattended pipeline. A caller that lost the value rotates again; see Idempotency for why a replay cannot hand it out a second time either.

Endpoints​

No access endpoints

An API client has no access setting of its own: it is authorized by the ACL rows of its ACL owner, which are managed on the Access Control screen and with the credential access endpoints. The authorization-restrictions and entitlements endpoints of earlier versions are removed. Per-client narrowing no longer exists: an API client that must not have its owner's reach is suspended or revoked.

API Clients — /api-clients​

MethodPathDescription
GET/api-clientsList the project's API clients, by clientId by default
GET/api-clients/{apiClientKey}One API client; never carries secret material
POST/api-clientsCreate an ACTIVE client with its first secret and deploy it to the gateways (201 + Location, no-store)
PUT/api-clients/{apiClientKey}Replace the editable half of a client; the version must match
POST/api-clients/{apiClientKey}/actions/suspendPause an ACTIVE client, reversibly
POST/api-clients/{apiClientKey}/actions/resumeReturn a SUSPENDED client to service
POST/api-clients/{apiClientKey}/actions/revokeEnd a client permanently and destroy its material — irreversible (200)
Draft lifecycle removed

A client is no longer created as a DRAFT. The POST /api-clients/{apiClientKey}/actions/activate endpoint (answers 404) and the draft discard DELETE /api-clients/{apiClientKey} (answers 405) of earlier versions are removed. To take a client out of service, suspend or revoke it.

Secret Generations — /api-clients/{apiClientKey}/secret-versions​

MethodPathDescription
POST/api-clients/{apiClientKey}/secret-versionsRotate: add a new generation (201 + Location, no-store); a generated value is returned once
PUT/api-clients/{apiClientKey}/secret-versions/{versionId}Configure one generation: replace its label and expiry (200)
POST/api-clients/{apiClientKey}/secret-versions/{versionId}/actions/revokeDestroy one generation now, including the active one

Administration path​

A portal application belongs to no project, so its API clients answer on /apiops/projects/admin/api-clients/... only. On a project path such a client answers 404 resourceNotFound and is left out of the list, also when an older record still carries that project. GET, POST and PUT, the three lifecycle actions and the secret generation endpoints all work there for the clients of PORTAL applications.

  • Who passes. System Administrator, Portal Business and Portal Manager. The two portal roles reach clients of PORTAL applications only; a client of an INTERNAL application answers them 404 resourceNotFound. A System Administrator reaches all of them. Portal Developer and every other caller get 404 resourceNotFound.
  • Owner. On this path owner.key (and the ownerKey filter) takes the application's applicationKey or its name. The key is unique and wins. A name that two applications share is 400 malformedRequest (params.field = owner.key, params.reason = applicationNameAmbiguous): send the applicationKey.

Owner Views (read-only)​

MethodPathDescription
GET/api-clients/{apiClientKey}/limit-assignmentsEvery limit assignment whose subject is this client
Not paged

The endpoint above returns a plain array — not the paged envelope GET /api-clients uses. Each answers a bounded set that belongs to one client.

List API clients​

GET /api-clients?ownerType&ownerKey&state&search&page&size&sort sorts by clientId,asc by default; sort may also name operationalState or expiresAt. name is still accepted as a sort field and is read as clientId. size is capped at 200, same as Pagination.

ParameterTypeMeaning
ownerTypeenumCONSUMER or APPLICATION — required together with ownerKey
ownerKeystringThe owner's own public key (a consumer's username or an application's name) — required together with ownerType
stateenumACTIVE | SUSPENDED | REVOKED (DRAFT only matches clients left from earlier versions)
searchstringCase-insensitive clientId / description contains-search

ownerKey without ownerType is 400 malformedRequest (params.reason = ownerKeyRequiresOwnerType); ownerType without ownerKey is the mirror (params.reason = ownerTypeRequiresOwnerKey).

Ownerless listing is intentional

Unlike the equivalent Manager screen, ownerType/ownerKey are optional here: the project is already authorized from the path, and the answer is bounded by the standard page size — a caller reconciling a whole project's clients has no single owner to name. The owner filter remains available and, when given, is resolved to an id first, so an unknown owner is 404 resourceNotFound rather than an empty page.

Idempotency​

POST /api-clients and POST /api-clients/{apiClientKey}/secret-versions each require an Idempotency-Key header (see Common Contract → Idempotency); a missing or blank header is 400 idempotencyKeyRequired. The key is claimed per project and per operation, together with a canonical hash of the request body:

SituationResult
New keyThe client is created with its first secret, or the secret generation is issued
Same key, same body, first call already finished409 idempotencyReplayExpired — params.resource names the apiClientKey (create) or the versionId (secret generation) the first call produced; read it back with GET instead
Same key, same body, first call still in flight409 idempotencyInProgress
Same key, a different body409 idempotencyKeyReused
A replayed secret is never handed out twice

The dedup record for POST /api-clients and POST .../secret-versions stores only the outcome's identity — the generation's versionId and the client's new version — never the generated value. A replayed key answers 409, not the original response; store the secret from the first successful call, because it cannot be retrieved again.

Request Body​

Create / update an API client — POST / PUT /api-clients/{apiClientKey}​

Both share one body. POST creates the client active, with its first secret, and deploys it to the gateways at once — there is no draft to equip and activate later. On PUT, the management key (apiClientKey) and the clientId are immutable — a body that omits either keeps the stored value, a body that changes either is 409 lifecycleConflict. The owner is not editable through this body: an owner sent on update is accepted syntactically and ignored, the stored owner is always restored. An absent organizationCode on update keeps the stored organization rather than detaching the client.

FieldTypeRequiredMeaning
ownerobjectCreate: yes. Update: no (kept){type, key} — see Owner reference below
organizationCodestringNoThe owning organization's code; absent on update keeps the stored one, an explicit blank clears it
descriptionstringNo
clientIdstringNoBlank on create mints one; immutable afterwards
validFrom / expiresAttimestampNoValidity window
allowedGrantTypesstring[]No
tokenEndpointAuthMethodstringNonone makes a public client (browser login): it is created active without a secret
redirectUrisstring[]NoBrowser-login redirect URIs of a public client; absent on update keeps the stored list
tokenIssuanceProfilestringNo
tokenNeverExpiresbooleanNo
tokenExpiresInAmountintegerNoPaired with tokenExpiresInUnit
tokenExpiresInUnitenumNoMILLI_SECONDS | SECONDS | MINUTES | HOURS | DAYS | WEEKS | MONTHS | YEARS
refreshTokenAllowedbooleanNo
refreshTokenCountintegerNo
refreshTokenExpiresInAmountintegerNoPaired with refreshTokenExpiresInUnit
refreshTokenExpiresInUnitenumNoSame values as tokenExpiresInUnit
jwtSignatureAlgorithmstringNo
secretobjectPOST only, optionalThe first secret, in the shape of POST .../secret-versions and under the same rules. Absent, a value is generated and returned once. Refused for a public client, and on PUT (400 malformedRequest, params.field = secret, params.reason = secretOnCreateOnly)
versionintegerPUT onlyThe stored version the caller expects to be updating

A body missing entirely on POST/PUT is 400 malformedRequest (params.reason = bodyRequired). A create whose body carries no owner at all is 400 malformedRequest (params.field = owner, params.reason = ownerRequired). A first secret that cannot be used now (already expired, or starting later) is refused before anything is written.

A client has no display name on this surface; it is identified by its clientId. A name sent in the body is accepted and ignored, never a 400.

Owner reference​

FieldTypeMeaning
typeenumCONSUMER or APPLICATION
keystringThe owner's own public key: a consumer's username, or an application's name

Lifecycle action body​

Shared by suspend / resume / revoke on the client, and by actions/revoke on a secret generation:

FieldTypeRequiredMeaning
versionintegerYesThe stored version the caller expects; a missing value is 400 malformedRequest (params.field = version, params.reason = versionRequired) rather than an unguarded write
reasonstringNoOperator free text; stored verbatim, read back only by the actions that record one (suspend, revoke)

Issue or rotate a secret — POST /api-clients/{apiClientKey}/secret-versions?version=​

The stored client version travels as a required query parameter. Idempotency-Key is also required (see Idempotency).

FieldTypeMeaning
materialTypeenumLOCAL_SECRET (default when absent) or SECRET_REFERENCE
generatebooleanAsk Apinizer to produce the value; ignored for a referenced secret
valuestringThe operator's own secret, for a manual (non-generated) LOCAL_SECRET; never echoed back
referencestringWhere the secret lives, for SECRET_REFERENCE; mutually exclusive with value
labelstringOperator-facing name of this generation
validFrom / expiresAttimestampValidity window
graceWindowSecondsintegerHow long the superseded generation keeps working; absent takes the platform's suggested default
storageModeenumENCRYPTED (default, recoverable) or HASHED (verifier only, shown once); ignored for SECRET_REFERENCE

An absent version query parameter is 400 malformedRequest (params.field = version, params.reason = versionRequired); a missing body entirely is 400 malformedRequest (params.reason = bodyRequired).

Configure a secret generation — PUT /api-clients/{apiClientKey}/secret-versions/{versionId}​

Replaces the label and the expiry of one generation and pushes the client to the gateways. The body is not a patch: a field you leave out is cleared, not kept.

FieldTypeMeaning
versionintegerRequired. The stored client version you expect; a missing value is 400 malformedRequest (params.field = version, params.reason = versionRequired)
labelstringOperator-facing name. Omit it and a label is generated
expiresAttimestampOmit it and the generation has no expiry. A changed value that does not lie in the future, or that lies before the start of the generation, is 400 malformedRequest; sending the stored value again is accepted

The answer is the Mutation shape. A stale version is 409 versionConflict (params.reason = apiClientRevisionConflict). A revoked client or a revoked generation is refused. The request needs no Idempotency-Key.

Response​

API client — 200 / 201​

GET /api-clients/{apiClientKey} and every row of GET /api-clients:

FieldTypeMeaning
apiClientKeystringPortable, installation-wide public identifier
clientIdstringThe gateway-facing identifier a token request presents
descriptionstring
ownerobject{type, key} — see Owner reference
organizationCodestringThe owning organization's code, or absent
operationalStateenumACTIVE | SUSPENDED | REVOKED — whether the operator currently allows this client to be used. DRAFT appears only on clients left from earlier versions
readinessenumREADY | PENDING_SECRET | UNRESOLVED_DEPENDENCY — whether the client actually has what it needs, independent of operationalState
validFrom / expiresAttimestamp
allowedGrantTypesstring[]
tokenEndpointAuthMethod / tokenIssuanceProfilestring
redirectUrisstring[]Browser-login redirect URIs of a public client
tokenNeverExpiresboolean
tokenExpiresInAmount / tokenExpiresInUnitinteger / enum
refreshTokenAllowedboolean
refreshTokenCountinteger
refreshTokenExpiresInAmount / refreshTokenExpiresInUnitinteger / enum
jwtSignatureAlgorithmstring
revokedAt / revokedBy / revokedReasontimestamp / string / stringPresent once operationalState is REVOKED
lifecycleRevisionintegerThe separate, monotonic counter the runtime reconciles against; informational here
versionintegerThe optimistic-concurrency fence — send back on the next write or lifecycle action
secretVersionsarrayEvery generation of this client, described without any secret material — see below
No material, ever

Neither this shape nor any other list on this page carries a stored verifier, a platform ciphertext, a KEK recovery envelope, a secret reference, or a hint of one. secretVersions describes each generation's identity and lifecycle only. A generated value exists on the wire exactly once, in the Client created or Secret issued response.

Secret generation (nested)​

One entry of secretVersions, on the client shape above:

FieldTypeMeaning
versionIdstringThis generation's own key
labelstring
statusenumPENDING_ACTIVATION | ACTIVE | GRACE | REVOKED
materialTypestringLOCAL_SECRET or SECRET_REFERENCE, as stored
storageModeenumENCRYPTED | HASHED
recoveryStateenumAVAILABLE | UNAVAILABLE | SHREDDED — whether the original material can still be produced for an operator through the Manager screen; unrelated to whether the version still authenticates
validFrom / expiresAt / graceUntil / createdAttimestamp
revokedAt / revokedReasontimestamp / string
versionintegerThis generation's own optimistic-concurrency counter

Mutation — 200 / 201​

Every write on the client itself (POST, PUT, and every actions/*), and POST .../secret-versions/{versionId}/actions/revoke, answers with the fields below; POST adds the first secret's fields — see Client created:

FieldTypeMeaning
apiClientobjectThe client as it now stands — the API client shape above
runtimeDeploymentobjectWhat the push told the gateways, when the write triggered one — the shared Write responses shape (state: IN_SYNC | DEGRADED, environments[]); absent when nothing was pushed

runtimeDeployment.state is IN_SYNC when every environment acknowledged the change, or DEGRADED when the change is stored but at least one environment did not acknowledge it. Each environments[] entry carries name, acknowledged and, when the environment did not acknowledge, a one-line detail of at most 300 characters. Do not retry a DEGRADED write; redeploy instead. After a secret rotation or a generation revoke, a DEGRADED write means a gateway may still verify against the previous copy of the secret, so a pipeline must test runtimeDeployment.state and not only the HTTP status before it retires the old secret.

Client created — 201​

The answer to POST /api-clients, sent with Cache-Control: no-store and Pragma: no-cache: the Mutation fields plus

FieldTypeMeaning
versionIdstringThe first secret generation's key; absent for a public client
secretstringThe generated first secret, shown once — absent when the caller supplied its own secret.value, and for a public client

Secret issued — 201​

The answer to POST .../secret-versions, sent with Cache-Control: no-store and Pragma: no-cache:

FieldTypeMeaning
apiClientobjectThe client as it now stands, including the new generation in secretVersions
versionIdstringThe new generation's key
secretstringThe generated value, shown once — absent when the caller supplied its own value
runtimeDeploymentobjectThe gateway push result for the rotation — same shape and meaning as in Mutation; absent when nothing was pushed

Limit assignments — 200​

Every row of GET .../limit-assignments — every limit assignment whose subject is this client, in the same portable-reference vocabulary as Bulk Limit Assignment and the Limit Plans surface:

FieldTypeMeaning
bindingKeystring
subjectobjectEchoes this client back as the subject (type: API_CLIENT, portable key)
familyenumREQUEST_RATE or AI_TOKEN_BUDGET
actorScopeenumANY | AUTHENTICATED | ANONYMOUS
targetobjectWhat the ceiling narrows to — proxy, group, environment, endpoint, model or provider, portably referenced
modeenumPLAN | CUSTOM | OFF
planKeysstring[]Plan keys, when mode is PLAN
customRuleSpecobjectThe plan's own rule body, inline, when mode is CUSTOM
audienceRuleobjectPresent only for a rule-based assignment; never the case for a client subject
cacheErrorHandlingType / cacheConnectionTimeoutInSecondsenum / integerRequest Rate only
showRateLimitStatisticsInResponseHeaderbooleanApplies to Request Rate and AI Token Budget assignments; an AI plan that leaves its own setting unset uses this value
customMessageTemplate / customMessageContentType / customMessageHttpStatusCodestring / string / integerRequest Rate only
sourceenumHow the assignment came to exist (manual, bulk run, portal subscription, …)
statusenumDRAFT | ACTIVE | ENDED
validFrom / validUntiltimestamp
assignedAt / assignedBytimestamp / string
endedAt / endedBy / endedReasontimestamp / string / string
operatorOverrideboolean
versioninteger
assignmentMode / inheritToenum / enumThe Counted As choice. An API client is always counted on its own, so these are SELF / absent here
bulkRunIdstringThe Bulk Assignment run that created this row, when there is one
replacedBindingKeystringThe row this one replaced under a REPLACE conflict policy, when there is one

List envelope — 200​

GET /api-clients returns the shared paged envelope; the limit-assignments list on this page returns a plain array instead.

Errors​

Every error follows the Common Contract's error envelope — branch on errorKey, never on message.

Error keyWhen
malformedRequestA body was missing (params.reason = bodyRequired), a required field was missing (ownerRequired, versionRequired, modeRequired — params.field names it), a secret was sent on PUT (secretOnCreateOnly), ownerKey/ownerType was given without its pair (ownerKeyRequiresOwnerType / ownerTypeRequiresOwnerKey), an application name on the administration path is shared by two applications (applicationNameAmbiguous, with params.field = owner.key), or an underlying validation rule was refused — params.reason then carries the service's own key
resourceNotFoundAn unknown or out-of-project apiClientKey or ownerKey
versionConflictThe stored version no longer matches what the caller sent — params.reason = apiClientRevisionConflict (client)
lifecycleConflictparams.reason names the cause: apiClientIllegalTransition (a suspend/resume/revoke move the current state refuses), apiClientNotReady (the client does not have what it needs to be in service), apiClientRevokedReadOnly (an update of a revoked client), apiClientSecretRotationRefused, secretVersionAlreadyRevoked, clientIdImmutable / apiClientKeyImmutable (an update tried to change an immutable identifier)
idempotencyKeyRequiredPOST /api-clients or POST .../secret-versions was missing its Idempotency-Key header
idempotencyKeyReusedThe same key was sent with a different request body
idempotencyReplayExpiredThe same key and body already completed — params.resource names what it created
idempotencyInProgressThe same key and body's first call is still running
permissionDeniedAuthenticated, but the caller lacks Identity Manage (writes) or View (reads)

See Common Contract → Error keys for the complete closed set and the status-code table.

Permissions & Licensing​

  • Reads (GET) require the Identity permission at View; writes (POST/PUT) require it at Manage. No AI Gateway license module gates any endpoint on this surface.
  • API clients are project-owned. The admin (platform) project has none of its own, with one exception: the clients of PORTAL applications answer on the admin path to the System Administrator, Portal Business and Portal Manager roles (see Administration path). The earlier 400 platformScopeNotSupported answer no longer applies to them.
  • A project outside the caller's authorized scope is always 404 resourceNotFound, never 403 — see Common Contract → Telling 402, 403 and 404 apart. The same rule applies to a client whose stored project does not match the one in the path: it is answered with the identical 404 a nonexistent key would get, never a distinguishing status.

Example​

Create a client owned by a consumer — it is active at once, with its first secret — rotate the secret, then revoke the client:

# 1. Create — ACTIVE, deployed, with a generated first secret
curl -X POST "https://apinizer.example.com/apiops/projects/finance/api-clients" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5e2a1c7b-9d4f-4a3e-8b1c-2f6d9a0e3c81" \
-d '{
"owner": { "type": "CONSUMER", "key": "acme-integration-user" },
"description": "Nightly settlement batch job",
"allowedGrantTypes": ["client_credentials"],
"tokenExpiresInAmount": 1,
"tokenExpiresInUnit": "HOURS",
"secret": { "generate": true, "label": "initial", "storageMode": "ENCRYPTED" }
}'
# 201 Created, Location: /apiops/projects/finance/api-clients/acme-nightly-batch-4f9a2c8d
# response.secret holds the plaintext ONCE; store it now. response.apiClient.version is the version to send next

# 2. Rotate the secret — send the client's current version
curl -X POST "https://apinizer.example.com/apiops/projects/finance/api-clients/acme-nightly-batch-4f9a2c8d/secret-versions?version=1" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9a0c3e7f-1b2d-4c5e-9f6a-8d7c1e0b4a92" \
-d '{
"generate": true,
"label": "rotation-1",
"graceWindowSeconds": 3600
}'
# 201 Created — response.secret holds the new plaintext ONCE

# 3. Revoke it later — irreversible; read the client back first for its current version
curl -X POST "https://apinizer.example.com/apiops/projects/finance/api-clients/acme-nightly-batch-4f9a2c8d/actions/revoke" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "version": 2, "reason": "Batch job decommissioned" }'
# 200 OK
  • API Clients - The concept, the Manager screen, and client-secret storage modes
  • Bulk Limit Assignment API - Attach a Request Rate or AI Token Budget assignment to a filtered population of clients (among other subject types) in one run
  • Limit Plans, Assignments & Defaults API - The manager's own REST surface behind limit plans and per-subject assignments
  • Common Contract - The shared error envelope, idempotency, pagination, and status-code rules every endpoint on this page follows
  • Error Handling - The envelope used by the manager's older endpoints