Base URL
All API requests should be made to:https://api.bouncewatch.com/api/v1
Request Headers
Include these headers with every request:| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API authentication key |
Content-Type | No | application/json (for POST requests) |
Accept | No | application/json |
curl -X GET "https://api.bouncewatch.com/api/v1/company/stripe.com" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Accept: application/json"
Response Format
All responses are returned in JSON format with a consistent structure:Successful Response
{
"success": true,
"credits_used": 10,
"credits_breakdown": {
"base": 10,
"enrichments": {}
},
"modules_included": ["base"],
"data": {
// Response data here
}
}
Error Response
Every error carriessuccess: false and a stable error code. Switch on error, not
on message — the code is part of the contract, the sentence is not and may be reworded.
{
"success": false,
"error": "webhook_required",
"message": "Webhook URL is required for enrichment requests",
"hint": "Configure a webhook URL in your API panel or provide it via the X-Webhook-URL header."
}
HTTP Status Codes
| Code | Status | Description |
|---|---|---|
200 | OK | Request successful (data served from 24h cache) |
202 | Accepted | Enrichment queued (results via webhook), or results not ready yet |
400 | Bad Request | Invalid parameters, or a missing/non-https webhook URL |
401 | Unauthorized | Invalid or missing API key |
402 | Payment Required | Insufficient credits, or no active subscription |
403 | Forbidden | Key suspended, IP not allowed, or batch belongs to another account |
404 | Not Found | Unknown batch id |
409 | Conflict | Domain already enriching, or inside a 24-hour cooldown |
410 | Gone | Retired mode=cached parameter |
422 | Unprocessable | Webhook URL rejected as a placeholder |
429 | Too Many Requests | Rate limit or concurrent-enrichment limit exceeded |
500 | Internal Server Error | Server error — please retry |
409 is common and is not a failure. We deduplicate per domain: if the same domain
is already being enriched, or was enriched in the last 24 hours, you get a 409 rather
than a second charge. Treat it as “ask the main endpoint again” — see
recently_enriched below.Error Codes
Authentication and access
Authentication and access
| Error Code | HTTP | What to do |
|---|---|---|
missing_api_key | 401 | Add the X-API-Key header |
invalid_api_key | 401 | Check the key; it may have been regenerated |
api_key_disabled | 403 | Account suspended — contact support |
ip_not_allowed | 403 | Calling IP is outside your allow-list |
subscription_required | 402 | Subscription expired — upgrade in the billing panel |
Request errors
Request errors
| Error Code | HTTP | What to do |
|---|---|---|
missing_parameter | 400 | A required parameter was absent |
invalid_domain | 400 | Provide a valid domain, e.g. stripe.com |
invalid_module | 400 | Unknown enrich module — the response lists valid_modules |
webhook_required | 400 | Configure an https:// webhook, or send X-Webhook-URL |
invalid_webhook_url | 422 | Placeholder/example host rejected — use a real URL |
deprecated_parameter | 410 | Remove mode=cached; the response carries a migration guide |
Deduplication and concurrency
Deduplication and concurrency
| Error Code | HTTP | What to do |
|---|---|---|
enrichment_in_progress | 409 | This domain is already being enriched — wait for the webhook |
recently_enriched | 409 | Enriched within 24h. Request the main endpoint again to get it free |
no_data_cooldown | 409 | We found nothing for this domain recently; Retry-After says when to retry |
concurrent_limit_reached | 429 | Too many enrichments in flight for your plan |
company_not_found is not returned for an unknown domain — the API enriches it
instead and answers 202. You will only see it if a company was deleted between
queuing a batch and fetching its results.Credits and rate limits
Credits and rate limits
| Error Code | HTTP | What to do |
|---|---|---|
insufficient_credits | 402 | Response carries credits_required and credits_available |
rate_limit_exceeded | 429 | Response carries retry_after and a Retry-After header |
Batch and enrichment results
Batch and enrichment results
| Error Code | HTTP | What to do |
|---|---|---|
batch_not_found | 404 | Unknown batch_id |
batch_forbidden | 403 | That batch belongs to another account |
enrichment_processing | 202 | Not finished yet — keep polling the status endpoint |
no_data_found | 200 | No data exists for this domain. Credits are refunded |
enrichment_failed | 200 | Enrichment failed. Credits are refunded |
server_error | 500 | Transient — retry |
Error Handling Example
Note: When a domain requires enrichment, the API returns
202 Accepted and starts enrichment automatically. Handle this by checking for status === 202 and the batch_id in the response. Results will be delivered to your webhook.async function getCompanyData(domain) {
try {
const response = await fetch(
`https://api.bouncewatch.com/api/v1/company/${domain}`,
{ headers: {
'X-API-Key': API_KEY,
'X-Webhook-URL': WEBHOOK_URL
}}
);
const data = await response.json();
// 202 — enrichment queued. Results arrive at your webhook.
if (response.status === 202) {
return { status: 'enriching', batch_id: data.batch_id };
}
if (!data.success) {
switch (data.error) {
case 'insufficient_credits':
console.log(`Need ${data.credits_required}, have ${data.credits_available}`);
break;
case 'rate_limit_exceeded':
console.log(`Rate limited. Retry after ${data.retry_after}s`);
break;
case 'webhook_required':
console.log('Configure an https webhook URL first');
break;
case 'recently_enriched':
case 'enrichment_in_progress':
// Not a failure: the data is on its way, or already free to fetch.
console.log(`Deduplicated: ${data.message}`);
break;
default:
console.log(`${data.error}: ${data.message}`);
}
return null;
}
return data.data;
} catch (error) {
console.error('Network error:', error);
return null;
}
}
import requests
def get_company_data(domain):
try:
response = requests.get(
f'https://api.bouncewatch.com/api/v1/company/{domain}',
headers={'X-API-Key': API_KEY, 'X-Webhook-URL': WEBHOOK_URL}
)
data = response.json()
# 202 — enrichment queued. Results arrive at your webhook.
if response.status_code == 202:
return {'status': 'enriching', 'batch_id': data['batch_id']}
if not data['success']:
error = data.get('error')
if error == 'insufficient_credits':
print(f"Need {data['credits_required']}, have {data['credits_available']}")
elif error == 'rate_limit_exceeded':
print(f"Rate limited. Retry after {data['retry_after']}s")
elif error == 'webhook_required':
print('Configure an https webhook URL first')
elif error in ('recently_enriched', 'enrichment_in_progress'):
# Not a failure: deduplicated, nothing was charged.
print(f"Deduplicated: {data['message']}")
else:
print(f"{error}: {data['message']}")
return None
return data['data']
except requests.RequestException as e:
print(f"Network error: {e}")
return None
function getCompanyData($domain) {
$ch = curl_init("https://api.bouncewatch.com/api/v1/company/{$domain}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'X-API-Key: ' . API_KEY,
'X-Webhook-URL: ' . WEBHOOK_URL,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false) {
echo "Network error\n";
return null;
}
$data = json_decode($response, true);
// 202 — enrichment queued. Results arrive at your webhook.
if ($httpCode === 202) {
return ['status' => 'enriching', 'batch_id' => $data['batch_id']];
}
if (!$data['success']) {
switch ($data['error']) {
case 'insufficient_credits':
echo "Need {$data['credits_required']}, have {$data['credits_available']}\n";
break;
case 'rate_limit_exceeded':
echo "Rate limited. Retry after {$data['retry_after']}s\n";
break;
case 'webhook_required':
echo "Configure an https webhook URL first\n";
break;
case 'recently_enriched':
case 'enrichment_in_progress':
// Not a failure: deduplicated, nothing was charged.
echo "Deduplicated: {$data['message']}\n";
break;
default:
echo "{$data['error']}: {$data['message']}\n";
}
return null;
}
return $data['data'];
}

