Every Lokalise API error returns a JSON body with a consistent structure. This skill covers the error response format, diagnosis of each HTTP status code (401, 400, 404, 429, 413, 500, 503), diagnostic curl commands for rapid troubleshooting, and a reusable error handling wrapper for the Node SDK.
curl available for diagnostic commands@lokalise/node-api SDK installed for the error wrapperLOKALISE_API_TOKEN environment variablejq installed for parsing JSON responses (optional but recommended)All Lokalise API errors return this structure:
{
"error": {
"message": "Human-readable error description",
"code": 401
}
}
The code field mirrors the HTTP status code. The message field provides specifics. When using the Node SDK, errors are thrown as exceptions with error.code, error.message, and error.headers properties.
{"error": {"message": "Invalid `X-Api-Token` header", "code": 401}}
Causes:
X-Api-Token header missing from requestFix:
# Verify your token works
curl -s -o /dev/null -w "%{http_code}" \
-H "X-Api-Token: ${LOKALISE_API_TOKEN}" \
"https://api.lokalise.com/api2/teams"
# Expected: 200
# If 401: regenerate token at https://app.lokalise.com/profile#apitokens
# Check for whitespace in token
echo -n "$LOKALISE_API_TOKEN" | xxd | head -2
# Look for 0a (newline) or 20 (space) at start/end
{"error": {"message": "Invalid parameter `platform` - must be one of: ios, android, web, other", "code": 400}}
Common 400 causes:
project_id format (must be {number}.{alphanumeric})ios, android, web, or other)Fix:
# Validate project ID format
curl -s -H "X-Api-Token: ${LOKALISE_API_TOKEN}" \
"https://api.lokalise.com/api2/projects/${PROJECT_ID}" | jq '.project_id'
# List valid languages for a project
curl -s -H "X-Api-Token: ${LOKALISE_API_TOKEN}" \
"https://api.lokalise.com/api2/projects/${PROJECT_ID}/languages" \
| jq '.languages[].lang_iso'
{"error": {"message": "Project not found", "code": 404}}
Causes:
Fix:
# List all accessible projects to find the correct ID
curl -s -H "X-Api-Token: ${LOKALISE_API_TOKEN}" \
"https://api.lokalise.com/api2/projects?limit=100" \
| jq '.projects[] | {project_id, name}'
# Verify a specific key exists
curl -s -H "X-Api-Token: ${LOKALISE_API_TOKEN}" \
"https://api.lokalise.com/api2/projects/${PROJECT_ID}/keys/${KEY_ID}" \
| jq '.key_id // .error'
{"error": {"message": "Too many requests", "code": 429}}
The response includes a Retry-After header indicating seconds to wait.
Fix: See lokalise-rate-limits skill for full implementation. Quick recovery:
# Check current rate limit status on any request
curl -s -D - -o /dev/null \
-H "X-Api-Token: ${LOKALISE_API_TOKEN}" \
"https://api.lokalise.com/api2/projects" 2>&1 \
| grep -i "x-ratelimit\|retry-after"
# Output:
# X-RateLimit-Limit: 6
# X-RateLimit-Remaining: 5
# X-RateLimit-Reset: 1700000001
{"error": {"message": "Request entity too large", "code": 413}}
Causes:
Fix:
{"error": {"message": "Internal server error", "code": 500}}
These are Lokalise-side issues. Do not retry immediately in a tight loop.
Fix:
# Check Lokalise status page
curl -s "https://status.lokalise.com/api/v2/status.json" | jq '.status'
# If status is operational, retry after 30 seconds
# If status shows incident, wait for resolution
Retry strategy for 500/503: wait 30 seconds, retry up to 3 times, then alert.
Quick health check script to diagnose the most common issues in sequence:
#!/bin/bash
# lokalise-diagnose.sh — Run against your environment
TOKEN="${LOKALISE_API_TOKEN}"
PROJECT="${LOKALISE_PROJECT_ID}"
echo "=== 1. Token validation ==="
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "X-Api-Token: $TOKEN" \
"https://api.lokalise.com/api2/teams")
if [ "$STATUS" = "200" ]; then
echo "Token: VALID"
else
echo "Token: INVALID (HTTP $STATUS)"
exit 1
fi
echo "=== 2. Project access ==="
curl -s -H "X-Api-Token: $TOKEN" \
"https://api.lokalise.com/api2/projects/$PROJECT" \
| jq '{project_id: .project_id, name: .name, team_id: .team_id}'
echo "=== 3. Rate limit status ==="
curl -s -D /dev/stderr -o /dev/null \
-H "X-Api-Token: $TOKEN" \
"https://api.lokalise.com/api2/projects" 2>&1 \
| grep -i "x-ratelimit"
echo "=== 4. Key count ==="
curl -s -H "X-Api-Token: $TOKEN" \
"https://api.lokalise.com/api2/projects/$PROJECT/keys?limit=1" \
| jq '.project_id as $p | {project: $p, total_keys: .keys | length}'
Wrap all SDK calls with structured error handling:
import { LokaliseApi } from "@lokalise/node-api";
interface LokaliseError {
code: number;
message: string;
headers?: Record<string, string>;
}
function isLokaliseError(error: unknown): error is LokaliseError {
return (
typeof error === "object" &&
error !== null &&
"code" in error &&
"message" in error
);
}
async function lokaliseCall<T>(
fn: () => Promise<T>,
context: string
): Promise<T> {
try {
return await fn();
} catch (error) {
if (!isLokaliseError(error)) throw error;
switch (error.code) {
case 401:
throw new Error(
`[${context}] Authentication failed. ` +
`Regenerate token at https://app.lokalise.com/profile#apitokens`
);
case 400:
throw new Error(
`[${context}] Invalid request: ${error.message}. ` +
`Check parameter values and required fields.`
);
case 404:
throw new Error(
`[${context}] Resource not found: ${error.message}. ` +
`Verify the project/key/resource ID exists and token has access.`
);
case 429:
console.warn(`[${context}] Rate limited. See lokalise-rate-limits.`);
throw error; // Let the rate limit handler deal with retries
case 413:
throw new Error(
`[${context}] Payload too large: ${error.message}. ` +
`Split into smaller batches (max 500 items per request).`
);
case 500:
case 503:
throw new Error(
`[${context}] Lokalise server error (${error.code}). ` +
`Check https://status.lokalise.com — retry after 30s.`
);
default:
throw new Error(
`[${context}] Lokalise error ${error.code}: ${error.message}`
);
}
}
}
// Usage
const lokalise = new LokaliseApi({ apiKey: process.env.LOKALISE_API_TOKEN! });
const keys = await lokaliseCall(
() => lokalise.keys().list({ project_id: projectId, limit: 500 }),
"listKeys"
);
| Code | Error | Root Cause | Resolution |
|---|---|---|---|
| 401 | Invalid API Token | Token wrong, expired, or whitespace | Regenerate at Lokalise profile |
| 400 | Bad Request | Invalid params, missing fields | Check API docs for required fields |
| 404 | Not Found | Wrong ID or no access | List resources to find correct ID |
| 429 | Rate Limited | Exceeded 6 req/sec | Honor Retry-After, use queue |
| 413 | Payload Too Large | Body > 50 MB or > 500 items | Split into batches |
| 500 | Internal Server Error | Lokalise-side failure | Check status page, retry after 30s |
| 503 | Service Unavailable | Lokalise maintenance/outage | Check status page, wait |
curl -s -H "X-Api-Token: $LOKALISE_API_TOKEN" \
"https://api.lokalise.com/api2/teams" | jq '.teams[0].name // "INVALID TOKEN"'
try {
await lokalise.keys().list({ project_id: "invalid" });
} catch (e: any) {
console.log("Code:", e.code); // 400
console.log("Message:", e.message); // "Invalid project ID format"
console.log("Headers:", e.headers); // rate limit headers
}
# Verify CLI token
lokalise2 project list --token "$LOKALISE_API_TOKEN" --format json | jq '.[0].name'
# Test with verbose output
lokalise2 --debug file download \
--token "$LOKALISE_API_TOKEN" \
--project-id "$PROJECT_ID" \
--format json \
--dest ./locales/
For building resilient integrations that handle errors automatically, see lokalise-rate-limits. For debugging translation data issues, see lokalise-data-handling.