Buddy Punch

Errors

All errors from the Buddy Punch Public API use a consistent JSON envelope, so you can handle them with a single parser. This guide covers the common (type, code) pairs you should expect, what to do with each, and when to retry. Individual endpoints may return additional codes for resource-specific situations (e.g., endpoint_limit_reached on webhooks) — always branch on type for retry logic, and use code + message for diagnostics.


Error Envelope

{
  "error": {
    "type": "invalid_request_error",
    "code": "resource_not_found",
    "message": "Employee 999 not found.",
    "requestId": "req_abc123"
  }
}
Field Type Description
type string High-level category — one of the types listed below
code string Specific reason within the category
message string Human-readable explanation
param string (Optional) The specific parameter at fault — included on some validation failures, but not guaranteed on every 400 or parameter_invalid response
errors array (Optional) Per-field breakdown for some multi-field validation failures, each item { field, message }; not guaranteed to be present on every validation error
requestId string Opaque id. Include this when reporting issues to support

Always branch on type for retry logic, not on HTTP status or message text. Messages are subject to change; types and codes are part of the contract.


HTTP Status → Error Type Mapping

Status Type Typical Code Retryable?
400 invalid_request_error parameter_invalid No — fix the request
401 authentication_error api_key_invalid No — fix credentials
401 authentication_error legacy_key_unsupported No — switch to a bp_live_ key
403 authorization_error insufficient_permissions No — grant missing scope
404 invalid_request_error resource_not_found No — the id doesn't exist (or isn't yours)
409 invalid_request_error idempotency_conflict No — fix the request or resolve the underlying conflict
429 rate_limit_error rate_limit_exceeded Yes — honor Retry-After
429 rate_limit_error account_rate_limit_exceeded Yes — honor Retry-After
429 rate_limit_error trial_resource_limit_exceeded No — trial cap on this resource reached; upgrade or reduce volume
429 rate_limit_error trial_resource_daily_limit_exceeded Wait for the rolling 24h window to free up, or upgrade
429 rate_limit_error trial_daily_writes_exceeded Wait for the rolling 24h window to free up, or upgrade
500 api_error internal_error Yes — exponential backoff, max 3 attempts

Error Types in Detail

invalid_request_error

The request couldn't be processed as submitted. Your client should log the error and surface it to the caller — retries won't succeed.

Code Status When
parameter_invalid 400 A parameter failed validation (missing required field, wrong type, out of range). See the errors array for per-field detail.
resource_not_found 404 The id in the path doesn't exist, OR exists in another account (we return 404 rather than 403 to avoid id-probing).
idempotency_conflict 409 A conflict on a unique constraint — e.g., creating a resource that would duplicate one that already exists. Not emitted for idempotency-key replays (those always return the cached response; see the Idempotency guide).

authentication_error

Your API key wasn't accepted.

Code Status When
api_key_invalid 401 Missing, malformed, expired, or revoked key
legacy_key_unsupported 401 The request used a legacy APIM subscription key header. The new API accepts only Authorization: Bearer bp_live_…. See the Migration Guide.

authorization_error

Authentication succeeded but the key isn't permitted to make this request.

Code Status When
insufficient_permissions 403 Your key doesn't hold the permission this endpoint requires (e.g., you called POST /employee with a key that only has employees:read). Grant the missing scope in the dashboard.

Dashboard-only limit: Trial accounts may create only one active API key at a time. This is enforced in the Buddy Punch dashboard (you'll see an inline error when you try to create a second key) — it is not returned as an API response since there is no Public API endpoint for creating keys. Revoke the existing key or upgrade to a paid plan to add more.

rate_limit_error

You exceeded one of the platform's rate limits. Per-minute limits are scoped per account and depend on your plan: Trial 60 rpm, Pro/Premium/Starter 600 rpm, Enterprise 1,000 rpm. A separate per-key DoS guard at 1,000 rpm runs alongside the per-account limit. Trial accounts also have per-resource and daily write caps.

Code Status When
rate_limit_exceeded 429 Per-key DoS guard hit (1,000 rpm). Sleep Retry-After seconds and retry.
account_rate_limit_exceeded 429 Your account-level per-minute quota was hit (60/600/1,000 rpm depending on plan). Sleep Retry-After seconds and retry; if you regularly hit it, distribute work or upgrade.
trial_resource_limit_exceeded 429 Trial-only — you've reached the trial cap for this resource (e.g., total employees or webhooks created via the API). Reduce usage or upgrade to a paid plan.
trial_resource_daily_limit_exceeded 429 Trial-only — you've reached the daily cap for this resource. Wait for the rolling 24h window to free up or upgrade.
trial_daily_writes_exceeded 429 Trial-only — daily total of write requests across the account has been used up. Wait for the rolling 24h window to free up or upgrade.

api_error

Something went wrong on our side.

Code Status When
internal_error 500 Unexpected server-side failure. Retry with exponential backoff. If it persists, report the requestId to support@buddypunch.com.

Validation Errors

400 responses on POST/PUT requests may include per-field detail via the errors array:

{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "Validation failed for one or more fields.",
    "errors": [
      { "field": "email",        "message": "email must be a valid email address." },
      { "field": "punchInDatetime", "message": "punchInDatetime is required." }
    ],
    "requestId": "req_abc123"
  }
}

Use the errors array to highlight specific form fields in your UI. Fall back to message when the array is absent — some single-parameter failures only populate the top-level message.


Retry Strategy

Error type Retry? Strategy
rate_limit_error (rate_limit_exceeded, account_rate_limit_exceeded) Yes Sleep Retry-After seconds, then retry. Back off exponentially on repeated 429s.
rate_limit_error (trial_resource_limit_exceeded, trial_resource_daily_limit_exceeded, trial_daily_writes_exceeded) No (short-term) Trial caps don't refresh within a Retry-After window — daily caps are measured over a rolling 24h window (old entries fall off gradually rather than resetting at a fixed time), and resource caps don't reset until usage drops. Surface the cap to the user or upgrade the plan rather than retrying in a tight loop.
api_error (500) Yes Exponential backoff starting at 1s, max 3 attempts (1s, 2s, 4s).
authentication_error No Don't retry. Fix the key.
authorization_error No Don't retry. Grant the missing scope, revoke a duplicate trial key, or upgrade.
invalid_request_error No The request is invalid or conflicts with current business state (including idempotency_conflict). Correct the request or resolve the conflict before trying again — a new idempotency key alone does not help.

Upstream or proxy infrastructure may still return 502 Bad Gateway or 503 Service Unavailable — these are not Buddy Punch-emitted api_error responses but transient transport failures. Treat them the same way as api_error: retry with exponential backoff.

For write requests that are safe to retry but not idempotent (e.g., POST /punch), use the Idempotency-Key header so a network-timeout retry doesn't create a duplicate — see the Idempotency guide.


Example: Parsing Errors in Client Code

curl + jq

response=$(curl -s -w '\n%{http_code}' \
  -H "Authorization: Bearer $BP_API_KEY" \
  https://api2.buddypunch.com/employee/999)

body=$(echo "$response" | head -n 1)
status=$(echo "$response" | tail -n 1)

if [ "$status" -ge 400 ]; then
  echo "$body" | jq -r '.error | "\(.type) / \(.code): \(.message) (reqId=\(.requestId))"'
fi

Node.js

const res = await fetch('https://api2.buddypunch.com/employee/999', {
  headers: { 'Authorization': `Bearer ${process.env.BP_API_KEY}` }
});

if (!res.ok) {
  const { error } = await res.json();
  if (error.type === 'rate_limit_error') {
    const retryAfter = Number(res.headers.get('retry-after') || 1);
    await new Promise(r => setTimeout(r, retryAfter * 1000));
    // retry…
  } else if (error.type === 'api_error') {
    // exponential backoff, max 3 attempts
  } else {
    console.error(`${error.type}/${error.code}: ${error.message} (req=${error.requestId})`);
  }
}