MY PROP JOURNAL

Pagination & errors

Pagination conventions, standard error bodies, and HTTP status codes.

Pagination

All list endpoints (trades, accounts, playbooks) support offset/page-based pagination to handle large datasets efficiently.

Pagination parameters

ParameterTypeDefaultMaximumDescription
offsetinteger0Number of items to skip
pageinteger1Page number (1-indexed); converted to offset internally
limitinteger20100Number of items per page

Use either offset or page to paginate. When both are provided, offset takes precedence.

Example requests

Get first page (default):

curl "https://app.mypropjournal.com/api/v1/trades" \
  -H "Authorization: Bearer mpj_your_api_key_here"

Get next page with offset:

curl "https://app.mypropjournal.com/api/v1/trades?offset=20&limit=25" \
  -H "Authorization: Bearer mpj_your_api_key_here"

JavaScript:

async function getPage(endpoint, offset = 0, limit = 20) {
  const url = new URL(`https://app.mypropjournal.com/api/v1/${endpoint}`);
  url.searchParams.append('offset', offset);
  url.searchParams.append('limit', limit);
  
  const response = await fetch(url, {
    headers: {
      'Authorization': 'Bearer mpj_your_api_key_here',
      'Content-Type': 'application/json'
    }
  });
  
  return response.json();
}

// Get second page (items 21-45)
const page2 = await getPage('trades', 20, 25);

Python:

import requests

def get_page(endpoint, offset=0, limit=20):
    url = f'https://app.mypropjournal.com/api/v1/{endpoint}'
    headers = {
        'Authorization': 'Bearer mpj_your_api_key_here',
        'Content-Type': 'application/json'
    }
    params = {'offset': offset, 'limit': limit}
    
    response = requests.get(url, headers=headers, params=params)
    response.raise_for_status()
    return response.json()

# Get items 101-200 (offset 100, limit 100)
page = get_page('trades', offset=100, limit=100)

Pagination response object

Every paginated response includes a pagination object with metadata:

{
  "data": [...],
  "pagination": {
    "total": 250,
    "limit": 25,
    "offset": 20,
    "has_more": true
  }
}

Pagination fields

FieldTypeDescription
totalintegerTotal number of items across all pages
limitintegerItems per page for this request
offsetintegerCurrent offset for this page of results
has_morebooleanWhether more items exist after this page

Fetching all pages

JavaScript example:

async function getAllTrades() {
  let allTrades = [];
  let offset = 0;
  let hasMore = true;
  const limit = 100;
  
  while (hasMore) {
    const response = await getPage('trades', offset, limit);
    allTrades = allTrades.concat(response.data);
    hasMore = response.pagination.has_more;
    offset += response.data.length;
    
    console.log(`Fetched offset ${offset - response.data.length}: ${response.data.length} trades`);
  }
  
  return allTrades;
}

const allTrades = await getAllTrades();
console.log(`Total trades fetched: ${allTrades.length}`);

Python example:

def get_all_trades():
    all_trades = []
    offset = 0
    has_more = True
    limit = 100
    
    while has_more:
        response = get_page('trades', offset=offset, limit=limit)
        all_trades.extend(response['data'])
        has_more = response['pagination']['has_more']
        offset += len(response['data'])
        
        print(f"Fetched offset {offset - len(response['data'])}: {len(response['data'])} trades")
    
    return all_trades

all_trades = get_all_trades()
print(f"Total trades fetched: {len(all_trades)}")

Pagination best practices

  1. Use the maximum limit (100) when fetching all data to minimize API calls
  2. Check has_more to determine if more pages exist
  3. Handle rate limits gracefully with exponential backoff (when implemented)
  4. Cache responses if data doesn't change frequently
  5. Show progress to users when fetching multiple pages

Error handling

Standard error response

All errors follow a consistent JSON structure:

{
  "error": "Human-readable error message",
  "code": "MACHINE_READABLE_CODE",
  "details": {
    "field_name": "Specific validation error"
  }
}

HTTP status codes

CodeStatusWhen it occurs
200OKSuccessful GET request
201CreatedSuccessful POST request (resource created)
204No ContentSuccessful DELETE request (no response body)
400Bad RequestInvalid request data or parameters
401UnauthorizedMissing or invalid API key
403ForbiddenValid API key but insufficient permissions
404Not FoundResource doesn't exist or isn't owned by you
500Internal Server ErrorServer-side error (rare)

Common error codes

Error CodeHTTP StatusDescription
INVALID_API_KEY401API key is malformed or doesn't exist
SUBSCRIPTION_REQUIRED403API access requires paid subscription
VALIDATION_ERROR400Request data failed validation
NOT_FOUND404Resource doesn't exist or isn't yours
UNAUTHORIZED401Authentication failed

Error examples

401 Unauthorized - Invalid API key:

{
  "error": "Invalid API key",
  "code": "INVALID_API_KEY"
}

403 Forbidden - Subscription required:

{
  "error": "API access requires an active paid subscription",
  "code": "SUBSCRIPTION_REQUIRED"
}

400 Bad Request - Validation error:

{
  "error": "Validation failed",
  "code": "VALIDATION_ERROR",
  "details": {
    "entry_price": "Must be a positive number",
    "side": "Must be 'long' or 'short'",
    "symbol": "Required field"
  }
}

404 Not Found - Resource not found:

{
  "error": "Trade not found",
  "code": "NOT_FOUND"
}

500 Internal Server Error:

{
  "error": "An unexpected error occurred. Please try again later.",
  "code": "INTERNAL_ERROR"
}

Error handling patterns

JavaScript/TypeScript

async function getTrade(tradeId) {
  try {
    const response = await fetch(
      `https://app.mypropjournal.com/api/v1/trades/${tradeId}`,
      {
        headers: {
          'Authorization': 'Bearer mpj_your_api_key_here',
          'Content-Type': 'application/json'
        }
      }
    );
    
    if (!response.ok) {
      const error = await response.json();
      
      switch (response.status) {
        case 401:
          console.error('Invalid API key');
          break;
        case 403:
          console.error('Subscription required for API access');
          break;
        case 404:
          console.error('Trade not found or access denied');
          break;
        default:
          console.error(`Error: ${error.error}`);
      }
      
      throw new Error(error.error);
    }
    
    return response.json();
  } catch (err) {
    console.error('Request failed:', err.message);
    throw err;
  }
}

Python

import requests
from requests.exceptions import HTTPError

def get_trade(trade_id):
    url = f'https://app.mypropjournal.com/api/v1/trades/{trade_id}'
    headers = {
        'Authorization': 'Bearer mpj_your_api_key_here',
        'Content-Type': 'application/json'
    }
    
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        return response.json()
        
    except HTTPError as err:
        error_data = err.response.json()
        error_code = error_data.get('code')
        error_message = error_data.get('error')
        
        if err.response.status_code == 401:
            print('Invalid API key')
        elif err.response.status_code == 403:
            print('Subscription required for API access')
        elif err.response.status_code == 404:
            print('Trade not found or access denied')
        else:
            print(f'Error: {error_message}')
        
        raise
        
    except requests.exceptions.RequestException as err:
        print(f'Request failed: {err}')
        raise

Retry logic with exponential backoff

For transient errors (5xx status codes), implement retry logic:

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      
      // Don't retry on 4xx errors (client errors)
      if (response.status >= 400 && response.status < 500) {
        return response;
      }
      
      // Retry on 5xx errors
      if (response.status >= 500) {
        if (attempt < maxRetries - 1) {
          const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
          console.log(`Server error, retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
      }
      
      return response;
    } catch (err) {
      if (attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`Request failed, retrying in ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      } else {
        throw err;
      }
    }
  }
}

Debugging tips

  1. Check the details field in validation errors for specific field-level messages
  2. Verify API key format starts with mpj_
  3. Confirm subscription status if receiving 403 errors
  4. Log full error responses during development for better debugging
  5. Test with cURL first to isolate issues from your application code

Example debugging command

# Test API key validity
curl -v "https://app.mypropjournal.com/api/v1/trades" \
  -H "Authorization: Bearer mpj_your_api_key_here" \
  2>&1 | grep -E "< HTTP|error|code"