This page explains how BuyerCaddy API reports errors, how to handle 429 (throttling), and how to build robust retries with backoff and jitter.
Audience: developers/integrators.
Applies to all endpoints underhttps://api.salescaddy.ai/api
.
Error model
BuyerCaddy returns standard HTTP status codes. Error payloads commonly follow this shape:
[
{ "message": "Description of error" }
]
Notes
- Some endpoints can return a single object instead of an array; always check
Content-Type
and status.- For 401/403 you might get a minimal message from the gateway/IdP.
Typical HTTP status codes
Code | Meaning | What to do |
---|---|---|
400 | Bad Request | Validate required params/body schema; check types and ranges. |
401 | Unauthorized | Obtain/refresh OAuth token; include Authorization: Bearer <token> . |
403 | Forbidden | Your client is authenticated but not allowed; verify account configuration. |
404 | Not Found | Identifier (e.g., companyDomain , productId ) is wrong or data is unavailable. |
409 | Conflict | The requested operation conflicts with the current state (rare). |
415 | Unsupported Media | Check Content-Type (e.g., application/json ). |
422 | Unprocessable Entity | Body parsed but invalid (semantic validation failed). |
429 | Rate Limit Exceeded | Back off and retry after a delay (see below). |
500 | Internal Error | Retry with backoff; contact support if persistent. |
503 | Service Unavailable | Transient; retry with backoff. |
Example payloads (trimmed)
401 Unauthorized
{ "status": 401, "error": "Unauthorized", "message": "Missing or invalid access token" }
404 Not Found
[ { "message": "Company 'acme.invalid' was not found" } ]
429 Rate Limit
[ { "message": "Rate limit exceeded. Please retry later." } ]
Rate limits
- BuyerCaddy enforces per‑client throttling to ensure stability.
- On 429, you must retry with exponential backoff.
- When present, honor the
Retry-After
header (seconds). - Prefer pagination & batching over large single requests.
- Keep concurrency reasonable; increase gradually and observe 429s.
If you consistently hit 429 even with backoff, reduce request rate or contact support to discuss plan limits.
Retry strategies (backoff + jitter)
Below are production‑ready snippets to handle transient errors (429, 500, 503, timeouts).
They implement exponential backoff with jitter and respect Retry-After
when present.
Example Retry (GET) — Vendors
# bash (curl) — simple loop with sleep and Retry-After support via grep/sed
URL="https://api.salescaddy.ai/api/vendors?page=0&size=10"
HDR="Authorization: Bearer $TOKEN"
for i in 0 1 2 3 4; do
RESP_HEADERS=$(mktemp); RESP_BODY=$(mktemp)
http_code=$(curl -sS -D "$RESP_HEADERS" -w "%{http_code}" -o "$RESP_BODY" -H "$HDR" "$URL")
if [ "$http_code" -lt 400 ] || [ "$http_code" -eq 404 ]; then
cat "$RESP_BODY"
rm -f "$RESP_HEADERS" "$RESP_BODY"
break
fi
# Extract Retry-After if present
ra=$(grep -i "^Retry-After:" "$RESP_HEADERS" | sed -E 's/Retry-After:\s*([0-9]+)/\1/i')
delay=${ra:-$(( 1 << i ))}
sleep "$delay"
done
// node (fetch) — exponential backoff + jitter + Retry-After
import fetch from "node-fetch";
const URL = "https://api.salescaddy.ai/api/vendors?page=0&size=10";
const headers = { Authorization: `Bearer ${process.env.TOKEN}` };
async function getWithRetry(url, max=5) {
let attempt = 0;
while (true) {
const res = await fetch(url, { headers });
if (res.status < 400 || res.status === 404) return res.json();
const retryAfter = parseInt(res.headers.get("retry-after") || "0", 10);
if (attempt >= max) throw new Error(`Failed after ${max} attempts: ${res.status}`);
const backoff = Math.min(60, Math.pow(2, attempt)) + Math.random(); // jitter
const delay = retryAfter > 0 ? retryAfter : backoff;
await new Promise(r => setTimeout(r, delay * 1000));
attempt++;
}
}
console.log(await getWithRetry(URL));
# python (requests) — exponential backoff + jitter + Retry-After
import os, time, random, requests
URL = "https://api.salescaddy.ai/api/vendors"
HEADERS = {"Authorization": f"Bearer {os.environ['TOKEN']}"}
def get_with_retry(url, params=None, max_attempts=5):
for attempt in range(max_attempts):
r = requests.get(url, params=params, headers=HEADERS, timeout=30)
if r.status_code < 400 or r.status_code == 404:
return r.json()
retry_after = r.headers.get("Retry-After")
if attempt == max_attempts - 1:
r.raise_for_status()
backoff = min(60, 2 ** attempt) + random.random()
delay = int(retry_after) if retry_after and retry_after.isdigit() else backoff
time.sleep(delay)
print(get_with_retry(URL, params={"page":0,"size":10}))
// csharp (HttpClientFactory recommended) — backoff + jitter + Retry-After
using System.Net.Http.Headers;
static async Task<string> GetWithRetry(HttpClient http, string url, string token, int max=5) {
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var rand = new Random();
for (int i=0; i<max; i++) {
var res = await http.GetAsync(url);
if ((int)res.StatusCode < 400 || res.StatusCode == System.Net.HttpStatusCode.NotFound)
return await res.Content.ReadAsStringAsync();
if (i == max - 1) throw new HttpRequestException($"Failed after {max} attempts: {(int)res.StatusCode}");
var retryAfter = 0;
if (res.Headers.TryGetValues("Retry-After", out var values) && int.TryParse(values.FirstOrDefault(), out var v))
retryAfter = v;
var backoff = Math.Min(60, Math.Pow(2, i)) + rand.NextDouble(); // jitter
var delay = retryAfter > 0 ? retryAfter : backoff;
await Task.Delay(TimeSpan.FromSeconds(delay));
}
throw new Exception("unreachable");
}
var json = await GetWithRetry(new HttpClient(), "https://api.salescaddy.ai/api/vendors?page=0&size=10", TOKEN);
Console.WriteLine(json);
Example Retry (POST) — Products In Use
When retrying POST requests, ensure your operation is idempotent on the client side (e.g., deduplicate by request hash) to avoid duplicates on transient failures.
curl -sS -X POST "https://api.salescaddy.ai/api/companies/hilton.com/products-in-use" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{ "productIds": ["prod_office365","prod_slack"] }'
const res = await fetch("https://api.salescaddy.ai/api/companies/hilton.com/products-in-use", {
method: "POST",
headers: { "Authorization": `Bearer ${process.env.TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify({ productIds: ["prod_office365","prod_slack"] })
});
console.log(await res.json());
r = requests.post("https://api.salescaddy.ai/api/companies/hilton.com/products-in-use",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type":"application/json"},
json={"productIds":["prod_office365","prod_slack"]})
print(r.json())
var req = new StringContent("{"productIds":["prod_office365","prod_slack"]}", System.Text.Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.salescaddy.ai/api/companies/hilton.com/products-in-use", req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
Validation & pagination hints
- Validate required fields (e.g.,
companyDomain
,productId
) and types (page
/size
integers,size
within limits). - Pagination defaults:
page=0
,size=20
(upper bounds vary by endpoint). - Avoid requesting very large
size
; prefer multiple pages to reduce timeouts. - For CSV exports, include
X-On-Behalf-Of-User
when required by the endpoint.
Networking & timeouts
- Use reasonable timeouts (e.g., 30s) and treat timeouts like other transient errors (retry with backoff).
- Prefer persistent HTTP clients (reuse connection pools) for performance.
- Log request IDs/contexts (if provided in headers) to correlate failures.
Monitoring
- Track 4xx/5xx rates, latency, and retry counts.
- Alert on sustained spikes in 429/5xx despite backoff (possible misconfiguration or plan limits).