Errors
These are the status codes and error codes the Webmetic API returns, and how your code should react.
The API reports every error as JSON with a status code, a fixed error code, and a readable text.
Error response format
All public endpoints answer an error in the same format. One example is a request without an API key:
curl "https://hub.webmetic.de/company-sessions?domain=your-website.com"The API answers with status 403:
{
"detail": {
"error": "API_KEY_MISSING",
"message": "Send your API key in the Authorization header."
}
}detail.error: the error code in capitals. Check this field in your code.detail.message: an English text for people, for example for your log. Don't check this text in code, because it can change.
This format doesn't apply to the Data Layer, which answers with a shorter body.
Some errors contain more fields in detail:
| Field | Comes with | Meaning |
|---|---|---|
errors | VALIDATION_ERROR | List of the missing or invalid parameters and fields. Each entry gives the place in loc, the problem in msg, and the kind of error in type |
feature | PLAN_REQUIRED, FEATURE_DISABLED | The feature your plan doesn't include or that is turned off |
required_tier | PLAN_REQUIRED | The plan the feature needs |
current_tier | PLAN_REQUIRED | Your current plan |
required | INSUFFICIENT_CREDITS | How many credits the action needs |
available | INSUFFICIENT_CREDITS | How many credits your account has |
website | WEBSITE_NOT_AUTHORIZED, SEGMENT_WEBSITE | The website in question |
limit | WEBHOOK_LIMIT_REACHED | The maximum number of webhooks for your account |
The API always reports problems with the key as 403, never as 401. For RATE_LIMITED, CONCURRENT_UPDATE, PROVIDER_UNAVAILABLE, PROVIDER_ERROR, and INTERNAL_ERROR, a new attempt can work because the error is temporary. For all other errors, change the request, the account, or the plan first.
This format applies to the API endpoints. For a mistyped path outside them, the API answers {"detail": "Not Found"}. So check in your code whether detail is an object.
Common errors
Any endpoint can return these errors. The key codes apply to the Visitor Intelligence key, which you also use for /contacts/* and /webhooks. Enrichment has its own codes.
| Status | Code | Meaning | What you do |
|---|---|---|---|
| 403 | API_KEY_MISSING | The request has no Authorization header | Send the key in the Authorization header |
| 403 | API_KEY_INVALID | The key has the wrong format, for example with Bearer in front | Send the key unchanged, without Bearer, spaces, or quotes |
| 403 | API_KEY_NOT_FOUND | No account has this key, for example after generating a new one or with the Enrichment key | Copy the current key under Menu > Integrations > API |
| 404 | NOT_FOUND | The path doesn't exist | Check the address in the reference |
| 405 | METHOD_NOT_ALLOWED | The path doesn't accept this method, for example POST instead of GET | Use the method from the reference |
| 422 | VALIDATION_ERROR | A parameter or field is missing or invalid, for example page below 1 or page_size above 10,000 | Fix the places listed in errors. For details, see Pagination |
| 500 | INTERNAL_ERROR | An error at Webmetic, with no details in the answer | Try again later. If the error stays, write to support |
Visitor Intelligence
The eight visit endpoints, for example /company-sessions and /company, also return these errors.
| Status | Code | Meaning | What you do |
|---|---|---|---|
| 400 | INVALID_DATE | The API can't read from_date or to_date, for example -3 weeks or 31.02.2026 | Use, for example, 2026-10-01 or -7 days. For details, see Date ranges and time zones |
| 403 | API_KEY_NOT_AUTHORIZED | The domain doesn't belong to your account or is spelled differently | Enter the domain exactly as it appears under Menu > Websites |
| 429 | RATE_LIMITED | More than one request per second to the same endpoint | Wait the number of seconds in Retry-After, then send the request again. For details, see Rate limits |
On the Free plan, the API reports no error for hidden companies but returns the row with blurred: true and only a few fields. For details, see Plans.
Enrichment
The endpoints under /enrichment/* use the Enrichment key.
| Status | Code | Meaning | What you do |
|---|---|---|---|
| 400 | INVALID_DOMAIN | No domain can be read from the input to GET /enrichment/company | Enter a domain such as your-website.com |
| 403 | ENRICHMENT_KEY_MISSING | The request has no Authorization header | Send the Enrichment key in the Authorization header |
| 403 | ENRICHMENT_KEY_INVALID | The key has the wrong format | Send the key unchanged, without Bearer |
| 403 | ENRICHMENT_KEY_NOT_FOUND | No account has this Enrichment key, for example with the Visitor Intelligence key | Copy the current Enrichment key under Menu > Integrations > API |
| 403 | PLAN_REQUIRED | New lists, new domains, and single lookups need the Growth plan. Reading and exporting still work, except with ACCOUNT_NOT_ELIGIBLE | Switch to the plan in required_tier, see Plans |
| 403 | ACCOUNT_NOT_ELIGIBLE | Your account can't use Enrichment right now, for example after a cancellation | Write to support. A new attempt changes nothing |
| 404 | LIST_NOT_FOUND | The list doesn't exist or belongs to another account | Use the ID of a list from GET /enrichment/lists |
| 422 | VALIDATION_ERROR | For example domain under 3 characters, more than 5,000 domains per call, or an unknown value in status | Fix the parameter. Spread many domains over several calls |
GET /enrichment/company also answers with 202 and 404, which are not errors. 202 means the result isn't ready yet, and 404 with "status": "not_found" means there is no company data for this domain. For details, see Asynchronous results.
Contacts
The endpoints under /contacts/* use the Visitor Intelligence key. If a call ends with an error, Webmetic deducts no credits. For details, see Credits in the API.
| Status | Code | Meaning | What you do |
|---|---|---|---|
| 402 | INSUFFICIENT_CREDITS | Your credits aren't enough. Webmetic deducts nothing and unlocks nothing | Buy more credits in the dashboard and repeat the call |
| 403 | CONTACTS_SETUP_REQUIRED | Your account hasn't set up contacts yet. This affects search, reveal, and find | Set up contacts once, see Set up contacts |
| 403 | COMPANY_NOT_IDENTIFIED | The company_id doesn't belong to a company that visited your websites | Use a company_id from /company-sessions or /company |
| 403 | PLAN_REQUIRED | The company is hidden on the Free plan | Pick a company you can see, or switch plans |
| 403 | FEATURE_DISABLED | Your partner turned off the Data profiles feature for your account | Ask your partner to enable the feature, see Choose features for customers |
| 404 | COMPANY_WITHOUT_WEBSITE | This company has no website on file | The API returns no contacts for this company |
| 404 | CONTACT_NOT_FOUND | The contact isn't from your search, can no longer be found, or may not be delivered | Search again and use the new result |
| 409 | CONCURRENT_UPDATE | The same unlock is already running, or another deduction ran at the same moment | Repeat the call after a short wait. Webmetic never deducts twice |
| 502 | PROVIDER_UNAVAILABLE | The data source is temporarily unreachable | Try again in a few minutes |
| 502 | PROVIDER_ERROR | The request to the data source failed | Try again later. If the error stays, write to support |
Webhooks
The endpoints under /webhooks use the Visitor Intelligence key. For details, see Webhooks.
| Status | Code | Meaning | What you do |
|---|---|---|---|
| 400 | INVALID_URL | The URL is empty, longer than 2,048 characters, not https, contains credentials, or has no valid host or port | Use a public https URL without credentials |
| 400 | PRIVATE_ADDRESS | The host points to a private or reserved address | Use a server that is reachable from the internet |
| 400 | UNRESOLVABLE_HOST | The host can't be resolved | Check the host name and its DNS record |
| 400 | SEGMENT_REQUIRED | The segment.entered event needs a segment_id | Send the segment_id |
| 400 | SEGMENT_WEBSITE | The segment belongs to a website the webhook doesn't cover | Add the website to websites |
| 400 | NO_WEBSITE | Your account has no website yet | Add a website first |
| 400 | UNKNOWN_EVENT | GET /webhooks/sample doesn't know the event | Use an event name from the reference |
| 403 | WEBSITE_NOT_AUTHORIZED | A website in websites or website doesn't belong to your account | Enter the website as it appears in your account |
| 404 | WEBHOOK_NOT_FOUND | The webhook doesn't exist or belongs to another account | Use the ID of a webhook from GET /webhooks |
| 404 | SEGMENT_NOT_FOUND | The segment doesn't exist | Check the segment_id |
| 409 | WEBHOOK_LIMIT_REACHED | Your account already has the maximum number of webhooks. limit gives the number | Delete webhooks you no longer need |
Error handling in code
The example reads the code from detail.error when an error comes back, and it also handles a text in detail. For RATE_LIMITED and CONCURRENT_UPDATE, it waits the seconds in Retry-After and tries up to three times. If the header is missing, it waits only briefly. It reports all other errors with status, code, and text.
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const RETRY = ['RATE_LIMITED', 'CONCURRENT_UPDATE'];
async function callWebmetic(url, attempts = 3) {
for (let i = 1; i <= attempts; i++) {
const res = await fetch(url, {
headers: { Authorization: 'wmtc_YOUR_API_KEY' },
});
if (res.ok) return res.json();
const { detail } = await res.json();
// Paths outside the API answer {"detail": "Not Found"}
const known = detail !== null && typeof detail === 'object';
const error = known ? detail.error : `HTTP_${res.status}`;
const message = known ? detail.message : String(detail);
// Temporary errors: wait and try again
if (RETRY.includes(error) && i < attempts) {
const retryAfter = Number(res.headers.get('Retry-After'));
await sleep(retryAfter > 0 ? retryAfter * 1000 : 1000 * i); // Retry-After first, else the backoff
continue;
}
throw new Error(`${res.status} ${error}: ${message}`);
}
}
const data = await callWebmetic(
'https://hub.webmetic.de/company-sessions?domain=your-website.com',
);If your code doesn't know an error code, treat it as an error that a new attempt won't fix.