AIVA logo
AIVA Developer DocumentationAuthentication · Authorization
← Back to Documentation Hub
Developer Contract
Developer guide · Authentication and authorizationBETA

AIVA Authorization

Authenticate an existing AIVA user on behalf of an approved integration with OAuth 2.0 / OpenID Connect.

Authorization Code + PKCEConfidential ClientsUser-delegated accessPreview
BETAIntegration details may change before general availability.

Overview

The AIVA Authorization Service provides the user-delegated sign-in needed by third-party integrations. An integration sends the user to the AIVA sign-in page and exchanges the returned authorization code for tokens from its backend. The integration never handles the user's AIVA password or further account verification.

1Start AuthorizationIntegration backend creates state and PKCE values.
2AIVA LoginUser signs in and completes MFA if required.
3CallbackBrowser returns a one-time code and state to integration.
4Token ExchangeIntegration backend sends code, verifier and client credentials
5API AccessUse delegated scope within the user's AIVA permissions

OAuth client approval and AIVA user permissions are separate requirements. A client scope permits an operation category; the authenticated user's AIVA permissions determine which organisations, passes and other resources they may access.

Getting Started

  1. Request an integration registration from AIVA Support. Provide the integration name, technical contact email, exact HTTPS redirect URI(s) and required API operations.
  2. Receive the service address, client ID and secret. Use the URL supplied for your assigned environment and store the secret securely in your backend.
  3. Read discovery metadata. Retrieve the environment's OpenID Connect discovery document to obtain the issuer and authorization/token endpoint URLs.
  4. Implement a browser redirect with a fresh PKCE verifier, S256 challenge and unique state per attempt.
  5. Process the callback server-side, validate state, then exchange the code for tokens and secure the returned credentials.
  6. Call the Public API using the API authentication contract provided for your integration, and refresh or restart the delegated login as needed.
Before testing

Your intended AIVA user must already exist and be permitted to access the relevant AIVA resources. Registering an integration does not create users, organisations or providers, and no user permissions are automatically granted.

Service Base Address

The Authorization Server URL for your environment will be provided when your integration is registered, together with the approved client details. There is no single public URL published in this guide.

Use the supplied address when retrieving discovery metadata. The published discovery document identifies the canonical issuer and the endpoints for that environment. Do not substitute an address from a different environment.

Discovery & Endpoints

This service supports standard OpenID Connect discovery. Once you receive the service address, retrieve its discovery metadata and use the endpoint URLs returned in that document rather than hard-coding a hostname or protocol path.

Discovery value Usage
issuer The canonical issuer for your assigned environment.
authorization_endpoint Redirect the user's browser here to begin authorization.
token_endpoint Exchange authorization codes and refresh tokens here from your backend.
jwks_uri Published public signing keys for consumers that need token validation.
code_challenge_methods_supported Confirm support for S256 PKCE.
Inspect discovery (cURL)
# Use the service address provided when your integration is registered.
AIVA_AUTH_URL='YOUR_ISSUED_AUTHORIZATION_SERVICE_URL'
curl --fail-with-body "${AIVA_AUTH_URL%/}/.well-known/openid-configuration"
Read endpoint URLs (pwsh)
$serviceUrl = 'YOUR_ISSUED_AUTHORIZATION_SERVICE_URL'
$metadata = Invoke-RestMethod "$($serviceUrl.TrimEnd('/'))/.well-known/openid-configuration"
$metadata.issuer
$metadata.authorization_endpoint
$metadata.token_endpoint
$metadata.code_challenge_methods_supported
Read endpoint URLs (.NET 10)
using System.Text.Json;

using var http = new HttpClient();
var serviceUrl = "YOUR_ISSUED_AUTHORIZATION_SERVICE_URL";
using var discovery = JsonDocument.Parse(await http.GetStringAsync($"{serviceUrl.TrimEnd('/')}/.well-known/openid-configuration"));
var metadata = discovery.RootElement;

Console.WriteLine(metadata.GetProperty("issuer").GetString());
Console.WriteLine(metadata.GetProperty("authorization_endpoint").GetString());
Console.WriteLine(metadata.GetProperty("token_endpoint").GetString());
Console.WriteLine(string.Join(", ", metadata.GetProperty("code_challenge_methods_supported").EnumerateArray().Select(x => x.GetString())));

Use the issuer and endpoint URLs exactly as advertised by your environment. The examples below use descriptive placeholders for these discovered values.

Client Registration

A separate confidential OAuth client is provisioned for each approved integration. Registrations are maintained by AIVA Support.

Property Example Requirement
Client ID integration-example Issued with the registration; not an AIVA user ID.
Client Secret (stored in backend) Confidential. Never embed in browser or mobile code.
Redirect URI https://integration.example.com/oauth/callback Pre-registered absolute HTTPS URL, no fragment; use the exact value in the flow.
Contact email integrations@example.com Operational point of contact for the client.
Approved scopes api, resources.read Assigned by AIVA. The api base scope is required.

Your registration includes the Authorization Server address for your environment. Clients use the authorization_code and refresh_token grants, the code response type and S256 PKCE. Contact AIVA Support if you need to replace a client secret or update your registered redirect URIs.

Confidential Application Only

Do not place the client secret in client-side JavaScript or a shipped desktop/mobile binary.

Always use a trusted backend within your control to start the sign-in, protect the verifier/state and redeem the code.

Authorization Code Flow

Authorization Code Flow is an OAuth 2.0 process that allows applications to obtain access tokens on behalf of users by redirecting them to a secure, hosted login page and exchanging a temporary authorization code for tokens without directly handling their credentials.

Your integration will generate an authorization request and redirect the user's browser to AIVA. The AIVA hosted sign-in may require an email verification code. A successful session returns the browser to the integrations registered & requested callback URI with an authorization code.

Example Authorization URL
HTTP GET AUTHORIZATION_ENDPOINT_FROM_DISCOVERY?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fintegration.example.com%2Foauth%2Fcallback
  &scope=openid%20api%20resources.read%20offline_access
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256
  &state=RANDOM_SESSION_BOUND_VALUE
Parameter Meaning
response_type=code Requests a one-time authorization code.
client_id The approved integration's OAuth client ID.
redirect_uri Exactly matches a registered HTTPS callback URI.
scope Space-separated approved scopes; include api. Use offline_access to request refresh tokens.
code_challenge Base64url-encoded SHA-256 digest of a fresh verifier.
code_challenge_method=S256 Mandatory. Plain PKCE is not supported.
state Unpredictable per-login request value; match on callback to prevent cross-request forgery.

The browser receives a redirect resembling:

Example Redirect
HTTP/1.1 302 Found
Location: https://integration.example.com/oauth/callback?code=ONE_TIME_CODE&state=RANDOM_SESSION_BOUND_VALUE

// First compare the returned state with the one stored for this login attempt.
// Exchange the code server-side; it is short-lived and cannot be redeemed twice.

Process that callback in your integration's backend.

Validate state before exchanging code.

Do not forward authorization codes, client secrets or verifier values to browser analytics or application logs - keep them safe.

PKCE

PKCE (Proof Key for Code Exchange) adds an extra layer of security to the Authorization Code flow, ensuring that intercepted authorization codes cannot be exchanged for tokens without the original code verifier.

Generate a new verifier and state for each individual attempt.

This .NET example uses only standard framework APIs; substitute the issued client ID, registered redirect URI, approved scopes and the authorization_endpoint from discovery.

Generate PKCE & State (.NET 10)
using System.Security.Cryptography;
using System.Text;

static string Base64Url(byte[] bytes) => Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');

var verifier = Base64Url(RandomNumberGenerator.GetBytes(32));
var challenge = Base64Url(SHA256.HashData(Encoding.ASCII.GetBytes(verifier)));
var state = Base64Url(RandomNumberGenerator.GetBytes(32));

// Store verifier + state against the user's login attempt in server-side session state.
var parameters = new Dictionary<string, string>
{
    ["response_type"] = "code",
    ["client_id"] = "YOUR_CLIENT_ID",
    ["redirect_uri"] = "https://integration.example.com/oauth/callback",
    ["scope"] = "openid api resources.read offline_access",
    ["code_challenge"] = challenge,
    ["code_challenge_method"] = "S256",
    ["state"] = state
};

var query = string.Join("&", parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
var authorizationEndpoint = "AUTHORIZATION_ENDPOINT_FROM_DISCOVERY";
var authorizeUrl = $"{authorizationEndpoint}?{query}";
// Redirect the user's browser to authorizeUrl. Never send the client secret to the browser.
Generate PKCE & State (Bash)
# Requires OpenSSL. Run on your trusted backend or development machine.
base64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
verifier=$(openssl rand 32 | base64url)
challenge=$(printf '%s' "$verifier" | openssl dgst -sha256 -binary | base64url)
state=$(openssl rand 32 | base64url)
# Keep verifier and state in the initiating user's server-side session.
Generate PKCE & State (pwsh)
# PowerShell 7 (pwsh)
function ConvertTo-Base64Url([byte[]]$bytes) {
    [Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_')
}
$verifier = ConvertTo-Base64Url ([Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
$challenge = ConvertTo-Base64Url ([Security.Cryptography.SHA256]::HashData([Text.Encoding]::ASCII.GetBytes($verifier)))
$state = ConvertTo-Base64Url ([Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
# Keep verifier and state in the initiating user's server-side session.
State / Verifier Storage

Persist the state and original verifier in the integration's protected, short-lived server-side session. Tie them to the initiating browser, compare returned state using a safe comparison, and remove both after use. An OAuth client library that already manages state/PKCE is preferable to hand-rolling production callback handling.

Exchange Authorization Code

The callback's code is short-lived, single-use and bound to the PKCE challenge and registered client. Redeem it from the backend at the token_endpoint returned by discovery. Supply the original code_verifier and confidential client credentials. The cURL examples use placeholders for readability; do not place real production secrets in command history or shared shell output.

Exchange Code (cURL)
# TOKEN_ENDPOINT is the token_endpoint value from discovery.
TOKEN_ENDPOINT='TOKEN_ENDPOINT_FROM_DISCOVERY'
curl --fail-with-body --request POST \
  --url "$TOKEN_ENDPOINT" \
  --user 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'code=ONE_TIME_CODE' \
  --data-urlencode 'redirect_uri=https://integration.example.com/oauth/callback' \
  --data-urlencode 'code_verifier=ORIGINAL_VERIFIER'

The PowerShell and .NET 10 examples below perform the same confidential, server-side exchange. Keep real client secrets out of source code and never log tokens.

Exchange Code (pwsh)
# PowerShell 7 (pwsh)
$tokenEndpoint = 'TOKEN_ENDPOINT_FROM_DISCOVERY'
$clientId = 'YOUR_CLIENT_ID'
$clientSecret = $env:AIVA_CLIENT_SECRET
$basic = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("${clientId}:${clientSecret}"))
$tokens = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Headers @{ Authorization = "Basic $basic" } `
    -ContentType 'application/x-www-form-urlencoded' -Body @{
        grant_type = 'authorization_code'
        code = 'ONE_TIME_CODE'
        redirect_uri = 'https://integration.example.com/oauth/callback'
        code_verifier = 'ORIGINAL_VERIFIER'
    }
# Store $tokens securely; do not print token values.
Exchange Code (.NET 10)
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var clientId = "YOUR_CLIENT_ID";
var clientSecret = Environment.GetEnvironmentVariable("AIVA_CLIENT_SECRET")!;
var tokenEndpoint = "TOKEN_ENDPOINT_FROM_DISCOVERY";
var callbackCode = "ONE_TIME_CODE";
var verifier = "ORIGINAL_VERIFIER";

using var http = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
{
    Content = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"] = "authorization_code",
        ["code"] = callbackCode,
        ["redirect_uri"] = "https://integration.example.com/oauth/callback",
        ["code_verifier"] = verifier
    })
};
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")));
using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
using var tokens = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var accessToken = tokens.RootElement.GetProperty("access_token").GetString();
// Store tokens securely; do not log their values.

If the exchange goes successfully, you will receive back a token response with the tokens.

Example Successful Response
{
  "access_token": "REDACTED_SIGNED_JWT",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "REDACTED_PROTECTED_TOKEN",
  "scope": "openid api resources.read offline_access"
}

Access Token

An Access Token is a short-lived credential issued by the Authorization Server that allows an application to access protected resources on behalf of an authenticated user, subject to the permissions granted to that user.

Treat access tokens as sensitive credentials. Do not send them to browser analytics or record them in application logs.

Token Response Value What your integration should do
access_token Use for authorized calls to supported AIVA APIs, following the API contract supplied to you.
token_type Identifies how the access token is used (typically Bearer).
expires_in Track when the token expires and renew access before it becomes unusable.
refresh_token If returned, store it securely on your backend for renewal.
scope Check the scopes actually granted, not merely those requested.

Access-token details are validated by the receiving API. Integrations must still respect the AIVA user's existing permissions on individual resources. Do not assume that decoding a token proves access to a particular organisation or resource.

Refresh Tokens & Session Renewal

A Refresh Token is a credential that allows an application to obtain new access tokens without requiring the user to repeat the login process, provided the refresh token remains valid.

Request offline_access during the original authorization if the integration needs to renew access without repeating an interactive sign-in. The response will then include a refresh token.

A refresh token is sensitive long-lived credential material; store it encrypted or in a server-side secret store.

Renew Session (cURL)
# TOKEN_ENDPOINT is the token_endpoint value from discovery.
TOKEN_ENDPOINT='TOKEN_ENDPOINT_FROM_DISCOVERY'
curl --fail-with-body --request POST \
  --url "$TOKEN_ENDPOINT" \
  --user 'YOUR_CLIENT_ID:YOUR_CLIENT_SECRET' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'refresh_token=STORED_REFRESH_TOKEN'
Renew Session (pwsh)
# PowerShell 7 (pwsh)
$tokenEndpoint = 'TOKEN_ENDPOINT_FROM_DISCOVERY'
$clientId = 'YOUR_CLIENT_ID'
$clientSecret = $env:AIVA_CLIENT_SECRET
$refreshToken = 'STORED_REFRESH_TOKEN'
$basic = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("${clientId}:${clientSecret}"))
$tokens = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Headers @{ Authorization = "Basic $basic" } `
    -ContentType 'application/x-www-form-urlencoded' -Body @{
        grant_type = 'refresh_token'
        refresh_token = $refreshToken
    }
$accessToken = $tokens.access_token
$expiresAt = [DateTimeOffset]::UtcNow.AddSeconds($tokens.expires_in)
if ($tokens.refresh_token) { $refreshToken = $tokens.refresh_token } # Persist replacement securely.
Renew Session (.NET 10)
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var tokenEndpoint = "TOKEN_ENDPOINT_FROM_DISCOVERY";
var clientId = "YOUR_CLIENT_ID";
var clientSecret = Environment.GetEnvironmentVariable("AIVA_CLIENT_SECRET")!;
var refreshToken = "STORED_REFRESH_TOKEN";

using var http = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint)
{
    Content = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"] = "refresh_token",
        ["refresh_token"] = refreshToken
    })
};
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")));
using var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();
using var tokens = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var accessToken = tokens.RootElement.GetProperty("access_token").GetString()!;
var expiresAt = DateTimeOffset.UtcNow.AddSeconds(tokens.RootElement.GetProperty("expires_in").GetInt32());
if (tokens.RootElement.TryGetProperty("refresh_token", out var replacement)) refreshToken = replacement.GetString()!;
// Securely persist the updated access token, expiry, and refresh token.

On a successful refresh, use the returned access token and expiry.

If the server returns a new refresh token, replace the previously stored value securely.

If renewal fails with invalid_grant, do not retry indefinitely; require a new browser authorization flow.

Using Tokens Against AIVA APIs

After the user has signed in, your integration receives a delegated access token. Pass that token to the appropriate API according to the authentication requirements supplied for your approved integration.

Unless otherwise stated in that API, the token should be populated within the Authorization header.

This guide documents how to obtain and renew an AIVA delegated token.

Follow the relevant API reference and integration instructions provided for the specific API you want to consume.

The following examples show a conventional bearer-token request. Use your API's documented endpoint and authentication headers; some AIVA integrations require additional client authentication.

Call a protected API (cURL)
# Replace with an API endpoint approved for your integration.
AIVA_API_URL='YOUR_APPROVED_API_ENDPOINT'
ACCESS_TOKEN='YOUR_ACCESS_TOKEN'
curl --fail-with-body --header "Authorization: Bearer $ACCESS_TOKEN" "$AIVA_API_URL"
Call a protected API (pwsh)
# PowerShell 7 (pwsh)
$apiUrl = 'YOUR_APPROVED_API_ENDPOINT'
$accessToken = 'YOUR_ACCESS_TOKEN'
$result = Invoke-RestMethod -Uri $apiUrl -Headers @{ Authorization = "Bearer $accessToken" }
Call a protected API (.NET 10)
using System.Net.Http.Headers;

var apiUrl = "YOUR_APPROVED_API_ENDPOINT";
var accessToken = "YOUR_ACCESS_TOKEN";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var response = await http.GetAsync(apiUrl);
response.EnsureSuccessStatusCode();

End to End Workflow

In a typical integration, the backend starts a browser login, receives a one-time code, exchanges it for delegated tokens, calls an approved AIVA API, then refreshes the access token when needed. These examples demonstrate the same sequence in three languages.

  1. Authorize: Discover the endpoints, create a fresh PKCE verifier and state, and open the authorization URL in the user's browser. AIVA handles sign-in and verification.
  2. Exchange: Receive code and state at the registered callback. Validate the returned state, then exchange the code from your backend using the original verifier and client secret.
  3. Use: Send the access token to the approved API using that API's documented authentication headers.
  4. Refresh: Request a new access token using the stored refresh token and client credentials. Replace the stored refresh token when a new one is returned.
About the examples

Replace the placeholder client, service, callback and API values. All three examples pause for you to copy the callback's code and state after the browser sign-in; cURL and console applications cannot perform AIVA's interactive sign-in themselves. Production web applications should handle that callback and state storage automatically, preferably using an OAuth/OIDC library.

The API call below uses a standard bearer header to illustrate the sequence. Use any additional authentication headers required by your specific AIVA API contract.

Complete workflow (cURL / Bash)
# Bash; requires curl, jq and openssl. Run from your trusted backend/dev machine.
AIVA_AUTH_URL='YOUR_ISSUED_AUTHORIZATION_SERVICE_URL'
AIVA_CLIENT_ID='YOUR_CLIENT_ID'
AIVA_CLIENT_SECRET='YOUR_CLIENT_SECRET' # Demo only: use secure secret storage.
AIVA_API_URL='YOUR_APPROVED_API_ENDPOINT'
REDIRECT_URI='https://integration.example.com/oauth/callback'

metadata=$(curl --fail-with-body -sS "${AIVA_AUTH_URL%/}/.well-known/openid-configuration")
authorize_endpoint=$(jq -r .authorization_endpoint <<< "$metadata")
token_endpoint=$(jq -r .token_endpoint <<< "$metadata")
base64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
urlencode() { jq -nr --arg text "$1" '$text | @uri'; }
verifier=$(openssl rand 32 | base64url)
challenge=$(printf '%s' "$verifier" | openssl dgst -sha256 -binary | base64url)
state=$(openssl rand 32 | base64url)

url="${authorize_endpoint}?response_type=code&client_id=$(urlencode "$AIVA_CLIENT_ID")&redirect_uri=$(urlencode "$REDIRECT_URI")&scope=$(urlencode 'openid api resources.read offline_access')&code_challenge=$challenge&code_challenge_method=S256&state=$state"
printf 'Open in your browser:\n%s\n' "$url"
# Complete AIVA login in the browser. Read code and state from the callback URL.
read -r -p 'Returned code: ' code
read -r -p 'Returned state: ' returned_state
[ "$returned_state" = "$state" ] || { echo 'State mismatch' >&2; exit 1; }

tokens=$(curl --fail-with-body -sS --user "$AIVA_CLIENT_ID:$AIVA_CLIENT_SECRET" \
  --data-urlencode 'grant_type=authorization_code' --data-urlencode "code=$code" \
  --data-urlencode "redirect_uri=$REDIRECT_URI" --data-urlencode "code_verifier=$verifier" "$token_endpoint")
access_token=$(jq -r .access_token <<< "$tokens")
refresh_token=$(jq -r .refresh_token <<< "$tokens")
# Use the token according to your API's authentication contract.
curl --fail-with-body -sS -H "Authorization: Bearer $access_token" "$AIVA_API_URL"

updated=$(curl --fail-with-body -sS --user "$AIVA_CLIENT_ID:$AIVA_CLIENT_SECRET" \
  --data-urlencode 'grant_type=refresh_token' --data-urlencode "refresh_token=$refresh_token" "$token_endpoint")
access_token=$(jq -r .access_token <<< "$updated")
refresh_token=$(jq -r --arg old "$refresh_token" '.refresh_token // $old' <<< "$updated")
printf 'New access token expires in %s seconds.\n' "$(jq -r .expires_in <<< "$updated")"
# Persist the refreshed tokens securely; never log their values.
Complete workflow (pwsh)
# PowerShell 7 (pwsh). Demo uses interactive callback values.
$serviceUrl = 'YOUR_ISSUED_AUTHORIZATION_SERVICE_URL'
$clientId = 'YOUR_CLIENT_ID'
$clientSecret = $env:AIVA_CLIENT_SECRET
$apiUrl = 'YOUR_APPROVED_API_ENDPOINT'
$redirectUri = 'https://integration.example.com/oauth/callback'
$metadata = Invoke-RestMethod "$($serviceUrl.TrimEnd('/'))/.well-known/openid-configuration"

function ConvertTo-Base64Url([byte[]]$bytes) {
    [Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_')
}
$verifier = ConvertTo-Base64Url ([Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
$challenge = ConvertTo-Base64Url ([Security.Cryptography.SHA256]::HashData([Text.Encoding]::ASCII.GetBytes($verifier)))
$state = ConvertTo-Base64Url ([Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
$authorizeUrl = "$($metadata.authorization_endpoint)?response_type=code&client_id=$([uri]::EscapeDataString($clientId))&redirect_uri=$([uri]::EscapeDataString($redirectUri))&scope=$([uri]::EscapeDataString('openid api resources.read offline_access'))&code_challenge=$challenge&code_challenge_method=S256&state=$state"
Write-Host "Open in your browser: $authorizeUrl"
$code = Read-Host 'Returned code'
$returnedState = Read-Host 'Returned state'
if ($returnedState -cne $state) { throw 'State mismatch' }

$basic = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("${clientId}:${clientSecret}"))
$headers = @{ Authorization = "Basic $basic" }
$tokens = Invoke-RestMethod -Method Post -Uri $metadata.token_endpoint -Headers $headers `
    -ContentType 'application/x-www-form-urlencoded' -Body @{
        grant_type = 'authorization_code'; code = $code
        redirect_uri = $redirectUri; code_verifier = $verifier
    }
$accessToken = $tokens.access_token
$refreshToken = $tokens.refresh_token
# The chosen API may require additional headers beyond this bearer-token example.
Invoke-RestMethod -Uri $apiUrl -Headers @{ Authorization = "Bearer $accessToken" }

$updated = Invoke-RestMethod -Method Post -Uri $metadata.token_endpoint -Headers $headers `
    -ContentType 'application/x-www-form-urlencoded' -Body @{
        grant_type = 'refresh_token'; refresh_token = $refreshToken
    }
$accessToken = $updated.access_token
if ($updated.refresh_token) { $refreshToken = $updated.refresh_token }
$expiresAt = [DateTimeOffset]::UtcNow.AddSeconds($updated.expires_in)
Write-Host "Refreshed access token expires (UTC): $expiresAt"
# Persist the refreshed tokens securely; never log their values.
Complete workflow (.NET 10)
// .NET 10 console example: BCL only; browser login is performed interactively.
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

var serviceUrl = "YOUR_ISSUED_AUTHORIZATION_SERVICE_URL";
var clientId = "YOUR_CLIENT_ID";
var clientSecret = Environment.GetEnvironmentVariable("AIVA_CLIENT_SECRET")!;
var apiUrl = "YOUR_APPROVED_API_ENDPOINT";
var redirectUri = "https://integration.example.com/oauth/callback";
using var http = new HttpClient();
using var discovery = JsonDocument.Parse(await http.GetStringAsync($"{serviceUrl.TrimEnd('/')}/.well-known/openid-configuration"));
var authorizationEndpoint = discovery.RootElement.GetProperty("authorization_endpoint").GetString()!;
var tokenEndpoint = discovery.RootElement.GetProperty("token_endpoint").GetString()!;

static string Base64Url(byte[] value) => Convert.ToBase64String(value).TrimEnd('=').Replace('+', '-').Replace('/', '_');
var verifier = Base64Url(RandomNumberGenerator.GetBytes(32));
var challenge = Base64Url(SHA256.HashData(Encoding.ASCII.GetBytes(verifier)));
var state = Base64Url(RandomNumberGenerator.GetBytes(32));
var query = new Dictionary<string, string>
{
    ["response_type"] = "code", ["client_id"] = clientId, ["redirect_uri"] = redirectUri,
    ["scope"] = "openid api resources.read offline_access", ["code_challenge"] = challenge,
    ["code_challenge_method"] = "S256", ["state"] = state
};
var authorizeUrl = $"{authorizationEndpoint}?{string.Join("&", query.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"))}";
Console.WriteLine($"Open in your browser: {authorizeUrl}");
Console.Write("Returned code: ");
var code = Console.ReadLine()!;
Console.Write("Returned state: ");
if (Console.ReadLine() != state) throw new InvalidOperationException("State mismatch");

async Task<JsonDocument> RequestTokensAsync(Dictionary<string, string> form)
{
    using var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint) { Content = new FormUrlEncodedContent(form) };
    request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")));
    using var response = await http.SendAsync(request);
    response.EnsureSuccessStatusCode();
    return JsonDocument.Parse(await response.Content.ReadAsStringAsync());
}

using var tokens = await RequestTokensAsync(new()
{
    ["grant_type"] = "authorization_code", ["code"] = code,
    ["redirect_uri"] = redirectUri, ["code_verifier"] = verifier
});
var accessToken = tokens.RootElement.GetProperty("access_token").GetString()!;
var refreshToken = tokens.RootElement.GetProperty("refresh_token").GetString()!;
using var apiRequest = new HttpRequestMessage(HttpMethod.Get, apiUrl);
apiRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var apiResponse = await http.SendAsync(apiRequest);
apiResponse.EnsureSuccessStatusCode(); // Follow your API's own authentication contract.

using var updated = await RequestTokensAsync(new()
{
    ["grant_type"] = "refresh_token", ["refresh_token"] = refreshToken
});
accessToken = updated.RootElement.GetProperty("access_token").GetString()!;
if (updated.RootElement.TryGetProperty("refresh_token", out var replacement)) refreshToken = replacement.GetString()!;
var expiresAt = DateTimeOffset.UtcNow.AddSeconds(updated.RootElement.GetProperty("expires_in").GetInt32());
Console.WriteLine($"Refreshed access token expires (UTC): {expiresAt:u}");
// In a real web app, persist state/verifier and tokens in secure server-side storage.

Scopes & Permissions

Scopes define the level of access an application can request when interacting with AIVA, limiting which resources and operations the application is authorised to access at the API level.

User Permissions determine what an authenticated user is allowed to access or perform within AIVA, ensuring that applications can only act within the permissions already granted to that user.

Delegated Scopes Only

As of this version only delegated scopes can be generated. Delegated scopes are attached to a user.

Application scopes are not currently supported.

The table below documents the available scopes that can be registered against a client and added to the authorize request.

Scope Purpose Notes
api Required base access to the AIVA API. Added to approved clients; request it during authorization.
resources.read Read permitted AIVA resource data. Requires AIVA user permission to the target resource.
resources.readwrite Read/create supported AIVA resources. Does not grant unrestricted write access.
transfers.readwrite Manage supported package-transfer transactions. Resource/user authorization still applies.
offline_access Requests refresh-token support. Standard OAuth/OIDC scope, not a resource permission.
openid Requests OpenID Connect identity semantics. Not an AIVA API access permission.

Ask for only the scopes your integration needs. AIVA administrators approve capabilities per client. An AIVA user then authenticates through the AIVA-hosted experience; access remains subject to that user’s existing resource permissions. Your integration must be approved before requesting these scopes.

Troubleshooting

Authorization endpoint errors may appear as redirects to the registered callback; token endpoint errors are returned in JSON. Handle errors by their standard OAuth error identifier, not by comparing human-readable descriptions.

Error / Symptom Likely Cause Recommendation
invalid_client Bad client ID/secret or missing confidential client authentication. Verify the correct environment's credentials; regenerate the secret if necessary.
invalid_grant Code expired or reused, verifier mismatch, or an invalid/expired grant. Stop replaying the grant; initiate a fresh authorization when appropriate.
invalid_scope / unauthorized_client Unapproved scope or unsupported flow. Check assigned client scopes; use Authorization Code + S256 PKCE.
invalid_request Missing/invalid parameters, callback or PKCE values. Compare with discovery and the registered redirect URI.
login_required prompt=none was used but an interactive login is necessary. Restart in an interactive browser, without prompt=none.
Browser loops back to sign-in Browser cookies blocked or stale state; forced login parameters re-applied by the integration. Inspect callback handling, session cookies and redirects without exposing tokens.
HTTP 429 Hosted authentication or protocol rate limit. Avoid rapid retries and repeated OTP sends, respect the Retry-After response header.
API 401/403 with a valid JWT Expired or unsuitable token, missing required scope, or insufficient user permission. Check token expiry, granted scopes and the API integration instructions; confirm the AIVA user can access the resource.
Example Token Endpoint Error
{
  "error": "invalid_grant",
  "error_description": "The token is no longer valid."
}

Never retry a consumed authorization code indefinitely. Start a new interactive authorization flow if the delegated session cannot be renewed.

Session Controls

Session Controls allow applications to influence how AIVA handles existing authenticated sessions, including whether users can continue without signing in again or must reauthenticate. These controls are optional parameters supplied as part of the OAuth authorization request.

Parameter Behaviour
prompt=none Attempts authorization without user interaction. Returns login_required if an existing authenticated session cannot be used.
prompt=login Forces the user to authenticate again, even when an existing session is available.
max_age=300 Specifies the maximum acceptable age of the user's authentication, in seconds. For example, 300 requires reauthentication if the user last signed in more than five minutes ago. Use max_age=0 to require fresh authentication.
prompt=select_account Requests a new sign-in, allowing the user to authenticate with a different account. AIVA does not display a separate account selection screen.
prompt=consent Not supported. Do not include this value in authorization requests.

Applying Session Controls

Session Controls are supplied as query-string parameters on the authorization endpoint obtained from the OpenID Connect discovery document. Include them alongside the standard authorization parameters, such as client_id, redirect_uri, scope and PKCE values.

For example, append either of the following parameters to an existing authorization request:

&prompt=login

Or, to require authentication within the last five minutes:

&max_age=300

If using an OAuth or OpenID Connect client library, these values can typically be supplied through its additional authorization request parameters.

Handling Non-Interactive Authentication

When using prompt=none, applications must be prepared to handle a login_required response. This indicates that interactive authentication is necessary. The application should initiate a normal authorization request without prompt=none, allowing the user to complete the AIVA login process.

Important

Session Controls affect authentication during the authorization flow. They do not change access-token lifetimes, refresh-token behaviour or the application's own sign-out process.

Integration Security Checklist

  • Keep all client secrets and refresh tokens on a trusted backend. Use TLS for every OAuth call.
  • Register exact HTTPS callback URIs. Never redirect to an unvalidated user-provided destination.
  • Use a fresh cryptographic PKCE verifier and S256 challenge for every authorization request.
  • Generate unpredictable state, bind it to the initiating session and validate it on callback.
  • Redeem codes once, server-side.
  • Do not log query strings containing codes, password-reset links, secrets or tokens.
  • Use a maintained OAuth/OIDC client library; never treat a decoded access token as proof of permission to a specific AIVA resource.
  • Protect refresh tokens at rest and prevent concurrent refreshes from racing over stored credentials.
  • Handle authentication failures, account changes, 429 responses and missing scope/resource access without retry storms.
  • Never ask the end user to send an AIVA password or emailed OTP to your integration.

Glossary of Terms

The following terms are used throughout this guide and in OAuth and OpenID Connect integration examples.

TermMeaning
Access Token A credential issued following successful authorization that an application presents when accessing a protected API on behalf of the authenticated user.
Audience (aud) Identifies the intended recipient of a token. An API should only accept access tokens issued for its expected audience.
Authentication The process of verifying who a user is. AIVA handles user authentication through its hosted sign-in experience.
Authorization The process of determining what an authenticated user or application is permitted to do.
Authorization Code A short-lived, single-use value returned to the application after a successful authorization request. The application exchanges it for tokens.
Authorization Code Flow An OAuth 2.0 flow that redirects the user to AIVA for sign-in and returns an authorization code that the application exchanges for tokens without handling the user’s credentials.
Authorization Endpoint The URL to which an application redirects the user to begin authorization. Obtain it from the environment’s discovery document.
Authorization Server The AIVA service that authenticates users, processes OAuth/OIDC authorization requests and issues tokens.
Client / Client Application The software integration requesting authorization to access AIVA on behalf of a user.
Client ID (client_id) The identifier assigned to an approved integration during registration.
Client Secret A confidential credential used to authenticate the registered client when exchanging or refreshing tokens. It must remain on a trusted server.
Code Challenge (code_challenge) A value derived from the PKCE code verifier and included in the authorization request.
Code Verifier (code_verifier) A cryptographically random value retained by the client and sent during the code exchange to prove it initiated the authorization request.
Confidential Client An application able to protect its client secret, typically by performing token requests on a trusted backend rather than in browser code.
Delegated Access Access exercised by an application on behalf of a signed-in AIVA user, subject to both the application’s scopes and that user’s AIVA permissions.
Discovery Document OpenID Connect metadata that publishes the environment’s issuer, authorization endpoint, token endpoint and signing-key location.
Grant Type The OAuth mechanism used to request tokens, such as authorization_code or refresh_token.
Issuer (iss) Identifies the Authorization Server that issued a token. Applications should validate it against the expected environment.
JSON Web Token (JWT) A structured token format containing claims such as subject, issuer, audience and expiry, protected by a digital signature.
JSON Web Key Set (JWKS) The public signing keys published by the Authorization Server so applications and APIs can validate signed tokens.
OpenID Connect (OIDC) An identity layer built on OAuth 2.0 that allows applications to authenticate users and receive standard identity information.
OAuth 2.0 A standard authorization framework that allows an application to request limited access to protected resources using tokens.
One-Time Token (OTP) A temporary verification code that AIVA may ask the user to enter as part of hosted sign-in.
PKCE (Proof Key for Code Exchange) An extension to the Authorization Code flow that prevents an intercepted authorization code being redeemed without the original code verifier.
Redirect URI (redirect_uri) An approved callback address to which the Authorization Server returns the user after the authorization step. It must match a URI registered for the client.
Refresh Token A credential that allows a confidential client to request a replacement access token without requiring another interactive sign-in while renewal remains permitted.
Resource Server An API that accepts access tokens and independently checks whether the request can access a protected resource.
S256 The PKCE method that derives the code challenge using SHA-256 followed by Base64URL encoding. AIVA requires this method.
Scope (scope) A named permission requested by a client, describing the category of API capability the integration is approved to use.
Subject (sub) The claim identifying the authenticated AIVA user represented by a delegated token.
Token Endpoint The URL used by the confidential client to exchange authorization codes or refresh tokens for new tokens. Obtain it from discovery.
User Permissions The permissions assigned to the signed-in user within AIVA, which continue to restrict which resources the integration may access.

Reference