Prime Services Authentication
Galaxy API Authentication Guide
This guide covers authentication for all Galaxy APIs using OAuth 2.0 Client Credentials flow.
Overview
Galaxy APIs use OAuth 2.0 with the Client Credentials grant type for machine-to-machine (M2M) authentication. You'll need:
- Client ID - Provided during onboarding
- Client Secret - Provided during onboarding (keep this secure!)
Getting an Access Token
Token Endpoint
Each environment has a branded authentication endpoint powered by Okta:
| Environment | Auth Domain | Auth Server ID |
|---|---|---|
| UAT | auth.api.uat.galaxy.com | ausy7o2fb3m86Kc8Z4x7 |
| Production | auth.api.galaxy.com | ausy7o2fb3m86Kc8Z4x7 |
Token Endpoint URL Pattern:
https://{auth_domain}/oauth2/{auth_server_id}/v1/token
Example for Production:
https://auth.api.galaxy.com/oauth2/ausy7o2fb3m86Kc8Z4x7/v1/token
Request
# Production example
curl -X POST "https://auth.api.galaxy.com/oauth2/ausy7o2fb3m86Kc8Z4x7/v1/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" \
-d "scope=api:read"
Request Parameters:
| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | Must be client_credentials |
client_id | Yes | Your client ID |
client_secret | Yes | Your client secret |
scope | Yes | Space-separated list of scopes (see Scopes) |
Response
Success (200 OK):
{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": "eyJraWQiOiJKV1..."
}
| Field | Description |
|---|---|
token_type | Always Bearer |
expires_in | Token lifetime in seconds (typically 3600 = 1 hour) |
access_token | JWT to include in API requests |
Error (400 Bad Request):
{
"error": "invalid_client",
"error_description": "Client authentication failed."
}
| Error Code | Description |
|---|---|
invalid_client | Invalid client credentials |
invalid_scope | Requested scope is not authorized for this client |
invalid_request | Missing required parameter |
Using the Access Token
Include the access token in the Authorization header for all API requests:
curl https://api.galaxy.com/service-reference-g1/accounts \
-H "Authorization: Bearer eyJraWQiOiJKV1..."
Scopes
| Scope | Description |
|---|---|
api:read | Read access to APIs (required for all requests) |
api:write | Write access to APIs (required for POST/PUT/DELETE) |
Request only the scopes you need. Most read-only integrations only need api:read.
Token Refresh Best Practices
Access tokens expire after the time specified in expires_in (typically 1 hour). Follow these best practices:
1. Cache and Reuse Tokens
Don't request a new token for every API call. Cache the token and reuse it until it expires.
import time
import requests
class GalaxyApiClient:
# Auth server IDs per environment
AUTH_SERVERS = {
"dev": "auszxcyntf9RscPsW1d7",
"qa": "auszxcyntf9RscPsW1d7",
"uat": "ausy7o2fb3m86Kc8Z4x7",
"prod": "ausy7o2fb3m86Kc8Z4x7",
}
def __init__(self, client_id, client_secret, env="prod"):
self.client_id = client_id
self.client_secret = client_secret
self.env = env
# Set URLs based on environment
if env == "prod":
self.base_url = "https://api.galaxy.com"
self.auth_domain = "auth.api.galaxy.com"
else:
self.base_url = f"https://api.{env}.galaxy.com"
self.auth_domain = f"auth.api.{env}.galaxy.com"
auth_server_id = self.AUTH_SERVERS[env]
self.auth_url = f"https://{self.auth_domain}/oauth2/{auth_server_id}/v1/token"
self._token = None
self._token_expires_at = 0
def _get_token(self):
"""Get a valid access token, refreshing if needed."""
# Refresh 5 minutes before expiry to avoid race conditions
if self._token and time.time() < (self._token_expires_at - 300):
return self._token
response = requests.post(
self.auth_url,
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
"scope": "api:read"
}
)
response.raise_for_status()
data = response.json()
self._token = data["access_token"]
self._token_expires_at = time.time() + data["expires_in"]
return self._token
def get(self, path):
"""Make an authenticated GET request."""
return requests.get(
f"{self.base_url}{path}",
headers={"Authorization": f"Bearer {self._get_token()}"}
)
2. Proactive Token Refresh
Refresh the token before it expires to avoid failed requests:
# Refresh 5 minutes (300 seconds) before expiry
REFRESH_BUFFER_SECONDS = 300
if time.time() > (token_expires_at - REFRESH_BUFFER_SECONDS):
refresh_token()
3. Handle 401 Responses Gracefully
If you receive a 401 Unauthorized response, refresh the token and retry once:
response = api_client.get("/accounts")
if response.status_code == 401:
# Token may have been revoked or expired
api_client.refresh_token()
response = api_client.get("/accounts")
if response.status_code == 401:
# Still failing - credentials may be invalid
raise AuthenticationError("Failed to authenticate")
4. Don't Hardcode Token Expiry
Always use the expires_in value from the token response. Token lifetimes may change.
# ✅ Good - use expires_in from response
token_expires_at = time.time() + response["expires_in"]
# ❌ Bad - hardcoded expiry
token_expires_at = time.time() + 3600
Error Handling
Authentication Errors
| HTTP Status | Error | Action |
|---|---|---|
| 400 | invalid_client | Check client_id and client_secret |
| 400 | invalid_scope | Request only scopes your client is authorized for |
| 401 | Token expired | Refresh the token and retry |
| 403 | Forbidden | Your client doesn't have access to this API |
Rate Limiting
The token endpoint has rate limits. If you receive a 429 Too Many Requests:
- Wait for the duration specified in the
Retry-Afterheader - Review your token caching - you may be requesting tokens too frequently
OpenID Connect Discovery
For programmatic configuration, use the OpenID Connect discovery endpoint:
Discovery URL Pattern:
https://{auth_domain}/oauth2/{auth_server_id}/.well-known/openid-configuration
| Environment | Discovery URL |
|---|---|
| Development | https://auth.api.dev.galaxy.com/oauth2/auszxcyntf9RscPsW1d7/.well-known/openid-configuration |
| QA | https://auth.api.qa.galaxy.com/oauth2/auszxcyntf9RscPsW1d7/.well-known/openid-configuration |
| UAT | https://auth.api.uat.galaxy.com/oauth2/ausy7o2fb3m86Kc8Z4x7/.well-known/openid-configuration |
| Production | https://auth.api.galaxy.com/oauth2/ausy7o2fb3m86Kc8Z4x7/.well-known/openid-configuration |
This returns metadata including:
{
"issuer": "https://auth.api.galaxy.com/oauth2/ausy7o2fb3m86Kc8Z4x7",
"token_endpoint": "https://auth.api.galaxy.com/oauth2/ausy7o2fb3m86Kc8Z4x7/v1/token",
"jwks_uri": "https://auth.api.galaxy.com/oauth2/ausy7o2fb3m86Kc8Z4x7/v1/keys",
"scopes_supported": ["openid", "api:read", "api:write"],
"grant_types_supported": ["client_credentials"]
}
Security Best Practices
- Never expose client secrets in client-side code, logs, or version control
- Use environment variables or a secrets manager for credentials
- Rotate credentials periodically - contact Galaxy support to rotate your client secret
- Use TLS - all Galaxy API endpoints require HTTPS
- Validate the issuer when verifying JWTs to prevent token confusion attacks
Code Examples
Python
import requests
# Production auth server ID
AUTH_SERVER_ID = "ausy7o2fb3m86Kc8Z4x7"
TOKEN_URL = f"https://auth.api.galaxy.com/oauth2/{AUTH_SERVER_ID}/v1/token"
def get_access_token(client_id, client_secret):
response = requests.post(
TOKEN_URL,
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"scope": "api:read"
}
)
response.raise_for_status()
return response.json()["access_token"]
Node.js
const axios = require('axios');
// Production auth server ID
const AUTH_SERVER_ID = 'ausy7o2fb3m86Kc8Z4x7';
const TOKEN_URL = `https://auth.api.galaxy.com/oauth2/${AUTH_SERVER_ID}/v1/token`;
async function getAccessToken(clientId, clientSecret) {
const response = await axios.post(
TOKEN_URL,
new URLSearchParams({
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
scope: 'api:read'
}),
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}
);
return response.data.access_token;
}
Java
import java.net.http.*;
import java.net.URI;
public class GalaxyAuth {
// Production auth server ID
private static final String AUTH_SERVER_ID = "ausy7o2fb3m86Kc8Z4x7";
private static final String TOKEN_URL =
"https://auth.api.galaxy.com/oauth2/" + AUTH_SERVER_ID + "/v1/token";
public static String getAccessToken(String clientId, String clientSecret) throws Exception {
HttpClient client = HttpClient.newHttpClient();
String body = String.format(
"grant_type=client_credentials&client_id=%s&client_secret=%s&scope=api:read",
clientId, clientSecret
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(TOKEN_URL))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
// Parse JSON response to extract access_token
// (use your preferred JSON library)
return parseAccessToken(response.body());
}
}
cURL
# Production auth server ID
AUTH_SERVER_ID="ausy7o2fb3m86Kc8Z4x7"
# Get token
TOKEN=$(curl -s -X POST "https://auth.api.galaxy.com/oauth2/${AUTH_SERVER_ID}/v1/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" \
-d "scope=api:read" | jq -r '.access_token')
# Use token
curl https://api.galaxy.com/service-reference-g1/accounts \
-H "Authorization: Bearer $TOKEN"
API Access Control (ACL)
Beyond authentication, some APIs have additional access control based on Consumer Groups. This allows Galaxy to scope external partners or systems to specific sets of APIs.
How It Works
┌─────────────────────────────────────────────────────────────────────────────┐
│ API Request Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Token Request 2. API Request │
│ ┌─────────┐ ┌─────────┐ │
│ │ Client │─────token request─────▶│ Okta │ │
│ │ App │◀────access_token──────│ │ │
│ └─────────┘ └─────────┘ │
│ │ │
│ │ 3. API call with token │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Kong Gateway │ │
│ │ ┌──────────┐ ┌──────────────┐ ┌───────────┐ ┌─────────┐ │ │
│ │ │ OIDC │───▶│ Consumer │───▶│ ACL │───▶│ API │ │ │
│ │ │ Plugin │ │ Lookup │ │ Check │ │ Backend │ │ │
│ │ └──────────┘ └──────────────┘ └───────────┘ └─────────┘ │ │
│ │ Validates Maps client_id Verifies Routes to │ │
│ │ JWT token to Consumer group access upstream │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
- OIDC Plugin validates your JWT token
- Consumer Lookup maps your
client_idto a Kong Consumer - ACL Plugin checks if your consumer's groups match the API's allow list
- If allowed, request is forwarded to the backend API
Consumer Groups
If your client is scoped to specific APIs, you'll be assigned to one or more consumer groups:
| Group | Description | Example APIs |
|---|---|---|
g1-access | G1 cluster APIs (trading, custody) | /g1/* endpoints |
gdtech-access | GDTech cluster APIs (internal tools) | /gdtech/* endpoints |
ACL Error Response
If you try to access an API your consumer isn't authorized for:
# Trying to access /gdtech/httpbin with a g1-only client
curl https://api.galaxy.com/gdtech/httpbin/get \
-H "Authorization: Bearer $TOKEN"
Response (403 Forbidden):
{
"message": "You cannot consume this service"
}
Requesting Access to Additional APIs
To request access to additional APIs:
- Contact Galaxy API Support at api-support@galaxy.com
- Specify which APIs you need access to
- Galaxy will add your consumer to the appropriate groups
- No credential changes needed - your existing token will work
Support
If you encounter authentication issues:
- Verify your client credentials are correct
- Ensure you're using the correct environment URL
- Check that your client has the required scopes
- Contact Galaxy API Support at api-support@galaxy.com