NodukanNodukan/ Developers
Introduction

Errors

Complete reference of every Nodukan API error status code, error type, machine-readable code, and what to do about each one.

Errors

The Nodukan API uses standard HTTP status codes. When a request fails, the response body always contains an error object with a machine-readable type, a more specific code, and a human-readable message.

Error envelope#

{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This key does not have the 'lead_magnets:write' scope.",
    "requiredScopes": ["lead_magnets:write"],
    "grantedScopes": ["lead_magnets:read"]
  },
  "requestId": "req_01j9xabc..."
}

The requestId is a unique trace ID for the request. Include it when contacting support โ€” it lets us look up the exact request in our logs.

Additional context fields appear only when relevant (e.g., requiredScopes only appears on scope errors; retryAfter only on rate limit errors).

Full error code reference#

4xx Client errors#

StatustypecodeWhen it happensWhat to do
400validation_errorinvalid_bodyRequest body failed schema validationRead error.details for the list of invalid fields
400validation_errorinvalid_query_paramA query parameter has an invalid valueCheck the parameter type and allowed values
401authentication_errormissing_credentialsNo Authorization header was sentAdd Authorization: Bearer NDK_...
401authentication_errorinvalid_tokenToken is malformed (wrong format, not parsable)Ensure the full NDK_<env>_<kid>.<secret> token is sent
401authentication_errorinvalid_or_revoked_keyKey ID not found, or the key has been revokedGenerate a new key in the dashboard
401authentication_errorinvalid_signatureThe secret portion of the token didn't matchCheck for truncation โ€” copy the full token
401authentication_errorkey_expiredThe key has passed its expiry dateGenerate a new key
403authentication_errorip_not_allowedThe request's source IP is not in the key's IP allowlistAdd your IP in Dashboard โ†’ API Keys โ†’ IP Restrictions
403authentication_erroraccount_suspendedThe developer account has been suspendedContact [email protected]
403permission_errorinsufficient_scopeThe key lacks the required scopeRegenerate the key with the necessary scope
404not_foundresource_not_foundThe requested resource doesn't existVerify the ID in the path segment
409conflictalready_existsAttempting to create a resource that already exists (e.g., duplicate short URL slug)Use a different identifier or PUT to update
422validation_errorunprocessable_entityRequest was parseable but semantically invalidRead error.details for field-level issues

4xx Rate limit errors#

StatustypecodeWhen it happensWhat to do
429rate_limit_errortoo_many_requestsRequests per 60-second window exceededWait Retry-After seconds, then retry with exponential backoff

5xx Server errors#

StatustypecodeWhen it happensWhat to do
500internal_errorserver_errorAn unexpected error occurred on our sideRetry the request; check status.nodukan.com
501not_implementednot_implementedThis endpoint isn't available for your account typeCheck your plan; contact support if you believe it should be available
503internal_errorservice_unavailableDownstream service temporarily unavailableRetry with exponential backoff; check status page

Validation errors in detail#

400 invalid_body responses include a details array pointing to the exact field(s) that failed:

{
  "error": {
    "type": "validation_error",
    "code": "invalid_body",
    "message": "Request body validation failed.",
    "details": [
      {
        "field": "scheduledAt",
        "message": "Must be a valid ISO 8601 datetime in the future",
        "received": "2024-01-01T00:00:00Z"
      },
      {
        "field": "platform",
        "message": "Must be one of: linkedin, instagram, facebook",
        "received": "twitter"
      }
    ]
  },
  "requestId": "req_01j9xabc..."
}

Not found errors#

404 resource_not_found includes the resource type and ID that was looked up:

{
  "error": {
    "type": "not_found",
    "code": "resource_not_found",
    "message": "No lead magnet with ID 'lm_notexist' was found for your account.",
    "resourceType": "lead_magnet",
    "resourceId": "lm_notexist"
  },
  "requestId": "req_01j9xabc..."
}

Conflict errors#

409 already_exists occurs when uniqueness constraints are violated, such as a short URL slug that's already taken:

{
  "error": {
    "type": "conflict",
    "code": "already_exists",
    "message": "A short URL with slug 'notion-template' already exists.",
    "field": "slug",
    "conflictingId": "url_01j9x3km"
  },
  "requestId": "req_01j9xabc..."
}

Handling errors in code#

JavaScript / TypeScript#

const res = await fetch('https://api.nodukan.com/v1/lead-magnets', {
  headers: { Authorization: `Bearer ${process.env.NODUKAN_API_KEY}` },
});

if (!res.ok) {
  const body = await res.json();
  const { type, code, message } = body.error;

  switch (code) {
    case 'missing_credentials':
    case 'invalid_token':
    case 'invalid_or_revoked_key':
      throw new Error(`Auth error: ${message}. Regenerate your API key.`);

    case 'insufficient_scope':
      throw new Error(`Scope error: ${message}`);

    case 'too_many_requests':
      // handle in retry loop
      break;

    case 'server_error':
      // log and retry
      console.error(`Server error (requestId: ${body.requestId})`);
      break;

    default:
      throw new Error(`API error [${type}/${code}]: ${message}`);
  }
}

const { data } = await res.json();

Python#

import requests

resp = requests.get(
    "https://api.nodukan.com/v1/lead-magnets",
    headers={"Authorization": f"Bearer {api_key}"},
)

if not resp.ok:
    err = resp.json().get("error", {})
    code = err.get("code")
    message = err.get("message")

    if code in ("missing_credentials", "invalid_token", "invalid_or_revoked_key"):
        raise ValueError(f"Auth error: {message}")
    elif code == "insufficient_scope":
        raise PermissionError(f"Scope error: {message}")
    elif code == "too_many_requests":
        pass  # handle in retry loop
    else:
        raise RuntimeError(f"API error [{code}]: {message}")

data = resp.json()["data"]
Note:

Always log the requestId from error responses. When filing a bug report or contacting support, include the requestId and we can trace the exact request within seconds.

Was this helpful?
Edit on GitHub

Last updated