Skip to main content

Error response format

All errors return a consistent JSON envelope. Check the success field first, then inspect error for details.
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "statusCode": 422,
    "requestId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "errors": [
      {
        "field": "email",
        "message": "Invalid email",
        "code": "invalid_string"
      }
    ],
    "suggestion": "One or more fields have incorrect types."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}
FieldTypeDescription
successbooleanAlways false for error responses
error.codestringMachine-readable error code (see reference below)
error.messagestringHuman-readable description
error.statusCodenumberHTTP status code
error.requestIdstringUnique request identifier — provide this when contacting support
error.errorsarrayField-level validation details (present on VALIDATION_ERROR only)
error.detailsobjectAdditional context, varies by error type
error.suggestionstringGuidance for resolving the error
timestampstringISO 8601 timestamp of when the error occurred
Always check error.suggestion first — it usually tells you exactly what to fix. Include error.requestId in any support request.

HTTP status codes

StatusMeaning
400 Bad RequestMalformed request or invalid parameters
401 UnauthorizedMissing or invalid API key
403 ForbiddenProject disabled or wrong key type for this endpoint
404 Not FoundThe requested resource does not exist
422 Unprocessable EntityValidation failed — check the errors array for field-level details
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorUnexpected server error — contact support with your requestId

Error codes reference

Authentication and authorization

CodeStatusDescription
MISSING_AUTH401Authorization header is absent or not in Bearer <key> format
UNAUTHORIZED401General authentication failure
INVALID_CREDENTIALS401Login credentials are incorrect
INVALID_API_KEY401API key is invalid, not found, or the wrong type for this endpoint
FORBIDDEN403Access denied
PROJECT_ACCESS_DENIED403The key does not have access to the requested project
PROJECT_DISABLED403The project has been disabled — write operations are blocked

Validation and input errors

CodeStatusDescription
BAD_REQUEST400General request error
INVALID_REQUEST_BODY400Request body is malformed or not valid JSON
VALIDATION_ERROR422One or more fields failed validation — check the errors array
MISSING_REQUIRED_FIELD422A required field is absent
INVALID_EMAIL422Email address format is invalid

Resource errors

CodeStatusDescription
RESOURCE_NOT_FOUND404Generic resource not found
CONTACT_NOT_FOUND404Contact does not exist in this project
TEMPLATE_NOT_FOUND404Template does not exist in this project
CAMPAIGN_NOT_FOUND404Campaign does not exist in this project
WORKFLOW_NOT_FOUND404Workflow does not exist in this project
CONFLICT409Resource conflict, such as a duplicate

Rate limiting and billing

CodeStatusDescription
RATE_LIMIT_EXCEEDED429Request rate limit exceeded
BILLING_LIMIT_EXCEEDED402Usage limit for your billing plan has been reached
UPGRADE_REQUIRED402This feature requires a plan upgrade

Server errors

CodeStatusDescription
INTERNAL_SERVER_ERROR500Unexpected server error
DATABASE_ERROR500Database operation failed
EXTERNAL_SERVICE_ERROR500A dependent external service (such as AWS SES) is unavailable

Common error examples

Validation error

Returned when one or more request fields fail schema validation. Inspect the errors array for field-level details.
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "statusCode": 422,
    "requestId": "abc-123",
    "errors": [
      {
        "field": "event",
        "message": "Required",
        "code": "invalid_type"
      },
      {
        "field": "email",
        "message": "Invalid email",
        "code": "invalid_string"
      }
    ],
    "suggestion": "One or more fields have incorrect types. Check that strings are quoted, numbers are unquoted, and booleans are true/false."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}

Resource not found

{
  "success": false,
  "error": {
    "code": "TEMPLATE_NOT_FOUND",
    "message": "Template with ID \"tpl_abc123\" was not found",
    "statusCode": 404,
    "requestId": "abc-123",
    "details": {
      "resource": "Template",
      "id": "tpl_abc123"
    },
    "suggestion": "Ensure the template ID is correct and belongs to your project. You can list available templates via the API."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}

Rate limit exceeded

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please try again later.",
    "statusCode": 429,
    "requestId": "abc-123",
    "suggestion": "You have exceeded the rate limit. Wait a moment before retrying, or upgrade your plan."
  },
  "timestamp": "2025-11-30T10:30:00.000Z"
}

Handling errors in code

const response = await fetch('https://next-api.useplunk.com/v1/track', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${publicKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ event: 'purchase', email: '[email protected]' }),
});

const data = await response.json();

if (!data.success) {
  const { code, message, suggestion, requestId, errors } = data.error;

  console.error(`[${code}] ${message}`);
  if (suggestion) console.log('Suggestion:', suggestion);
  console.log('Request ID:', requestId);

  if (code === 'VALIDATION_ERROR') {
    errors?.forEach(err => console.log(`  ${err.field}: ${err.message}`));
  }

  if (code === 'RATE_LIMIT_EXCEEDED') {
    // Implement exponential backoff before retrying
  }
}

Debugging with request IDs

Every request — successful or not — gets a unique requestId. It appears in:
  • The error.requestId field of error responses
  • The X-Request-ID response header (present on all responses)
Log the request ID in your application so you can correlate your own logs with Plunk’s logs when investigating issues.
const response = await fetch('https://next-api.useplunk.com/v1/send', { /* ... */ });

const requestId = response.headers.get('X-Request-ID');
console.log('Request ID:', requestId);

const data = await response.json();
if (!data.success) {
  console.error('Error:', data.error.message, '| Request ID:', data.error.requestId);
}
When contacting support, include the requestId. This lets the Plunk team trace your request through the full system — API, database, queue, and worker — without needing additional information from you.

Troubleshooting by status code

401 Unauthorized

  • Verify the API key is copied correctly with no leading or trailing spaces.
  • Check you are using the right key type: sk_ for all endpoints, pk_ only for /v1/track.
  • Ensure the Authorization header uses the exact format Bearer YOUR_API_KEY.
  • Confirm the key has not been deleted or regenerated in your project settings.

422 Unprocessable Entity

  • Read the errors array — each entry names the failing field and describes the problem.
  • Confirm all required fields are present in the request body.
  • Check field types: strings must be quoted, numbers must be unquoted, booleans must be true or false.

404 Not Found

  • Confirm the resource ID is correct.
  • Verify the resource belongs to the project associated with your API key.
  • Check the resource has not been deleted.

429 Too Many Requests

  • Implement exponential backoff: wait 1 s, then 2 s, then 4 s before retrying.
  • Reduce request frequency or batch operations.
  • Plunk queues bulk operations automatically — avoid sending them synchronously.

500 Internal Server Error

  • Note the requestId from the error response.
  • Wait a moment and retry — most 500 errors are transient.
  • If the error persists, contact support and include the requestId.

Build docs developers (and LLMs) love