Skip to content

Authentication

Access to Milkyway is protected with OAuth 2.0 (Client Credentials Grant). The sending bank obtains an access token from Planet9's Keycloak and passes it on every request to Milkyway in the Authorization: Bearer <access_token> header.

This page describes how a sending bank obtains and uses the token in the stage and production environments.

Obtaining credentials

At onboarding, Planet9 creates an OAuth client for you in Keycloak and issues a client_id / client_secret pair. Self-service client registration is not offered.

  • Credentials are issued separately for stage and production — these are different realms.
  • client_secret is a secret: keep it in secure storage, do not commit it to a repository, and do not log it.
  • To rotate or revoke credentials, contact the Planet9 team.

Environments

There is a single Keycloak for both environments; they are separated by realms.

Environment Realm Token endpoint
stage planet9-stage https://keycloak.ac8o.planet9.ae/realms/planet9-stage/protocol/openid-connect/token
production planet9 https://keycloak.ac8o.planet9.ae/realms/planet9/protocol/openid-connect/token

The realm's full OIDC configuration (endpoints, supported algorithms, JWKS) is available at the discovery URL:

https://keycloak.ac8o.planet9.ae/realms/<realm>/.well-known/openid-configuration

Credentials are not portable between environments

Stage client_id / client_secret do not work in production and vice versa. Use the realm and credentials of the environment you are calling.

Getting a token

Send a request to your environment's token endpoint with grant_type=client_credentials (example for stage):

curl -s -X POST \
  'https://keycloak.ac8o.planet9.ae/realms/planet9-stage/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' \
  -d 'client_id=<your client_id>' \
  -d 'client_secret=<your client_secret>'

For production, replace the planet9-stage realm with planet9.

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI...",
  "expires_in": 300,
  "token_type": "Bearer",
  "scope": "..."
}

Using the token

Pass the obtained access_token on every request to Milkyway in the Authorization header:

curl 'https://<milkyway-api>/payments/v1/precheck' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -d '{ ... }'

Milkyway API base URL

The Milkyway API address for stage and production is agreed at onboarding and is not listed here — a token from a given realm is used against the API of the same environment.

Lifetime and renewal

  • The access token is short-lived (see expires_in, in seconds). Once it expires, request a new one with the same request — the Client Credentials Grant does not use a refresh token.
  • Cache the token and reuse it until it expires; do not request a new token on every API call. It is recommended to refresh the token ahead of time, with a small margin before expires_in.
sequenceDiagram
    autonumber
    participant S as Sending bank
    participant K as Keycloak
    participant M as Milkyway

    S->>K: POST token (client_credentials)
    K-->>S: access_token (+ expires_in)
    Note over S: Cache until expiry
    S->>M: Request + Authorization: Bearer <token>
    M-->>S: Response

Common errors

Symptom Likely cause
401 invalid_client from Keycloak Wrong client_id / client_secret, or calling the wrong realm (environment mixed up).
401 Unauthorized from Milkyway Token expired, missing Authorization header, or token issued in a different environment.
400 unauthorized_client Client Credentials Grant is not enabled for the client — contact the Planet9 team.