Skip to content

Error Handling

Error Response Format

All API errors follow a consistent JSON structure with an appropriate HTTP status code:

json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description of what went wrong"
  }
}

The code field is a stable, machine-readable identifier you can use in your error handling logic. The message field provides additional context and may vary between occurrences of the same error code.

Error Categories

Authentication Errors (401)

Returned when the request cannot be authenticated.

CodeDescription
UNAUTHORIZEDThe Authorization header is missing, malformed, or contains an invalid token.
KEY_EXPIREDThe API key was valid but has passed its expiration date. Create a new key to continue.
json
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing or invalid Authorization header"
  }
}

Authorization Errors (403)

Returned when the authenticated user does not have permission to access the requested resource.

CodeDescription
FORBIDDENYou are attempting to access or modify a resource that belongs to another user (e.g. revoking someone else's API key).

Not Found Errors (404)

Returned when the requested resource does not exist.

CodeDescription
NOT_FOUNDThe resource identified in the URL (extraction, API key, etc.) does not exist or has been deleted.

Validation Errors (400)

Returned when the request body or parameters are invalid.

CodeDescription
VALIDATION_ERRORThe request body failed schema validation. The message field describes which fields are invalid.
MAX_KEYS_REACHEDYou have reached the maximum of 10 active API keys. Revoke an existing key before creating a new one.
json
{
  "error": {
    "code": "MAX_KEYS_REACHED",
    "message": "Maximum of 10 active API keys allowed"
  }
}

Rate Limiting Errors (429)

Returned when usage limits have been exceeded.

CodeDescription
USAGE_LIMIT_EXCEEDEDYour monthly extraction limit has been reached. Upgrade your plan or wait until the next billing period.
json
{
  "error": {
    "code": "USAGE_LIMIT_EXCEEDED",
    "message": "Monthly extraction limit reached (25/25). Upgrade your plan for more extractions."
  }
}

Server Errors (500)

Returned when something unexpected happens on the server side.

CodeDescription
INTERNAL_ERRORAn unexpected server error occurred. These are automatically logged and investigated.
EXTRACTION_FAILEDThe AI processing pipeline encountered an error while extracting data from your document. The document may be corrupted, unreadable, or in an unsupported format.

Retryable Errors

Not all errors should be retried. Here is a breakdown:

CodeRetryableAction
INTERNAL_ERRORYesRetry with exponential backoff
EXTRACTION_FAILEDYesRetry -- the AI may produce a different result. If it persists, check the document quality
USAGE_LIMIT_EXCEEDEDNoUpgrade your plan or wait until the next billing period
UNAUTHORIZEDNoFix your authentication credentials
KEY_EXPIREDNoCreate a new API key
FORBIDDENNoYou are accessing a resource you do not own
NOT_FOUNDNoThe resource does not exist -- verify the ID
VALIDATION_ERRORNoFix the request body to match the expected schema
MAX_KEYS_REACHEDNoRevoke an existing key before creating a new one

Retry Strategy

For retryable errors, use exponential backoff to avoid overwhelming the server. Here is a reusable implementation:

typescript
interface ApiResponse<T> {
  data?: T;
  error?: { code: string; message: string };
}

async function fetchWithRetry<T>(
  url: string,
  options: RequestInit,
  maxRetries = 3,
): Promise<ApiResponse<T>> {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      const body = await response.json();

      if (response.ok) {
        return body as ApiResponse<T>;
      }

      const errorCode = body?.error?.code;

      // Only retry server errors
      if (
        (errorCode === "INTERNAL_ERROR" || errorCode === "EXTRACTION_FAILED") &&
        attempt < maxRetries
      ) {
        const delayMs = 1000 * Math.pow(2, attempt); // 1s, 2s, 4s
        console.log(`Retrying in ${delayMs}ms (attempt ${attempt + 1}/${maxRetries})...`);
        await new Promise((resolve) => setTimeout(resolve, delayMs));
        continue;
      }

      // Non-retryable error -- return immediately
      return body as ApiResponse<T>;
    } catch (err) {
      lastError = err as Error;

      if (attempt < maxRetries) {
        const delayMs = 1000 * Math.pow(2, attempt);
        await new Promise((resolve) => setTimeout(resolve, delayMs));
        continue;
      }
    }
  }

  throw lastError ?? new Error("Max retries exceeded");
}

Usage:

typescript
const result = await fetchWithRetry<ExtractionResult>(
  "https://api.docmap.io/v1/extractions/run",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      templateId: "tmpl_abc123",
      fileName: "invoice.pdf",
      pdfBase64: encodedPdf,
      mimeType: "application/pdf",
    }),
  },
);

if (result.error) {
  console.error(`Extraction failed: [${result.error.code}] ${result.error.message}`);
} else {
  console.log("Extracted data:", result.data);
}

Best Practices

  • Always check the HTTP status code first. A 2xx status means success; anything else is an error. Do not assume the response body has a data field without checking.

  • Parse the error body for the code field. Use code (not message) in your control flow. Error messages may change over time, but error codes are stable.

  • Handle specific error codes. Branch on known codes like USAGE_LIMIT_EXCEEDED or KEY_EXPIRED to show targeted messages to your users or trigger specific workflows.

  • Log unknown errors. If you encounter an error code you do not handle explicitly, log the full response (status code, error code, and message) for debugging.

  • Do not retry 4xx errors. Authentication, authorization, validation, and not-found errors will not resolve on their own. Fix the underlying issue before retrying.

  • Set a retry limit. Never retry indefinitely. Three retries with exponential backoff (1s, 2s, 4s) is a reasonable default for server errors.

DocMap API Documentation