---
title: "Prime Services Authentication"
url: "https://docs.api.galaxy.com/guides/galaxyone/authentication"
image: "https://docs.api.galaxy.com/_og/d/c_Ocean.takumi,title_Prime+Services+Authentication,props_eyJ0aGVtZSI6eyJtb2RlIjoiZGFyayIsImNvbG9ycyI6eyJwcmltYXJ5IjoiI2ZmNWExZiJ9fX0,p_Ii9ndWlkZXMvZ2FsYXh5b25lL2F1dGhlbnRpY2F0aW9uIg,s_M9lvQ7mHdklBGfzT.png"
---

# Prime Services Authentication

# Galaxy API Authentication Guide

This guide covers authentication for all Galaxy APIs using OAuth 2.0 Client Credentials flow.

## [Overview](#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](#getting-an-access-token)

### [Token Endpoint](#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:**

```text
https://{auth_domain}/oauth2/{auth_server_id}/v1/token
```

**Example for Production:**

```text
https://auth.api.galaxy.com/oauth2/ausy7o2fb3m86Kc8Z4x7/v1/token
```

### [Request](#request)

```bash
# 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](#response)

**Success (200 OK):**

```json
{
  "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):**

```json
{
  "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](#using-the-access-token)

Include the access token in the `Authorization` header for all API requests:

```bash
curl https://api.galaxy.com/service-reference-g1/accounts \
  -H "Authorization: Bearer eyJraWQiOiJKV1..."
```

## [Scopes](#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](#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](#_1-cache-and-reuse-tokens)

Don't request a new token for every API call. Cache the token and reuse it until it expires.

```python
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](#_2-proactive-token-refresh)

Refresh the token **before** it expires to avoid failed requests:

```python
# 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](#_3-handle-401-responses-gracefully)

If you receive a `401 Unauthorized` response, refresh the token and retry once:

```python
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](#_4-dont-hardcode-token-expiry)

Always use the `expires_in` value from the token response. Token lifetimes may change.

```python
# ✅ 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](#error-handling)

### [Authentication Errors](#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](#rate-limiting)

The token endpoint has rate limits. If you receive a `429 Too Many Requests`:

1.  Wait for the duration specified in the `Retry-After` header
2.  Review your token caching - you may be requesting tokens too frequently

## [OpenID Connect Discovery](#openid-connect-discovery)

For programmatic configuration, use the OpenID Connect discovery endpoint:

**Discovery URL Pattern:**

```text
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:

```json
{
  "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](#security-best-practices)

1.  **Never expose client secrets** in client-side code, logs, or version control
2.  **Use environment variables** or a secrets manager for credentials
3.  **Rotate credentials periodically** - contact Galaxy support to rotate your client secret
4.  **Use TLS** - all Galaxy API endpoints require HTTPS
5.  **Validate the issuer** when verifying JWTs to prevent token confusion attacks

## [Code Examples](#code-examples)

### [Python](#python)

```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](#nodejs)

```javascript
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](#java)

```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](#curl)

```bash
# 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)](#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](#how-it-works)

```text
┌─────────────────────────────────────────────────────────────────────────────┐
│                          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    │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

1.  **OIDC Plugin** validates your JWT token
2.  **Consumer Lookup** maps your `client_id` to a Kong Consumer
3.  **ACL Plugin** checks if your consumer's groups match the API's allow list
4.  If allowed, request is forwarded to the backend API

### [Consumer Groups](#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](#acl-error-response)

If you try to access an API your consumer isn't authorized for:

```bash
# 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):**

```json
{
  "message": "You cannot consume this service"
}
```

### [Requesting Access to Additional APIs](#requesting-access-to-additional-apis)

To request access to additional APIs:

1.  Contact Galaxy API Support at [api-support@galaxy.com](mailto:api-support@galaxy.com)
2.  Specify which APIs you need access to
3.  Galaxy will add your consumer to the appropriate groups
4.  No credential changes needed - your existing token will work

## [Support](#support)

If you encounter authentication issues:

1.  Verify your client credentials are correct
2.  Ensure you're using the correct environment URL
3.  Check that your client has the required scopes
4.  Contact Galaxy API Support at [api-support@galaxy.com](mailto:api-support@galaxy.com)

## [Related Pages](#related-pages)

-   [Getting Started](https://docs.api.galaxy.com/guides/galaxyone/getting-started.md)
-   [First API Call](https://docs.api.galaxy.com/guides/galaxyone/first-api-call.md)
-   [Core Concepts](https://docs.api.galaxy.com/guides/galaxyone/core-concepts.md)
-   [Errors](https://docs.api.galaxy.com/guides/galaxyone/errors.md)