Skip to main content

Base URL

All API requests should be made to:
https://api.aries.com

Quick Start

1

Create OAuth2 Client

Log into Aries and navigate to Client Center > API tab to create an OAuth2 client and get your client_id and client_secret
2

Configure Scopes

Select the permission scopes your application needs (e.g., account:information, order:execution)
3

Implement OAuth2 Flow

Redirect users to the authorization endpoint, handle the callback, and exchange the authorization code for access tokens
4

Start Trading

Use the access token to make authenticated API requests on behalf of your users

Prerequisites

Creating an OAuth2 Client

Before you can integrate OAuth2, you need to create an OAuth2 client:
1

Access Client Center

Log into Aries and navigate to Client Center > API tab
2

Create New Client

Click Create New Client or Add OAuth2 Client
3

Configure Your Client

Provide the following information:
  • Client Name: A descriptive name for your application
  • Redirect URIs: One or more callback URLs (e.g., https://yourapp.com/oauth/callback)
  • Scopes: Select the permission scopes your application needs
4

Save Credentials

After creation, you’ll receive:
  • Client ID: Public identifier for your application
  • Client Secret: Confidential key (store securely - shown only once!)
The client_secret is shown only once during creation. Store it securely in your environment variables or secrets manager. If lost, you’ll need to regenerate it.

Available Scopes

Scopes define the permissions your application has to access user data and perform actions. Request only the scopes your application needs.

Currently Supported Scopes

ScopeDescriptionExample Endpoints
user:managementManage user accounts, update profiles, modify settings, and perform administrative operationsPATCH /v1/users/me
POST /v1/users/me/email/change
POST /v1/users/me/trading-password
user:informationView user profile information, personal details, and preferencesGET /v1/users/me
GET /v1/users/me/accounts
GET /v1/users/me/profile
account:informationView account balances, positions, transaction history, and account detailsGET /v1/accounts/{id}/balances
GET /v1/accounts/{id}/positions
WS /v1/accounts/ws
order:executionPlace, modify, and cancel orders. Execute trades on user’s behalfPOST /v1/orders
PUT /v1/orders
DELETE /v1/orders
POST /v1/orders/preview
order:informationView order history, status, execution reports, and order detailsGET /v1/accounts/{id}/orders
position:informationView current positions, holdings, and position analyticsGET /v1/accounts/{id}/positions
market:informationAccess live and historical market data, quotes, and instrument informationGET /v1/marketdata/search
WSS /v1/marketdata/ws
GET /v1/equity/top-gainers
calendar:informationAccess calendar events, earnings announcements, and market schedulesGET /v1/calendars/economics
GET /v1/calendars/earnings
GET /v1/calendars/historical/{symbol}
options:informationAccess options chains, Greeks, expiration data, and options analyticsGET /v1/options/stocks/top-volume
GET /v1/options/etfs/top-volume
analytics:informationView analytics, ratings, portfolio metrics, and market insightsGET /v1/analytics/ratings
GET /v1/analytics/insights
GET /v1/analytics/market-breadth
When creating your OAuth2 client, you can select multiple scopes separated by spaces. For example: account:information order:execution market:information

OAuth2 Authorization Flow

The standard OAuth2 Authorization Code flow. This is the recommended approach for web and mobile applications.

Flow Overview

Step 1: Redirect User to Authorization URL

Redirect users to the Aries authorization endpoint:
https://app.tradearies.dev/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&scope=account:information order:execution&state=RANDOM_STRING
Query Parameters:
ParameterRequiredDescription
response_typeYesMust be code for authorization code flow
client_idYesYour OAuth2 client ID
redirect_uriYesMust exactly match one of your registered redirect URIs
scopeYesSpace-separated list of scopes (e.g., account:information order:execution)
stateRecommendedRandom string to prevent CSRF attacks. Verify this matches on callback
code_challengeOptionalOnly needed if implementing PKCE flow (advanced). Base64 URL-encoded SHA256 hash of the code_verifier
code_challenge_methodOptionalOnly needed if implementing PKCE flow (advanced). Must be S256 or plain
Example Implementation (Node.js):
const crypto = require('crypto');

// Generate state for CSRF protection
const state = crypto.randomBytes(32).toString('hex');
// Store state in session for verification later

// Build authorization URL
const params = new URLSearchParams({
  response_type: 'code',
  client_id: 'your_client_id',
  redirect_uri: 'https://yourapp.com/oauth/callback',
  scope: 'account:information order:execution market:information',
  state: state
});

const authUrl = `https://app.tradearies.dev/oauth2/authorize?${params}`;

// Redirect user to authUrl
res.redirect(authUrl);
After redirecting, users will:
  1. Log in with their Aries credentials
  2. Review permissions your app is requesting
  3. Approve or deny access

Step 3: Handle Authorization Callback

After approval, Aries redirects the user back to your redirect_uri with the authorization code:
https://yourapp.com/oauth/callback?code=AUTHORIZATION_CODE&state=RANDOM_STRING
Callback Parameters:
ParameterDescription
codeAuthorization code (valid for 10 minutes, single-use)
stateThe state parameter you provided. Verify it matches!
Error Response:
https://yourapp.com/oauth/callback?error=access_denied&error_description=User+denied+access&state=RANDOM_STRING
Example Callback Handler (Express.js):
app.get('/oauth/callback', async (req, res) => {
  // Verify state to prevent CSRF
  if (req.query.state !== req.session.oauth_state) {
    return res.status(400).send('Invalid state parameter');
  }

  // Check for errors
  if (req.query.error) {
    return res.status(400).send(`OAuth error: ${req.query.error}`);
  }

  // Get authorization code
  const authCode = req.query.code;
  if (!authCode) {
    return res.status(400).send('No authorization code received');
  }

  // Exchange code for tokens (next step)
  const tokens = await exchangeCodeForToken(authCode);
  // Store tokens and redirect to app
  res.redirect('/dashboard');
});

Step 4: Exchange Code for Access Token

Exchange the authorization code for access and refresh tokens using the /oauth2/token endpoint. Request:
curl -X POST 'https://api.aries.com/v1/oauth2/token' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "your_client_id",
    "client_secret": "your_client_secret",
    "grant_type": "code",
    "code": "AUTHORIZATION_CODE",
    "redirect_uri": "https://yourapp.com/oauth/callback"
  }'
Response:
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ...",
  "scope": "account:information order:execution market:information"
}
Response Fields:
FieldDescription
access_tokenJWT token for API requests (valid for duration specified in expires_in)
token_typeAlways Bearer
expires_inNumber of seconds until access token expires (typically 3600 = 1 hour)
refresh_tokenLong-lived token to obtain new access tokens
scopeGranted scopes (may differ from requested if user denied some)

Step 5: Make Authenticated API Requests

Use the access token in the Authorization header for all API requests:
curl -X GET 'https://api.aries.com/v1/users/me' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Node.js Example:
async function getUserInfo(accessToken) {
  const response = await fetch('https://api.aries.com/v1/users/me', {
    headers: { 'Authorization': `Bearer ${accessToken}` }
  });

  if (!response.ok) throw new Error('Failed to fetch user info');

  return await response.json();
}

// Usage
const userInfo = await getUserInfo(tokens.access_token);
console.log('User:', userInfo.email);

Refresh Token Flow

Access tokens expire after a period (typically 1 hour). Use the refresh token to obtain new access tokens without requiring user re-authentication.

Refreshing Access Token

Request:
curl -X POST 'https://api.aries.com/v1/oauth2/token' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "your_client_id",
    "client_secret": "your_client_secret",
    "grant_type": "refresh_token",
    "refresh_token": "YOUR_REFRESH_TOKEN",
    "redirect_uri": "https://yourapp.com/oauth/callback"
  }'
Response:
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAifQ...",
  "scope": "account:information order:execution market:information"
}

Best Practices

Security

Store Credentials Securely

Never hardcode client_secret or tokens in your code. Use environment variables or secrets management services.
const CLIENT_SECRET = process.env.ARIES_CLIENT_SECRET;

Use HTTPS Only

Always use HTTPS for redirect URIs and API calls in production. Never transmit credentials over HTTP.

Validate State Parameter

Always generate and verify the state parameter to prevent CSRF attacks.
// Before redirect
const state = crypto.randomBytes(32).toString('hex');
req.session.oauth_state = state;

// On callback
if (req.query.state !== req.session.oauth_state) {
  return res.status(400).send('Invalid state');
}

Implement Token Rotation

Refresh tokens before they expire. Update stored refresh tokens when new ones are issued.

Error Handling

async function makeApiRequest(endpoint, accessToken, method = 'GET', data = null) {
  const url = `https://api.aries.com${endpoint}`;
  const options = {
    method,
    headers: { 'Authorization': `Bearer ${accessToken}` },
    signal: AbortSignal.timeout(10000) // 10 second timeout
  };

  if (data) {
    options.headers['Content-Type'] = 'application/json';
    options.body = JSON.stringify(data);
  }

  try {
    const response = await fetch(url, options);

    if (!response.ok) {
      const status = response.status;

      if (status === 401) {
        // Token expired or invalid
        console.log('Authentication failed. Refresh token or re-authenticate.');
        // Attempt token refresh
      } else if (status === 403) {
        // Insufficient permissions
        console.log('Insufficient permissions. Check granted scopes.');
      } else if (status === 429) {
        // Rate limit exceeded
        const retryAfter = response.headers.get('Retry-After') || 60;
        console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds.`);
      } else {
        const errorText = await response.text();
        console.log(`HTTP error: ${status} - ${errorText}`);
      }

      throw new Error(`HTTP ${status}`);
    }

    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Request timed out. Check network connection.');
    } else {
      console.log(`Request failed: ${error.message}`);
    }
    throw error;
  }
}

// Usage
try {
  const userData = await makeApiRequest('/v1/users/me', accessToken);
  console.log('User:', userData.email);
} catch (error) {
  console.log('Failed to fetch user data:', error);
}

Rate Limiting

Different endpoints have different rate limits. Implement exponential backoff for rate limit errors:
async function makeRequestWithRetry(endpoint, accessToken, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await makeApiRequest(endpoint, accessToken);
    } catch (error) {
      if (error.message.includes('429') && attempt < maxRetries - 1) {
        const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff: 1s, 2s, 4s
        console.log(`Rate limited. Retrying in ${waitTime/1000}s...`);
        await new Promise(resolve => setTimeout(resolve, waitTime));
        continue;
      }
      throw error;
    }
  }

  throw new Error('Max retries exceeded');
}

Troubleshooting

Common Errors

Error:
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired or already been used"
}
Causes:
  • Authorization code was used more than once (codes are single-use)
  • More than 10 minutes elapsed between code generation and exchange
  • Code was already exchanged for tokens
Solution:
  • Start the OAuth flow again from the beginning
  • Ensure your code exchange happens immediately after receiving the code
  • Don’t reuse authorization codes
Error:
{
  "error": "invalid_client",
  "error_description": "Client authentication failed"
}
Causes:
  • Incorrect client_id or client_secret
  • Client has been deleted or disabled
  • Using test/invalid credentials
Solution:
  • Verify your client credentials in the developer portal
  • Ensure you’re using the correct client_secret (it’s only shown once during creation)
  • Check that your OAuth2 client is active
Error:
{
  "error": "invalid_scope",
  "error_description": "One or more requested scopes are invalid or not allowed"
}
Causes:
  • Requesting scopes not configured for your client
  • Typo in scope name
  • Requesting scopes that don’t exist
Solution:
  • Verify scope names match exactly (case-sensitive)
  • Check your OAuth2 client configuration in the developer portal
  • Ensure the scopes are enabled for your client
Error:
{
  "error": "redirect_uri_mismatch",
  "error_description": "Redirect URI does not match registered URIs"
}
Causes:
  • redirect_uri doesn’t exactly match one registered in your client
  • Query parameters or fragments in redirect URI
  • Protocol mismatch (http vs https)
Solution:
  • Redirect URI must match exactly, including trailing slashes
  • Register all redirect URIs you’ll use in the developer portal
  • Use HTTPS in production
Error (in callback URL):
?error=access_denied&error_description=User+denied+access
Cause:
  • User clicked “Deny” on the consent screen
Solution:
  • Display a friendly message explaining why permissions are needed
  • Provide a way for users to retry authorization
  • Consider requesting fewer scopes if users frequently deny access

Debugging Tips

1. Enable Request Logging
// Enable fetch debugging with a wrapper
const originalFetch = fetch;
global.fetch = async (...args) => {
  console.log('Request:', args[0], args[1]);
  const response = await originalFetch(...args);
  console.log('Response:', response.status, response.statusText);
  return response;
};
2. Inspect Token Claims
function decodeToken(accessToken) {
  // Decode JWT token (without verification for debugging)
  // Note: Don't use this for authentication, only debugging
  const parts = accessToken.split('.');
  const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
  console.log(JSON.stringify(payload, null, 2));
  return payload;
}

// Check token scopes
const decoded = decodeToken(accessToken);
console.log('Token scopes:', decoded.scope);
console.log('Expires at:', new Date(decoded.exp * 1000));
3. Test OAuth Flow Use the OAuth2 Debugger or Postman to test your OAuth2 configuration.

API Categories

Explore the available API endpoints organized by category:

Rate Limits

API rate limits vary by endpoint category:

Authentication

10 requests/minute

User Management

100 requests/minute

Market Data

1,000 requests/minute

Trading

50 requests/minute
Rate limit headers are included in every response:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000

HTTP Status Codes

CodeStatusDescription
200SuccessRequest completed successfully
400Bad RequestValidation error in request
401UnauthorizedInvalid or missing authentication
403ForbiddenInsufficient permissions/scopes
404Not FoundResource does not exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error

Error Response Format

All error responses follow this format:
{
  "error": "ERROR_CODE",
  "error_description": "Human-readable error description",
  "code": "SPECIFIC_ERROR_CODE"
}

Support & Resources

Email Support

Developer Portal


Ready to integrate OAuth2? Create your client in the Developer Portal and start building!