Skip to main content

Error Handling

LinkForty API uses conventional HTTP status codes and returns detailed error information in JSON format.

Error Response Format

All errors follow this structure:

{
"message": "Human-readable description",
"error": "STABLE_CODE",
"errors": [
{ "path": "field", "message": "What is wrong with this field" }
]
}
FieldTypeDescription
messagestringHuman-readable error message, safe to show to an end user
errorstringStable, machine-readable code such as LINK_NOT_FOUND. Branch on this, not on message
errorsarrayPresent on validation failures only: one entry per field, with its path and reason

The HTTP status code is carried by the response itself, not repeated in the body.

Stable error codes

Codes never change once published. Messages may be reworded.

CodeStatusMeaning
VALIDATION_ERROR400One or more fields failed validation; see errors
INVALID_JSON_BODY400The request body is not valid JSON
NO_UPDATES400An update request contained no fields to change
INVALID_BULK_PAYLOAD400A bulk endpoint received a missing or empty array
BULK_LIMIT_EXCEEDED400A bulk endpoint received more than 100 items
DOMAIN_NOT_VERIFIED400The domainId is not one of your verified custom domains
CURRENT_PASSWORD_INCORRECT400Password change rejected because the current password did not match
AUTH_REQUIRED, UNAUTHORIZED401Missing or invalid credential
WORKSPACE_RESTRICTED403The workspace is restricted; writes are refused
LINK_NOT_FOUND, TEMPLATE_NOT_FOUND, PROJECT_NOT_FOUND, API_KEY_NOT_FOUND, USER_NOT_FOUND, ORGANIZATION_NOT_FOUND404The addressed resource does not exist in your workspace
EMAIL_TAKEN409The requested account email is already in use
TEMPLATE_HAS_LINKS409A template cannot be deleted while links still use it

HTTP Status Codes

2xx Success

CodeNameDescription
200OKRequest succeeded
201CreatedResource created successfully
204No ContentRequest succeeded, no response body

4xx Client Errors

CodeNameWhen It Happens
400Bad RequestInvalid request format or validation error
401UnauthorizedMissing or invalid API key
403ForbiddenInsufficient permissions
404Not FoundResource doesn't exist
409ConflictResource already exists (e.g., duplicate short code)
422Unprocessable EntityRequest valid but semantically incorrect
429Too Many RequestsRate limit exceeded

5xx Server Errors

CodeNameWhen It Happens
500Internal Server ErrorUnexpected server error
502Bad GatewayUpstream service unavailable
503Service UnavailableTemporary maintenance or overload
504Gateway TimeoutRequest timeout

Common Errors

400 Bad Request

Cause: Invalid request data or validation failure.

Example: Missing required field

{
"message": "Validation error",
"error": "VALIDATION_ERROR",
"errors": [
{ "path": "templateId", "message": "Either templateId or templateSlug is required" }
]
}

How to fix:

  • Check all required fields are present
  • Verify field formats (URLs, UUIDs, numbers)
  • Review API documentation for endpoint

401 Unauthorized

Cause: Missing or invalid API key.

Example: No Authorization header

curl https://api.linkforty.com/api/links
# No Authorization header provided

Response:

{
"error": "Unauthorized",
"message": "Missing or invalid API key",
"statusCode": 401
}

How to fix:

curl https://api.linkforty.com/api/links \
-H "Authorization: Bearer dl_a1b2c3d4e5f6a7b8"

Example: Invalid API key

{
"error": "Unauthorized",
"message": "Invalid API key",
"statusCode": 401
}

How to fix:

  • Verify API key is correct
  • Check key hasn't been deleted
  • Ensure the key belongs to the workspace you're accessing

403 Forbidden

Cause: Authenticated but lacking permissions.

Example: Accessing another organization's resource

{
"error": "Forbidden",
"message": "You do not have access to this resource",
"statusCode": 403
}

How to fix:

  • Verify you're in the correct organization
  • Check your role (Owner/Admin/Member/Viewer)
  • Ensure resource belongs to your organization

404 Not Found

Cause: Resource doesn't exist.

Example: Link not found

curl https://api.linkforty.com/api/links/nonexistent123 \
-H "Authorization: Bearer $LINKFORTY_API_KEY"

Response:

{
"message": "Link not found",
"error": "LINK_NOT_FOUND"
}

How to fix:

  • Verify resource ID is correct
  • Check resource hasn't been deleted
  • Ensure resource exists in your organization

409 Conflict

Cause: Resource already exists.

Example: Duplicate custom short code

curl -X POST https://api.linkforty.com/api/links \
-H "Authorization: Bearer $LINKFORTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "template_123",
"originalUrl": "https://example.com",
"customCode": "summer-sale"
}'

Response:

{
"error": "Conflict",
"message": "Short code 'summer-sale' is already in use",
"statusCode": 409
}

How to fix:

  • Use a different custom code
  • Let LinkForty generate a random code (omit customCode)
  • Check if link was already created

422 Unprocessable Entity

Cause: Request valid but can't be processed.

Example: Invalid URL format

{
"error": "Unprocessable Entity",
"message": "Invalid URL format",
"statusCode": 422,
"validation": {
"originalUrl": "Must be a valid URL starting with http:// or https://"
}
}

How to fix:

  • Verify URL format is correct
  • Ensure URL is reachable
  • Check for typos

429 Too Many Requests

Cause: Rate limit exceeded.

Response:

{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Try again in 42 seconds.",
"statusCode": 429,
"retryAfter": 42
}

Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1642531200
Retry-After: 42

How to fix:

  • Wait for retryAfter seconds
  • Implement exponential backoff
  • Use bulk endpoints
  • See Rate Limits for details

500 Internal Server Error

Cause: Unexpected server error.

Response:

{
"message": "Internal server error"
}

A 500 never carries detail. If you receive one, the fault is on our side; a 4xx always names what to change in the request.

How to fix:

  • Retry the request
  • If persistent, contact support
  • Check status page: status.linkforty.com

Validation Errors

Validation errors list every failing field in errors. The path uses dot notation for nested fields, such as utmParameters.source.

{
"message": "Validation error",
"error": "VALIDATION_ERROR",
"errors": [
{ "path": "webFallbackUrl", "message": "Invalid url" },
{ "path": "attributionWindowHours", "message": "Attribution window must be at most 2160 hours (90 days)" }
]
}

Common validation errors:

FieldErrorFix
originalUrl"Must be a valid URL"Include http:// or https://
templateId"Must be a valid UUID"Use proper template ID format
attributionWindowHours"Must be between 1 and 2160"Set value in allowed range
customCode"Already in use"Choose different code
expiresAt"Must be a future date"Set date in future

Error Handling Best Practices

1. Always Check Status Code

const response = await fetch('https://api.linkforty.com/api/links', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(linkData)
});

if (!response.ok) {
const error = await response.json();
console.error(`Error ${error.statusCode}: ${error.message}`);
throw new Error(error.message);
}

const link = await response.json();

2. Handle Specific Errors

try {
const response = await createLink(linkData);
return response;
} catch (error) {
if (error.statusCode === 409) {
// Duplicate short code - try with different code
return createLink({ ...linkData, customCode: generateRandomCode() });
} else if (error.statusCode === 429) {
// Rate limited - wait and retry
await sleep(error.retryAfter * 1000);
return createLink(linkData);
} else if (error.statusCode === 400) {
// Validation error - show to user
throw new ValidationError(error.validation);
} else if (error.statusCode >= 500) {
// Server error - retry with backoff
return retryWithBackoff(() => createLink(linkData));
} else {
// Other error - propagate
throw error;
}
}

3. Implement Retry Logic

async function retryWithBackoff(fn, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (attempt === maxRetries || error.statusCode < 500) {
throw error;
}

const delay = Math.min(1000 * Math.pow(2, attempt), 32000);
console.log(`Retry ${attempt}/${maxRetries} in ${delay}ms`);
await sleep(delay);
}
}
}

4. Log Errors Properly

async function createLink(data) {
try {
const response = await fetch('https://api.linkforty.com/api/links', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});

if (!response.ok) {
const error = await response.json();
logError({
endpoint: '/api/links',
method: 'POST',
statusCode: error.statusCode,
message: error.message,
requestData: data,
timestamp: new Date().toISOString()
});
throw error;
}

return await response.json();
} catch (error) {
throw error;
}
}

5. User-Friendly Error Messages

function getUserMessage(error) {
switch (error.statusCode) {
case 400:
return `Invalid input: ${Object.values(error.validation || {}).join(', ')}`;
case 401:
return 'Authentication failed. Please check your API key.';
case 403:
return 'You don\'t have permission to perform this action.';
case 404:
return 'The requested resource was not found.';
case 409:
return 'This short code is already in use. Please choose another.';
case 429:
return `Too many requests. Please wait ${error.retryAfter} seconds.`;
case 500:
case 502:
case 503:
case 504:
return 'Server error. Please try again later.';
default:
return 'An unexpected error occurred. Please try again.';
}
}

Debugging Errors

Check Request/Response

const response = await fetch('https://api.linkforty.com/api/links', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(linkData)
});

console.log('Status:', response.status);
console.log('Headers:', Object.fromEntries(response.headers));

const body = await response.text();
console.log('Body:', body);

Test with cURL

curl -v -X POST https://api.linkforty.com/api/links \
-H "Authorization: Bearer $LINKFORTY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "template_123",
"originalUrl": "https://example.com"
}'

Production Error Monitoring

1. Centralized Error Tracking

import * as Sentry from '@sentry/node';

Sentry.init({
dsn: 'your-sentry-dsn'
});

async function createLink(data) {
try {
return await client.links.create(data);
} catch (error) {
Sentry.captureException(error, {
tags: {
endpoint: 'create_link',
statusCode: error.statusCode
},
extra: {
requestData: data
}
});
throw error;
}
}

2. Error Metrics

const errorCounts = {
400: 0,
401: 0,
404: 0,
429: 0,
500: 0
};

function trackError(error) {
errorCounts[error.statusCode] = (errorCounts[error.statusCode] || 0) + 1;

// Send to monitoring service
metrics.increment('linkforty.api.error', {
statusCode: error.statusCode,
endpoint: error.endpoint
});
}

3. Alert on Error Spikes

if (errorCounts[500] > 10) {
alert('High rate of 500 errors from LinkForty API!');
}

if (errorCounts[429] > 5) {
alert('Hitting rate limits frequently - consider upgrading plan');
}

Troubleshooting Guide

Error: "Invalid API key"

Check:

  1. API key starts with dl_
  2. Key hasn't been deleted from dashboard
  3. Key belongs to the correct workspace

Fix:

  • Generate new API key from dashboard
  • Update environment variables

Error: "Template not found"

Check:

  1. Template ID is correct
  2. Template belongs to your organization
  3. Template hasn't been deleted

Fix:

  • List templates: GET /api/templates
  • Create new template if needed
  • Use correct template ID

Error: "Short code already in use"

Check:

  1. Custom code is globally unique
  2. Code hasn't been used by your org before

Fix:

  • Choose different custom code
  • Omit customCode to auto-generate
  • Check existing links: GET /api/links

Next Steps

Support

Having trouble with errors?