Skip to main content

API Credentials

API Credentials allow users to create OAuth2 client credentials for programmatic access to the CXTMS API. Each credential provides a client_id and client_secret for use with the OAuth2 client credentials flow.

Overview​

  • Authentication Method: OAuth2 Client Credentials Grant
  • Security: Secrets hashed using PBKDF2-HMAC-SHA256 (100,000 iterations)
  • Limit: Maximum 5 credentials per user
  • Features: Optional IP restrictions, expiration dates, and soft delete
  • Permissions: Credentials inherit the user's roles and permissions

Authorization​

  • Regular Users: Can create API credentials for themselves only
  • Administrators: Can create API credentials for any user in their organization (must be member of "Administrators" group)

GraphQL Schema​

Types​

ApiCredential​

type apiCredential {
apiCredentialId: Int!
organizationId: Int!
userId: String!
name: String!
clientId: String!
expiresAt: DateTime
allowedIpAddresses: [String]
isDeleted: Boolean!
lastUsedAt: DateTime
createdBy: String
created: DateTime
lastModifiedBy: String
lastModified: DateTime
}

ApiCredentialWithSecret​

Returned only during creation or secret regeneration. This is the only time the plaintext secret is shown.

type apiCredentialWithSecret {
apiCredentialId: Int!
organizationId: Int!
userId: String!
name: String!
clientId: String!
clientSecret: String! # Only shown once!
expiresAt: DateTime
allowedIpAddresses: [String]
isDeleted: Boolean!
lastUsedAt: DateTime
createdBy: String
created: DateTime
lastModifiedBy: String
lastModified: DateTime
}

Queries​

Get Single API Credential​

Retrieve a specific API credential by ID. Users can only access their own credentials.

query {
apiCredential(
organizationId: 1
apiCredentialId: 123
) {
apiCredentialId
name
clientId
expiresAt
allowedIpAddresses
lastUsedAt
created
}
}

List API Credentials​

Retrieve all API credentials for the current user within an organization.

query {
apiCredentials(
organizationId: 1
search: "production"
orderBy: "created desc"
skip: 0
take: 10
) {
items {
apiCredentialId
name
clientId
expiresAt
lastUsedAt
}
pageInfo {
hasNextPage
hasPreviousPage
}
totalCount
}
}

Parameters:

  • organizationId (required): Organization ID
  • search (optional): Search by name or clientId
  • filter (optional): Filter expression
  • orderBy (optional): Sort order (e.g., "created desc", "name asc")
  • skip (optional): Number of items to skip for pagination
  • take (optional): Number of items to return (max 100)

Mutations​

Create API Credential​

Create a new API credential. Returns the credential with the plaintext clientSecret - this is the only time it will be shown.

For yourself:

mutation {
createApiCredential(
organizationId: 1
values: {
name: "Production API Key"
expiresAt: "2026-12-31T23:59:59Z"
allowedIpAddresses: ["192.168.1.0/24", "10.0.0.5"]
}
) {
apiCredentialId
name
clientId
clientSecret # Save this - it won't be shown again!
expiresAt
allowedIpAddresses
}
}

For another user (Administrators only):

mutation {
createApiCredential(
organizationId: 1
values: {
name: "John's API Key"
userId: "user-abc-123" # Only Administrators can specify this
expiresAt: "2026-12-31T23:59:59Z"
}
) {
apiCredentialId
userId
clientId
clientSecret
}
}

Response:

{
"data": {
"createApiCredential": {
"apiCredentialId": 456,
"name": "Production API Key",
"clientId": "api-a1b2c3d4e5f6",
"clientSecret": "dGVzdHNlY3JldDEyMzQ1Njc4OTBhYmNkZWZnaGlqa2xtbm8",
"expiresAt": "2026-12-31T23:59:59Z",
"allowedIpAddresses": ["192.168.1.0/24", "10.0.0.5"]
}
}
}

Important: Store the clientSecret securely. It cannot be retrieved after creation.

Update API Credential​

Update an existing API credential's properties. Users can only update their own credentials.

mutation {
updateApiCredential(
organizationId: 1
apiCredentialId: 456
values: {
name: "Production API Key (Updated)"
expiresAt: "2027-12-31T23:59:59Z"
allowedIpAddresses: ["192.168.1.0/24", "10.0.0.0/8"]
}
) {
apiCredentialId
name
expiresAt
allowedIpAddresses
lastModified
}
}

Regenerate Secret​

Generate a new secret for an existing credential. Returns the new plaintext secret - this is the only time it will be shown.

mutation {
regenerateApiCredentialSecret(
organizationId: 1
apiCredentialId: 456
) {
apiCredentialId
clientId
clientSecret # New secret - save this!
lastModified
}
}

Delete API Credential​

Soft-delete an API credential. Deleted credentials can no longer be used for authentication.

mutation {
deleteApiCredential(
organizationId: 1
apiCredentialId: 456
) {
deletedCount
deletedId
}
}

IP Address Restrictions​

API credentials support IP-based access control using the allowedIpAddresses field. If specified, the credential can only be used from requests originating from the allowed IP addresses.

Supported Formats​

  • IPv4: 192.168.1.5
  • IPv6: 2001:0db8:85a3::8a2e:0370:7334
  • CIDR Notation: 192.168.1.0/24, 10.0.0.0/8, 2001:db8::/32

Examples​

Single IP:

allowedIpAddresses: ["203.0.113.10"]

Multiple IPs:

allowedIpAddresses: ["192.168.1.5", "10.0.0.100", "2001:db8::1"]

CIDR Ranges:

allowedIpAddresses: ["192.168.1.0/24", "10.0.0.0/8"]

No Restrictions:

allowedIpAddresses: null # or omit the field

Using API Credentials​

OAuth2 Token Request​

Use the credential to obtain an access token via OAuth2 Client Credentials flow:

POST /connect/token HTTP/1.1
Host: api.cargoxplorer.com
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=api-a1b2c3d4e5f6
&client_secret=dGVzdHNlY3JldDEyMzQ1Njc4OTBhYmNkZWZnaGlqa2xtbm8

Response:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}

Using the Access Token​

Include the access token in the Authorization header:

POST /graphql HTTP/1.1
Host: api.cargoxplorer.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
"query": "{ orders(organizationId: 1) { items { orderId } } }"
}

Code Example (JavaScript)​

// Step 1: Get access token
async function getAccessToken(clientId, clientSecret) {
const response = await fetch('https://api.cargoxplorer.com/connect/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: clientId,
client_secret: clientSecret,
}),
});

const data = await response.json();
return data.access_token;
}

// Step 2: Use access token with GraphQL
async function queryOrders(accessToken, organizationId) {
const response = await fetch('https://api.cargoxplorer.com/graphql', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
query {
orders(organizationId: ${organizationId}) {
items {
orderId
trackingNumber
status
}
}
}
`,
}),
});

return await response.json();
}

// Usage
const clientId = 'api-a1b2c3d4e5f6';
const clientSecret = 'dGVzdHNlY3JldDEyMzQ1Njc4OTBhYmNkZWZnaGlqa2xtbm8';

const token = await getAccessToken(clientId, clientSecret);
const orders = await queryOrders(token, 1);
console.log(orders);

Code Example (Python)​

import requests

# Step 1: Get access token
def get_access_token(client_id, client_secret):
response = requests.post(
'https://api.cargoxplorer.com/connect/token',
data={
'grant_type': 'client_credentials',
'client_id': client_id,
'client_secret': client_secret,
}
)
return response.json()['access_token']

# Step 2: Use access token with GraphQL
def query_orders(access_token, organization_id):
response = requests.post(
'https://api.cargoxplorer.com/graphql',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
},
json={
'query': f'''
query {{
orders(organizationId: {organization_id}) {{
items {{
orderId
trackingNumber
status
}}
}}
}}
'''
}
)
return response.json()

# Usage
client_id = 'api-a1b2c3d4e5f6'
client_secret = 'dGVzdHNlY3JldDEyMzQ1Njc4OTBhYmNkZWZnaGlqa2xtbm8'

token = get_access_token(client_id, client_secret)
orders = query_orders(token, 1)
print(orders)

Security Best Practices​

Credential Management​

  1. Store Secrets Securely: Never commit secrets to version control
  2. Use Environment Variables: Store credentials in environment variables or secure vaults
  3. Rotate Regularly: Regenerate secrets periodically using the regenerateApiCredentialSecret mutation
  4. Delete Unused Credentials: Remove credentials that are no longer needed
  5. Monitor Usage: Check the lastUsedAt field to identify unused credentials

IP Restrictions​

  1. Use CIDR Notation: Restrict access to known IP ranges
  2. Minimize Exposure: Use the smallest IP range necessary
  3. Update When Needed: Update allowedIpAddresses when infrastructure changes

Expiration​

  1. Set Expiration Dates: Always set an expiresAt value for production credentials
  2. Renew Before Expiry: Create new credentials before old ones expire
  3. Short-Lived for Testing: Use short expiration times for development/testing

Token Handling​

  1. Cache Tokens: Access tokens are valid for 1 hour - cache and reuse them
  2. Handle Expiration: Implement token refresh logic when tokens expire
  3. Secure Storage: Store tokens securely in memory, never in local storage or cookies

Limits and Quotas​

  • Maximum Credentials per User: 5
  • Client ID Format: api-{32-character-hex}
  • Secret Length: 43 characters (URL-safe Base64)
  • Token Lifetime: 3600 seconds (1 hour)
  • Name Length: Maximum 100 characters
  • IP Addresses: Maximum 50 addresses per credential

Error Handling​

Common Errors​

Maximum credentials exceeded:

{
"errors": [{
"message": "Maximum of 5 API credentials per user is allowed",
"extensions": {
"code": "INVALID_OPERATION"
}
}]
}

Unauthorized (non-admin creating for another user):

{
"errors": [{
"message": "Only members of the Administrators group can create API credentials for other users",
"extensions": {
"code": "UNAUTHORIZED"
}
}]
}

Invalid IP address:

{
"errors": [{
"message": "All IP addresses must be valid IPv4, IPv6, or CIDR notation",
"extensions": {
"code": "VALIDATION_ERROR"
}
}]
}

Credential not found:

{
"errors": [{
"message": "ApiCredential with id 999 was not found",
"extensions": {
"code": "NOT_FOUND"
}
}]
}

IP blocked:

HTTP/1.1 403 Forbidden
{
"error": "invalid_client",
"error_description": "IP address not allowed"
}

FAQ​

How do I get my first API credential?​

Use the CXTMS web interface:

  1. Navigate to Settings → API Credentials
  2. Click "Create New Credential"
  3. Fill in the details and save
  4. Copy the secret immediately - it won't be shown again

What happens if I lose my client secret?​

You cannot retrieve a lost secret. Use the regenerateApiCredentialSecret mutation to generate a new one. The old secret will be invalidated immediately.

Can I use the same credential from multiple servers?​

Yes, if those servers are within your allowed IP ranges. If no IP restrictions are set, the credential can be used from anywhere.

How do I test my API credential?​

Use tools like Postman, cURL, or the code examples above to:

  1. Request an access token from /connect/token
  2. Use the token to query /graphql

Why is my credential not working?​

Check the following:

  • Credential hasn't expired (expiresAt)
  • Credential isn't deleted (isDeleted: false)
  • IP address is allowed (if allowedIpAddresses is set)
  • Client ID and secret are correct
  • Token hasn't expired (tokens last 1 hour)

Can Administrators see other users' secrets?​

No. Secrets are hashed and cannot be retrieved by anyone, including administrators. Administrators can create credentials for others but cannot view their secrets after creation.

See Also​