Skip to main content

Control-Plane API

OAuth Clients

Use OAuth when your application must have user permission to access Verdictan. The user signs in to Verdictan, reviews the permissions your app requests, and approves or denies access.

The flow has three parts: register your app, get consent in the Console, then exchange the authorization code at the API. PKCE connects the code to the app that started the request. State connects the callback to the same sign-in attempt.

For unattended automation, use an API token with a scoped role. OAuth is not necessary for your first model request.

OAuth routes​

RouteHostUse
GET /oauth/authorizeRegional Console hostStarts consent in the browser of the user
POST /v1/oauth/tokenRegional API hostExchanges an authorization code or a refresh token for an access token
POST /v1/oauth/revokeRegional API hostRevokes an access token or a refresh token

The API route GET /v1/oauth/authorize is retired. It returns method not allowed.

Before you start​

  • Sign in with a role that has the oauth_clients:read and oauth_clients:write actions.
  • Get an application owner and a support contact.
  • Select the smallest applicable OAuth scopes from the scope catalog.
  • Prepare one callback URI for each environment.
  • Implement PKCE with the S256 challenge method in the application.
  • Implement and verify the state value in the application.

Register a public client​

  1. Open API Tokens → OAuth Clients.
  2. Select Create client.
  3. Type the Client name.
  4. Type each Redirect URIs value.
  5. In Scopes, select only the necessary scopes.
  6. Save the client.
  7. Copy the client ID into the application configuration.

The registration creates a public client ID. It does not create a client secret. An authorization request can use only registered scopes. Verdictan also verifies each scope against the active scope catalog.

Example OAuth client registry with test data

Example OAuth client registry with test data.

Redirect URIs must follow these rules:

  • Use an absolute URL.
  • Use HTTPS for deployed callbacks.
  • Use HTTP only for the loopback hosts localhost, 127.0.0.1, and [::1] in local development.
  • Do not include a fragment or user information in the URI.

The callback URI in the authorization request must equal a registered URI. Match the scheme, host, port, path, query, and trailing slash. Use a different client for environments with different owners or revocation procedures.

Select the regional hosts​

Use the Console and API from the same region as the registered client:

RegionConsent in the browserToken exchange and API calls
EUhttps://console.eu.verdictan.comhttps://api.eu.verdictan.com
UShttps://console.us.verdictan.comhttps://api.us.verdictan.com

Replace the example hosts with the hosts for your region.

Start authorization in the browser​

  1. Create a high-entropy code verifier.
  2. Create the base64url-encoded SHA-256 code challenge without padding.
  3. Create a unique state value.
  4. Store the value.
  5. Open the Console authorization route in the browser of the user:
https://console.example.com/oauth/authorize?response_type=code&client_id=replace-with-client-id&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=replace-with-registered-scope&state=replace-with-state&code_challenge=replace-with-s256-challenge&code_challenge_method=S256

Replace console.example.com with the regional Console host above. Use the URL encoder for your programming language to encode the callback and scopes in the query.

For example, Python can generate the PKCE values without a library dependency:

import base64
import hashlib
import secrets

code_verifier = secrets.token_urlsafe(64)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode("ascii")).digest()
).rstrip(b"=").decode("ascii")
state = secrets.token_urlsafe(32)

Keep code_verifier and state in the short-lived authorization transaction. Send only code_challenge and state in the authorization URL. Do not log the verifier.

Do not send the browser to GET /v1/oauth/authorize. Verdictan retired that API route. It returns method not allowed.

The user must sign in to Verdictan in the Console. It sends a protected preview request and shows the client, the callback, and the requested scopes. The user can approve or deny the request.

OAuth consent request with an example client, callback URI, and requested scopes

Example OAuth consent request with test data.

After the decision, the Console follows a redirect that the API builds. Approval adds a one-time code and the initial state. Denial adds error=access_denied and the initial state.

Validate the callback​

  1. Compare the callback state value with the stored value. If they are different, stop the flow.
  2. If the callback contains an OAuth error, stop the flow.
  3. Exchange an approved code one time only.
  4. Do not write codes or verifier values to logs.

Exchange the code​

Send the code to the API host. Use the same client ID, callback URI, and PKCE verifier from the authorization request.

curl --request POST "https://api.example.com/v1/oauth/token" \
  --header "Content-Type: application/json" \
  --data '{
    "grant_type": "authorization_code",
    "code": "replace-with-authorization-code",
    "redirect_uri": "https://app.example.com/callback",
    "client_id": "replace-with-client-id",
    "code_verifier": "replace-with-original-code-verifier"
  }'

A response with status 200 contains access_token, token_type, expires_in, scope, and refresh_token. The access token starts with vdt_at_. Send it as a bearer token only to API routes that the approved scope authorizes.

For a web application with a backend, keep access and refresh tokens on the server. Use a session cookie with the Secure flag for the browser. For a native application, use its platform credential store. Do not put tokens in URLs, tickets, logs, or browser local storage.

Refresh or revoke a token​

Use the refresh token grant to replace an expired access token:

curl --request POST "https://api.example.com/v1/oauth/token" \
  --header "Content-Type: application/json" \
  --data '{
    "grant_type": "refresh_token",
    "client_id": "replace-with-client-id",
    "refresh_token": "replace-with-refresh-token"
  }'

The response rotates the refresh token. Replace the stored value immediately.

Revoke an access token or a refresh token when the application no longer uses it:

curl --request POST "https://api.example.com/v1/oauth/revoke" \
  --header "Content-Type: application/json" \
  --data '{
    "client_id": "replace-with-client-id",
    "token": "replace-with-token"
  }'

The revoke route returns success for an unknown token.

Replace or delete a client​

  1. Register a replacement client with the necessary callbacks and scopes.
  2. Update each application environment with the new client ID.
  3. Complete the PKCE and consent flow in each environment.
  4. In API Tokens → OAuth Clients, select Delete client for the previous client.
  5. Review Trail.

When you delete a client, Verdictan deactivates it. In the same transaction, Verdictan revokes all access tokens and refresh tokens for that client. New authorization requests and refresh grants for that client fail.

Verify​

  1. Complete the consent flow with a test user. Make sure that the callback contains code and the initial state.
  2. Exchange the code. Make sure that the response contains a vdt_at_ access token.
  3. Call one API route that the scope authorizes. Make sure that the response status is 200.
  4. Call one API route that the scope does not authorize. Make sure that the response status is 403.
  5. Deny a second consent request. Make sure that the callback contains error=access_denied.
  6. Open Trail. Find the client registration event.

Troubleshooting​

SymptomCauseAction
Redirect mismatchThe callback URI does not match a registered URICompare the scheme, host, port, path, query, and trailing slash.
oauth.invalid_clientThe client ID is unknown, or you deleted the clientVerify the client ID, the organization, and the environment.
oauth.invalid_scopeThe requested scope is not registered for the client or not in the scope catalogRequest only registered scopes.
PKCE failureThe verifier does not match the challenge, or the method is not S256Use the initial verifier and base64url encoding without padding.
No consent pageThe browser went to the retired API routeSend the browser to the Console /oauth/authorize route.
State mismatchThe stored transaction expired or the callback is a replayStart a new authorization request.
API returns 403 with the code oauth.insufficient_scopeThe approved scope does not include the permission for the routeRequest the necessary scope. Complete consent again.

Next steps​