Seal Docs

Error Handling

Understand API error responses and implement robust error handling

Error Handling

The Seal API uses standard HTTP status codes and returns detailed error responses following RFC 7807 (Problem Details for HTTP APIs) to help you diagnose and handle errors effectively.

Error Response Format

All error responses follow this consistent structure:

{
  "type": "https://seal.nyc/errors/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "Invalid email format for recipient",
  "instance": "/api/v1/documents/abc123/recipients"
}

Response Fields

FieldTypeDescription
typestringURI reference identifying the error type
titlestringShort, human-readable summary of the error
statusnumberHTTP status code
detailstringDetailed explanation of the specific error
instancestringURI reference to the specific request that caused the error

Common Error Codes

400 Bad Request

The request was malformed or contains invalid parameters.

Common Causes:

  • Missing required fields in request body
  • Invalid JSON syntax
  • Malformed data (e.g., invalid email format, invalid date)
  • Invalid enum values (e.g., unknown recipient role)

Example:

{
  "type": "https://seal.nyc/errors/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "Field 'email' is required for recipient",
  "instance": "/api/v1/documents/abc123/recipients"
}

401 Unauthorized

Authentication failed or credentials are missing.

Common Causes:

  • Missing Authorization header
  • Invalid API key format
  • Expired API key
  • Malformed Bearer token

Example:

{
  "type": "https://seal.nyc/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid or expired API key",
  "instance": "/api/v1/documents"
}

403 Forbidden

Authentication succeeded but you don't have permission to perform this action.

Common Causes:

  • API key lacks required scope (e.g., trying to write with read-only scope)
  • User is not a member of the organization
  • User's role doesn't have the required permission
  • Resource belongs to a different organization

Example:

{
  "type": "https://seal.nyc/errors/insufficient-scope",
  "title": "Forbidden",
  "status": 403,
  "detail": "Missing required scope: seal:documents:write",
  "instance": "/api/v1/documents"
}

404 Not Found

The requested resource doesn't exist.

Common Causes:

  • Invalid document/recipient/template ID
  • Resource was deleted
  • Typo in the endpoint URL
  • Resource belongs to a different organization

Example:

{
  "type": "https://seal.nyc/errors/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "Document not found: abc123",
  "instance": "/api/v1/documents/abc123"
}

429 Too Many Requests

You've exceeded the rate limit for API requests.

Common Causes:

  • Too many requests in a short time window
  • Burst traffic exceeding rate limits

Example:

{
  "type": "https://seal.nyc/errors/rate-limit",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit exceeded. Retry after 60 seconds.",
  "instance": "/api/v1/documents"
}

Retry Strategy:

  • Wait before retrying (use exponential backoff)
  • Check Retry-After header if present
  • Implement request queuing to stay within limits

500 Internal Server Error

An unexpected error occurred on the server.

Common Causes:

  • Temporary server issues
  • Database connectivity problems
  • Unexpected internal state

Example:

{
  "type": "https://seal.nyc/errors/internal-error",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "An unexpected error occurred. Please try again later.",
  "instance": "/api/v1/documents/abc123/send"
}

Retry Strategy:

  • Retry with exponential backoff
  • Maximum 3 retry attempts
  • Log the error for investigation

Error Handling Best Practices

TypeScript Example

interface ApiError {
  type: string;
  title: string;
  status: number;
  detail: string;
  instance: string;
}

async function sendDocument(documentId: string, message: string) {
  try {
    const response = await fetch(`https://seal.convex.site/api/v1/documents/${documentId}/send`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.SEAL_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ message }),
    });

    if (!response.ok) {
      const error: ApiError = await response.json();

      // Handle specific error types
      switch (error.status) {
        case 400:
          console.error("Invalid request:", error.detail);
          throw new Error(`Validation failed: ${error.detail}`);

        case 401:
          console.error("Authentication failed:", error.detail);
          // Refresh API key or re-authenticate
          throw new Error("Authentication required");

        case 403:
          console.error("Permission denied:", error.detail);
          throw new Error(`Insufficient permissions: ${error.detail}`);

        case 404:
          console.error("Document not found:", error.detail);
          throw new Error("Document does not exist");

        case 429:
          console.error("Rate limit exceeded. Retrying after delay...");
          // Implement exponential backoff
          await new Promise((resolve) => setTimeout(resolve, 60000));
          return sendDocument(documentId, message); // Retry

        case 500:
          console.error("Server error:", error.detail);
          // Retry with exponential backoff
          throw new Error("Server error. Please try again later.");

        default:
          console.error("Unexpected error:", error);
          throw new Error(error.detail);
      }
    }

    return await response.json();
  } catch (error) {
    // Handle network errors
    if (error instanceof TypeError && error.message.includes("fetch")) {
      console.error("Network error:", error);
      throw new Error("Network connection failed");
    }

    throw error;
  }
}

Exponential Backoff Implementation

async function retryWithBackoff<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000,
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const isLastAttempt = attempt === maxRetries - 1;
      const shouldRetry =
        error instanceof Error && (error.message.includes("500") || error.message.includes("429"));

      if (isLastAttempt || !shouldRetry) {
        throw error;
      }

      // Exponential backoff: 1s, 2s, 4s, 8s...
      const delay = baseDelay * Math.pow(2, attempt);
      console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }

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

// Usage
const result = await retryWithBackoff(() => sendDocument("doc123", "Please sign"));

Validation Errors

For 400 errors, the detail field often contains specific validation information:

{
  "type": "https://seal.nyc/errors/validation-error",
  "title": "Validation Error",
  "status": 400,
  "detail": "Validation failed: email must be a valid email address, role must be one of: signer, approver, viewer",
  "instance": "/api/v1/documents/abc123/recipients"
}

Tip: Always log the full error response (including type and instance) to help with debugging. The instance field shows exactly which request failed.

Best Practices

  1. Always Check Response Status: Don't assume requests succeed. Check response.ok or status code.

  2. Parse Error Responses: Extract the detail field for user-friendly error messages.

  3. Implement Retry Logic: Use exponential backoff for 429 and 500 errors.

  4. Log Errors: Include type, status, detail, and instance in logs for debugging.

  5. Handle Network Errors: Catch network failures separately from API errors.

  6. User-Friendly Messages: Transform technical error details into actionable messages for end users.

  7. Monitor Error Rates: Track error responses to identify integration issues early.

Next Steps

  • API Reference - Explore all endpoints and their specific error conditions
  • Webhooks - Set up error notifications via webhooks
  • Authentication - Review authentication requirements and scope errors

Last updated on

On this page