API Reference
Error Codes
Every error the SchemaCheck API can return, its HTTP status, and how to fix it. All errors follow the same shape so you can handle them with a single catch block.
Error response shape
All error responses include an error object. The code field is machine-readable and stable — safe to switch on in your code.
{
"error": {
"code": "invalid_api_key", // stable machine-readable string
"message": "Human-readable explanation."
}
}Quick reference
| Code | HTTP | Meaning |
|---|---|---|
| missing_api_key | 401 | Missing API Key |
| invalid_api_key | 401 | Invalid API Key |
| inactive_api_key | 401 | Inactive API Key |
| quota_exceeded | 429 | Monthly Quota Exceeded |
| rate_limit_exceeded | 429 | Rate Limit Exceeded |
| missing_input | 400 | Missing Input |
| invalid_url | 400 | Invalid URL |
| invalid_jsonld | 400 | Invalid JSON-LD |
| parse_error | 422 | JSON-LD Parse Error |
| fetch_failed | 422 | URL Fetch Failed |
| fetch_timeout | 422 | Fetch Timeout |
| internal_error | 500 | Internal Server Error |
Error details
missing_api_keyHTTP 401When
Request has no x-api-key header and no access_key query parameter.
Fix
Add your API key as a header (x-api-key: sc_live_…) or query param (?access_key=sc_live_…).
{
"error": {
"code": "missing_api_key",
"message": "API key is required. Pass it as x-api-key header or access_key query param."
}
}invalid_api_keyHTTP 401When
The key was provided but doesn't exist in the database or is malformed.
Fix
Double-check the key value. Keys follow the pattern sc_live_[32 hex chars]. Get a new key at /docs/getting-started.
{
"error": {
"code": "invalid_api_key",
"message": "Invalid or inactive API key."
}
}inactive_api_keyHTTP 401When
The key exists but the account has been deactivated.
Fix
Contact support if you believe this is an error. You can sign up for a new free key at /docs/getting-started.
{
"error": {
"code": "inactive_api_key",
"message": "Invalid or inactive API key."
}
}quota_exceededHTTP 429When
Your account has used all included validations for the current billing period. Free plan accounts are blocked at the limit.
Fix
Upgrade to a paid plan or wait until your quota resets at the next billing cycle. Cached responses don't count against your quota.
{
"error": {
"code": "quota_exceeded",
"message": "You've used all 100 free validations this month. Upgrade to continue."
}
}rate_limit_exceededHTTP 429When
Too many requests in a short window. Limits are per-plan: free 10/min, basic 30/min, growth 60/min, scale 120/min.
Fix
Slow your request rate or implement exponential backoff. The Retry-After header tells you how many seconds to wait.
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Your free plan allows 10 requests per minute. Retry after 2026-03-18T10:31:00.000Z."
}
}missing_inputHTTP 400When
POST request body contains neither a url field nor a jsonld field, or both are empty.
Fix
Include either url (string, valid URL) or jsonld (object) in the request body — not both, not neither.
{
"error": {
"code": "missing_input",
"message": "Provide either 'url' or 'jsonld' in the request body."
}
}invalid_urlHTTP 400When
The url parameter is present but not a valid URL (e.g. missing scheme, localhost, IP address).
Fix
Pass a fully qualified URL including scheme: https://example.com. Private or localhost URLs are not supported.
{
"error": {
"code": "invalid_url",
"message": "URL must use http:// or https://."
}
}invalid_jsonldHTTP 400When
The jsonld body field was provided but is not a valid JSON-LD object or array.
Fix
Ensure the jsonld field is a plain JSON object (or array of objects) with an @type field. Strings and numbers are not accepted.
{
"error": {
"code": "invalid_jsonld",
"message": "The 'jsonld' field must be a JSON object or array."
}
}parse_errorHTTP 422When
A JSON-LD block was found but could not be parsed as valid JSON, or the @type field is missing or unrecognised.
Fix
Run the raw JSON through a JSON linter to find the syntax error. Ensure every schema block has a valid @type.
{
"error": {
"code": "parse_error",
"message": "Failed to parse JSON-LD block: Unexpected token } at position 142."
}
}fetch_failedHTTP 422When
SchemaCheck attempted to fetch the URL but received a non-2xx HTTP response (e.g. 404, 403, 500) or a network error.
Fix
Verify the URL is publicly accessible. If the page requires authentication or blocks bots, use the jsonld field with the raw markup instead.
{
"error": {
"code": "fetch_failed",
"message": "Failed to fetch https://example.com: server responded with HTTP 403."
}
}fetch_timeoutHTTP 422When
The target URL did not respond within 25 seconds.
Fix
Check that the URL is reachable and not unusually slow. If the page is slow to load, extract the JSON-LD yourself and send it via the jsonld field instead.
{
"error": {
"code": "fetch_timeout",
"message": "Validation timed out after 25 seconds."
}
}internal_errorHTTP 500When
An unexpected error occurred on SchemaCheck's servers. This is not caused by your request.
Fix
Retry after a short delay. If the error persists, contact support.
{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred during validation."
}
}Retry logic
Use exponential backoff for 5xx errors and respect the Retry-After header on rate_limit_exceeded. Never retry 4xx errors (except rate limits) — they indicate a problem with the request itself.
async function validateWithRetry(url: string, apiKey: string, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const res = await fetch(
`https://schemacheck.dev/api/v1/validate?url=${encodeURIComponent(url)}&access_key=${apiKey}`
);
const data = await res.json();
if (res.ok) return data;
const { code } = data.error ?? {};
// Don't retry client errors
if (res.status >= 400 && res.status < 500 && code !== "rate_limit_exceeded") {
throw new Error(`[SchemaCheck] ${code}: ${data.error.message}`);
}
// For rate limits, respect Retry-After header
if (code === "rate_limit_exceeded") {
const retryAfter = Number(res.headers.get("Retry-After") ?? 60);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
// Exponential backoff for 5xx
if (attempt < maxRetries) {
await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
}
}
throw new Error("Max retries exceeded");
}Tips
Switch on code, not message
The code field is stable across API versions. The message field is for humans and may change.
Credits are never charged on errors
4xx and 5xx errors never consume a validation credit. Only successful non-cached validations count.
Use jsonld to bypass fetch issues
If a URL is behind a firewall, returns 403 to bots, or is slow — extract the JSON-LD yourself and POST it in the jsonld field.
Cached responses never error
If a URL was validated in the last hour and is cached, the cached response is returned immediately without fetching the page.