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#
| Status | type | code | When it happens | What to do |
|---|---|---|---|---|
400 | validation_error | invalid_body | Request body failed schema validation | Read error.details for the list of invalid fields |
400 | validation_error | invalid_query_param | A query parameter has an invalid value | Check the parameter type and allowed values |
401 | authentication_error | missing_credentials | No Authorization header was sent | Add Authorization: Bearer NDK_... |
401 | authentication_error | invalid_token | Token is malformed (wrong format, not parsable) | Ensure the full NDK_<env>_<kid>.<secret> token is sent |
401 | authentication_error | invalid_or_revoked_key | Key ID not found, or the key has been revoked | Generate a new key in the dashboard |
401 | authentication_error | invalid_signature | The secret portion of the token didn't match | Check for truncation โ copy the full token |
401 | authentication_error | key_expired | The key has passed its expiry date | Generate a new key |
403 | authentication_error | ip_not_allowed | The request's source IP is not in the key's IP allowlist | Add your IP in Dashboard โ API Keys โ IP Restrictions |
403 | authentication_error | account_suspended | The developer account has been suspended | Contact [email protected] |
403 | permission_error | insufficient_scope | The key lacks the required scope | Regenerate the key with the necessary scope |
404 | not_found | resource_not_found | The requested resource doesn't exist | Verify the ID in the path segment |
409 | conflict | already_exists | Attempting to create a resource that already exists (e.g., duplicate short URL slug) | Use a different identifier or PUT to update |
422 | validation_error | unprocessable_entity | Request was parseable but semantically invalid | Read error.details for field-level issues |
4xx Rate limit errors#
| Status | type | code | When it happens | What to do |
|---|---|---|---|---|
429 | rate_limit_error | too_many_requests | Requests per 60-second window exceeded | Wait Retry-After seconds, then retry with exponential backoff |
5xx Server errors#
| Status | type | code | When it happens | What to do |
|---|---|---|---|---|
500 | internal_error | server_error | An unexpected error occurred on our side | Retry the request; check status.nodukan.com |
501 | not_implemented | not_implemented | This endpoint isn't available for your account type | Check your plan; contact support if you believe it should be available |
503 | internal_error | service_unavailable | Downstream service temporarily unavailable | Retry 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"]
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.
Last updated