Error Reference

Every error returned by the Monogoto API uses the same JSON structure, making it straightforward to write a single error handler that covers all failure modes.


Error Response Format

{
  "status_code": 400,
  "message": "Field 'username' is required",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Field Type Description
status_code integer Mirrors the HTTP response status code
message string Human-readable explanation of what went wrong
request_id string Unique trace ID for this request — always include this when contacting support

Support tip: When filing a bug report or opening a support ticket, include the full error response body. The request_id allows the Monogoto team to locate the exact request in server-side logs.


Error Codes

400 Bad Request

The server could not process the request because the input was malformed or incomplete.

Common causes

  • A required field is missing from the request body
  • A field value is the wrong type (e.g. a string where an integer is expected)
  • A field value falls outside the allowed range or enum
  • The request body is not valid JSON

Example

{
  "status_code": 400,
  "message": "Field 'username' is required",
  "request_id": "b3c4d5e6-f7a8-9012-bcde-f12345678901"
}

Fix: Compare your request payload against the endpoint's schema in the API Reference. Pay attention to required fields, field names (case-sensitive), and allowed values.


401 Unauthorized

The request could not be authenticated. Either no token was provided, or the token is invalid or expired.

Common causes

  • The Authorization header is absent
  • The access token has expired (tokens are valid for 4 hours)
  • The token was manually revoked or invalidated by a refresh
  • The Bearer prefix is missing or misspelled

Example

{
  "status_code": 401,
  "message": "Token has expired or is invalid",
  "request_id": "c4d5e6f7-a8b9-0123-cdef-012345678902"
}

Fix: Refresh the access token using POST /v1/auth/refresh. If the refresh token has also expired, re-authenticate via POST /v1/auth/token. See the Authentication guide for the full token lifecycle.


403 Forbidden

The request was authenticated but the caller is not authorised to perform this action.

Common causes

  • The token lacks the required scope for this endpoint
  • The account has been suspended or restricted
  • Attempting to read or modify another account's resources

Example

{
  "status_code": 403,
  "message": "Insufficient scope: 'admin' required",
  "request_id": "d5e6f7a8-b9c0-1234-defa-123456789003"
}

Fix: Check the scope badge on the endpoint page in the API Reference. If you believe your account should have the required scope, contact support@monogoto.io and include the request_id.


404 Not Found

The requested resource does not exist at the given path.

Common causes

  • An incorrect or stale resource ID in the URL (e.g. wrong ICCID, group ID, or filter ID)
  • The resource was deleted after the ID was recorded
  • A typo in the URL path segment

Example

{
  "status_code": 404,
  "message": "Thing '8937010000000000000' not found",
  "request_id": "e6f7a8b9-c0d1-2345-efab-234567890004"
}

Fix: Verify the resource ID by listing the parent collection first (e.g. GET /v1/things to confirm the ICCID exists before calling GET /v1/things/{iccid}).


409 Conflict

The request is valid but conflicts with the current state of the resource on the server.

Common causes

  • Trying to activate a SIM that is already active
  • Creating a resource with a name or identifier that already exists
  • A concurrent modification conflict — another request mutated the resource between your read and write

Example

{
  "status_code": 409,
  "message": "SIM '8937010000000000000' is already in 'active' state",
  "request_id": "f7a8b9c0-d1e2-3456-fabc-345678900005"
}

Fix: Fetch the current state of the resource before mutating it. For concurrent modification scenarios, implement an optimistic locking strategy using ETags or version fields where the endpoint supports them.


422 Unprocessable Entity

The request body is structurally valid JSON with correct field types, but the values violate a business rule.

Common causes

  • A rate plan is not compatible with the SIM's home network
  • Insufficient account balance to complete the operation
  • A field combination that is logically invalid (e.g. conflicting filter rules)
  • Exceeding a resource quota

Example

{
  "status_code": 422,
  "message": "Rate plan 'PLAN-EU-5G' is not available for SIM on network 'US-MVNO-1'",
  "request_id": "a8b9c0d1-e2f3-4567-abcd-456789000006"
}

Fix: Read the message field carefully — it will describe the exact constraint that was violated. Consult the relevant endpoint documentation or contact support if the constraint is unclear.


429 Too Many Requests

Your integration has exceeded the rate limit for the current time window.

Common causes

  • Too many requests sent in a short window (API limit is per User ID)
  • Too many login or refresh attempts from one IP (auth limit is 5 per 60 seconds per IP)

Example

{
  "status_code": 429,
  "message": "Rate limit exceeded. Retry after 47 seconds.",
  "request_id": "b9c0d1e2-f3a4-5678-bcde-567890000007"
}

Fix: Read the Retry-After response header and wait the indicated number of seconds. Then retry with exponential backoff. See the Rate Limits guide for a complete implementation.


500 Internal Server Error

An unexpected error occurred on Monogoto's servers. This is not caused by your request.

Example

{
  "status_code": 500,
  "message": "An unexpected error occurred. Please try again or contact support.",
  "request_id": "c0d1e2f3-a4b5-6789-cdef-678900000008"
}

Fix: Retry the request with exponential backoff (1s, 2s, 4s). If the error persists after several retries, check the API Status page for ongoing incidents, or contact support@monogoto.io with the request_id.


502 Bad Gateway / 503 Service Unavailable

A downstream service that the API depends on is temporarily unavailable, or the API gateway itself is restarting.

Fix: These errors are transient. Apply the same retry-with-backoff approach as for 500 errors. Monitor the API Status page for incident updates. Under normal conditions these resolve within seconds to minutes.


Error Handling Patterns

Classifying Errors

Not all errors should be handled the same way. A well-designed integration distinguishes between errors that are worth retrying and those that require user intervention:

Status code Class Action
400 Client error — bad request Fix the input; do not retry
401 Auth error — token expired Refresh token, then retry once
403 Auth error — insufficient scope Do not retry; escalate to configuration
404 Client error — wrong ID Verify the ID; do not retry blindly
409 State conflict Read current state, adjust, then retry
422 Business rule violation Fix the request semantics; do not retry
429 Transient — rate limited Wait for Retry-After, then retry
500 Transient — server error Retry with exponential backoff
502 / 503 Transient — unavailable Retry with exponential backoff

Complete Error Handler

class ApiError extends Error {
  constructor(message, statusCode, requestId) {
    super(message);
    this.name = 'ApiError';
    this.statusCode = statusCode;
    this.requestId = requestId;
  }

  isRetryable() {
    return [429, 500, 502, 503].includes(this.statusCode);
  }

  isAuthError() {
    return this.statusCode === 401;
  }

  toString() {
    return `${this.name} [${this.statusCode}]: ${this.message} (request_id: ${this.requestId})`;
  }
}

async function callApi(url, options = {}, { tokenStore, maxRetries = 3 } = {}) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const res = await fetch(url, {
      ...options,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${tokenStore.accessToken}`,
        ...options.headers,
      },
    });

    if (res.ok) return res.json();

    const body = await res.json().catch(() => ({
      status_code: res.status,
      message: res.statusText,
      request_id: 'unknown',
    }));

    const err = new ApiError(body.message, body.status_code, body.request_id);

    // 401: refresh token and retry once
    if (err.isAuthError() && attempt === 0) {
      await tokenStore.refresh();
      continue;
    }

    // 429 / 5xx: wait and retry
    if (err.isRetryable() && attempt < maxRetries) {
      const retryAfter = res.headers.get('Retry-After');
      const delay = retryAfter
        ? parseInt(retryAfter, 10) * 1000
        : Math.pow(2, attempt) * 1000 + Math.random() * 500;
      console.warn(`${err} — retrying in ${(delay / 1000).toFixed(1)}s`);
      await new Promise(r => setTimeout(r, delay));
      continue;
    }

    // Non-retryable or exhausted retries
    throw err;
  }
}

  • Authentication — How to refresh tokens when you receive 401
  • Rate Limits — Retry strategies and backoff implementation for 429
  • API Status — Real-time platform health for 500/502/503 diagnosis