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 IDsearch(optional): Search by name or clientIdfilter(optional): Filter expressionorderBy(optional): Sort order (e.g., "created desc", "name asc")skip(optional): Number of items to skip for paginationtake(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
- Store Secrets Securely: Never commit secrets to version control
- Use Environment Variables: Store credentials in environment variables or secure vaults
- Rotate Regularly: Regenerate secrets periodically using the
regenerateApiCredentialSecretmutation - Delete Unused Credentials: Remove credentials that are no longer needed
- Monitor Usage: Check the
lastUsedAtfield to identify unused credentials
IP Restrictions
- Use CIDR Notation: Restrict access to known IP ranges
- Minimize Exposure: Use the smallest IP range necessary
- Update When Needed: Update
allowedIpAddresseswhen infrastructure changes
Expiration
- Set Expiration Dates: Always set an
expiresAtvalue for production credentials - Renew Before Expiry: Create new credentials before old ones expire
- Short-Lived for Testing: Use short expiration times for development/testing
Token Handling
- Cache Tokens: Access tokens are valid for 1 hour - cache and reuse them
- Handle Expiration: Implement token refresh logic when tokens expire
- 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