Skip to main content

REST API Errors

The Meter API uses standard HTTP status codes and returns JSON error messages.

HTTP status codes

CodeMeaningDescription
200OKRequest succeeded
201CreatedResource created successfully
400Bad RequestInvalid request parameters
401UnauthorizedInvalid or missing API key
403ForbiddenValid key but insufficient permissions
404Not FoundResource doesn’t exist
422Unprocessable EntityRequest valid but semantically incorrect
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error
503Service UnavailableTemporary service issue

Error response format

All errors return JSON with a detail field:
{
  "detail": "Error message describing what went wrong"
}

Common errors

401 Unauthorized

Cause: Invalid or missing API key
{
  "detail": "Invalid or missing API key"
}
Solutions:
  • Verify Authorization header is included
  • Check API key is correct
  • Ensure key hasn’t been deleted

400 Bad Request

Cause: Invalid request parameters
{
  "detail": "Invalid URL format"
}
Common causes:
  • Missing required fields
  • Invalid field types
  • Malformed JSON
  • Invalid UUIDs
Solutions:
  • Check request body matches expected format
  • Verify all required fields are present
  • Ensure JSON is valid

404 Not Found

Cause: Resource doesn’t exist
{
  "detail": "Strategy not found"
}
Solutions:
  • Verify the UUID is correct
  • Check the resource hasn’t been deleted
  • Ensure you have permission to access it

422 Unprocessable Entity

Cause: Request is valid but contains semantic errors
{
  "detail": "Invalid URL format"
}
Solutions:
  • Check field values meet semantic requirements (e.g., valid URL format)
  • Verify data relationships are valid

500 Internal Server Error

Cause: Server-side error
{
  "detail": "Internal server error"
}
Solutions:
  • Retry the request after a delay
  • If persistent, contact support
  • Check API status page

Handling errors

JavaScript

const response = await fetch('https://api.meter.sh/api/strategies', {
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  }
});

if (!response.ok) {
  const error = await response.json();
  console.error(`API error (${response.status}): ${error.detail}`);

  if (response.status === 401) {
    // Handle authentication error
  } else if (response.status === 404) {
    // Handle not found
  } else if (response.status === 429) {
    // Handle rate limit - check Retry-After header
    const retryAfter = response.headers.get('Retry-After') || '60';
    console.log(`Rate limited. Retry after ${retryAfter} seconds.`);
  }
}

const data = await response.json();

Python

import requests
import time

response = requests.get(
    'https://api.meter.sh/api/strategies',
    headers={'Authorization': f'Bearer {api_key}'}
)

if not response.ok:
    error = response.json()
    print(f"API error ({response.status_code}): {error['detail']}")

    if response.status_code == 401:
        # Handle authentication error
        pass
    elif response.status_code == 404:
        # Handle not found
        pass
    elif response.status_code == 429:
        # Handle rate limit - check Retry-After header
        retry_after = int(response.headers.get('Retry-After', 60))
        print(f"Rate limited. Retry after {retry_after} seconds.")
        time.sleep(retry_after)

data = response.json()

curl

response=$(curl -s -w "\n%{http_code}" https://api.meter.sh/api/strategies \
  -H "Authorization: Bearer sk_live_...")

http_code=$(echo "$response" | tail -n 1)
body=$(echo "$response" | sed '$d')

if [ "$http_code" != "200" ]; then
  echo "Error ($http_code): $body"
fi

Best practices

For 429, 500, and 503 errors, retry with appropriate backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);

      // Rate limited - respect Retry-After header
      if (response.status === 429) {
        const retryAfter = response.headers.get('Retry-After') || '60';
        await new Promise(r => setTimeout(r, parseInt(retryAfter) * 1000));
        continue;
      }

      // Server errors - exponential backoff
      if ((response.status === 500 || response.status === 503) && i < maxRetries - 1) {
        await new Promise(r => setTimeout(r, 2 ** i * 1000));
        continue;
      }

      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await new Promise(r => setTimeout(r, 2 ** i * 1000));
    }
  }
}
Always check HTTP status codes before parsing responses:
const response = await fetch(url, options);

// Check before parsing JSON
if (!response.ok) {
  const error = await response.json();
  throw new Error(error.detail);
}

const data = await response.json();
Include request details when logging errors:
if (!response.ok) {
  const error = await response.json();
  console.error('API request failed', {
    status: response.status,
    detail: error.detail,
    url: response.url,
    method: options.method
  });
}

Rate limiting

Strategy generation endpoints (/api/strategies/generate, /api/watch) are rate limited to prevent overloading the underlying LLM service.

429 Too Many Requests

When you hit rate limits, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
  "detail": "Please slow down - limits on strategy generation"
}
The Retry-After header tells you how many seconds to wait before retrying.

Handling rate limits

JavaScript:
const response = await fetch(url, options);

if (response.status === 429) {
  const retryAfter = response.headers.get('Retry-After') || '60';
  const waitSeconds = parseInt(retryAfter, 10);

  console.log(`Rate limited. Waiting ${waitSeconds} seconds...`);
  await new Promise(r => setTimeout(r, waitSeconds * 1000));

  // Retry the request
  return fetch(url, options);
}
Python:
response = requests.post(url, headers=headers, json=data)

if response.status_code == 429:
    retry_after = int(response.headers.get('Retry-After', 60))
    print(f"Rate limited. Waiting {retry_after} seconds...")
    time.sleep(retry_after)

    # Retry the request
    response = requests.post(url, headers=headers, json=data)

503 Service Unavailable

If the LLM service is temporarily unavailable:
{
  "detail": "Service temporarily unavailable. Please try again later."
}

Next steps

Need help?

Email me at mckinnon@meter.sh