Migrating from the Legacy API to the Buddy Punch Public API
This guide helps you migrate from the legacy Buddy Punch API (api.buddypunch.com) to the new Buddy Punch Public API.
What's Changing
The new API provides:
- A new base URL —
https://api2.buddypunch.com, the permanent home of the new API. (The legacyapi.buddypunch.comhost keeps serving the old API until it is decommissioned.) - Simpler authentication with a single API key instead of APIM subscription keys + bearer tokens
- Structured error responses with consistent JSON envelopes
- Rate limit transparency via response headers
- Date-based versioning for predictable breaking changes
- Interactive documentation at the Scalar developer portal
Single-word endpoint paths (/employee, /location, /punch, etc.) carry over from the legacy API unchanged. Multi-word paths are now camelCased for consistency with the JSON property casing — so /payperiod becomes /payPeriod, /timecard becomes /timeCard, /timeoff becomes /timeOff, /timeentry becomes /timeEntry, /departmentcode becomes /departmentCode, and /webhookendpoint becomes /webhookEndpoint. Responses continue to use camelCase JSON. The new API also adds many endpoints not available in the legacy API — see the endpoint reference below and the interactive API portal for the full list.
Step 1: Get an API Key
- Log in to Buddy Punch
- Navigate to Settings > Integrations > Developer API > Manage API Keys
- Click Create API Key
- Select the permission scopes your integration needs (e.g.,
employees:read,locations:write) - Copy the key. Production keys start with
bp_live_.
Important: Store your API key securely. It cannot be retrieved after creation.
Step 2: Update Authentication
The legacy API required two headers: an APIM subscription key and a bearer token. The new API replaces both with a single API key passed as a Bearer token.
Before (Legacy):
GET https://api.buddypunch.com/employee
Ocp-Apim-Subscription-Key: your-subscription-key
Authorization: Bearer your-apim-bearer-token
After (New API):
GET https://api2.buddypunch.com/employee
Authorization: Bearer bp_live_xxxxxxxxxxxxxxxx
Remove the Ocp-Apim-Subscription-Key header entirely and replace the old Bearer token with your new API key (bp_live_).
Step 3: Verify Your Endpoints
The base URL for the new API is https://api2.buddypunch.com. Single-word paths are unchanged; multi-word paths are camelCased (see the What's Changing section above for the full list of renames). Here are the available endpoints:
| Resource | Method | Path |
|---|---|---|
| Employees | GET | /employee |
| GET | /employee/{id} |
|
| POST | /employee |
|
| PUT | /employee/{id} |
|
| GET | /employee/payrates (all employees, paginated) |
|
| GET | /employee/{id}/payrates |
|
| Locations | GET / POST | /location |
| GET / PUT / DELETE | /location/{id} |
|
| GET / PUT / DELETE | /location/{id}/employees |
|
| PUT / DELETE | /location/{id}/departmentCodes |
|
| Department Codes | GET / POST | /departmentCode |
| GET / PUT / DELETE | /departmentCode/{id} |
|
| GET / PUT / DELETE | /departmentCode/{id}/employees |
|
| Positions | GET / POST | /position |
| GET / PUT / DELETE | /position/{id} |
|
| Groups | GET / POST | /group |
| GET / PUT / DELETE | /group/{id} |
|
| Geofences | GET / POST | /geofence |
| GET / PUT / DELETE | /geofence/{id} |
|
| GET / PUT / DELETE | /geofence/{id}/employees |
|
| PUT | /geofence/{id}/employees/settings |
|
| Pay Periods | GET | /payPeriod |
| PUT | /payPeriod/{id} (update status) |
|
| GET | /payPeriod/{id}/times |
|
| Time Cards | GET | /timeCard |
| GET | /timeCard/{id} |
|
| GET | /timeCard/{id}/times (with ?type= filter) |
|
| GET | /timeCard/{id}/details |
|
| PUT | /timeCard/{id}/approve |
|
| Punches | POST | /punch/in, /punch/out |
| GET | /punch?timeCardId= |
|
| GET | /punch/{id} (full details) |
|
| POST | /punch (omit punchOutDatetime for an open punch, supply it for a completed one) |
|
| PUT | /punch/{id}/in, /punch/{id}/out |
|
| DELETE | /punch/{id} |
|
| Breaks | GET / POST | /punch/{punchId}/break |
| PUT / DELETE | /punch/{punchId}/break/{breakId} |
|
| DELETE | /punch/{punchId}/break/automatic |
|
| POST | /break/start, /break/end (real-time, operate on employee's open punch) |
|
| Time Entries | GET / POST | /timeEntry |
| GET / PUT / DELETE | /timeEntry/{id} |
|
| Shifts | POST | /shift |
| GET / PUT / DELETE | /shift/{id} |
|
| Schedules | GET | /schedule (filter by start, end, locationIds, jobCodeIds, positionIds, employeeIds) |
| Time Off | GET | /timeOffType |
| GET / POST | /timeOff (list filters: employeeId, startDate, endDate, includeAllStaff) |
|
| GET / DELETE | /timeOff/{id} |
|
| POST | /timeOff/{id}/approve, /timeOff/{id}/reject |
|
| Time Off Accruals | GET | /employee/{employeeId}/timeOffAccrualBalances |
| GET / POST | /employee/{employeeId}/timeOffAccrualEntries |
|
| Webhooks | GET / POST | /webhookEndpoint |
| GET / PUT / DELETE | /webhookEndpoint/{id} |
|
| Events | GET | /event |
| GET | /event/{id} |
Relocated endpoints: The legacy API's "Time" section is now split across parent resources:
- List Time by Pay Period →
GET /payPeriod/{id}/times(under Pay Periods)- List Time for Time Card →
GET /timeCard/{id}/times(under Time Cards)- The unified
/timeCard/{id}/timesendpoint accepts an optional?type=filter (punch,time_entry, ortime_off) to filter by time type.
Strict type contracts: Each resource endpoint serves only its own type —
/punch/{id}and/punch/{punchId}/break/*return 404 for time-entry or time-off ids, and/timeOff/{id}only accepts time-off request ids. Use/timeEntry/{id}and/timeOff/{id}for those resources directly, or/timeCard/{id}/timesto get everything in one list.
Step 4: Handle New Response Formats
List Responses
List endpoints now return a paginated envelope:
{
"object": "list",
"data": [ ... ],
"hasMore": true,
"totalCount": 142,
"url": "/employee"
}
Your items are in the data array. Use hasMore and cursor-based pagination (?startingAfter=123) to page through results.
Error Responses
All errors return a structured envelope:
{
"error": {
"type": "invalid_request_error",
"code": "resource_not_found",
"message": "Employee 999 not found.",
"requestId": "req_abc123"
}
}
| HTTP Status | Error Type | Meaning |
|---|---|---|
| 400 | invalid_request_error |
Bad request parameters |
| 401 | authentication_error |
Missing or invalid API key |
| 403 | authorization_error |
Insufficient permissions |
| 404 | invalid_request_error |
Resource not found |
| 429 | rate_limit_error |
Rate limit exceeded |
| 500 | api_error |
Server error |
Step 5: Monitor Rate Limits
Per-minute rate limits are scoped per account and depend on your Buddy Punch plan:
| Plan tier | Per-account limit |
|---|---|
| Trial / non-paying | 60 requests/minute |
| Pro / Premium / Starter (any paid non-Enterprise) | 600 requests/minute |
| Enterprise | 1,000 requests/minute |
A separate per-API-key DoS guard at 1,000 rpm runs alongside the per-account limit. Trial accounts also have per-resource caps, daily write caps, and a one-active-API-key limit — see the Errors guide for the full list of trial-only 429/403 codes.
On a 429 Too Many Requests the response advertises which limit you hit:
Header (on 429 only) |
Description |
|---|---|
X-RateLimit-Limit |
Max requests per window for the limit you tripped |
X-RateLimit-Remaining |
Always 0 on a 429 |
Retry-After |
Seconds to wait before retrying |
Code Samples
cURL
# Legacy
curl -H "Ocp-Apim-Subscription-Key: your-subscription-key" \
-H "Authorization: Bearer your-apim-bearer-token" \
https://api.buddypunch.com/employee
# New
curl -H "Authorization: Bearer bp_live_xxx" \
https://api2.buddypunch.com/employee
JavaScript
// Legacy
const response = await fetch('https://api.buddypunch.com/employee', {
headers: {
'Ocp-Apim-Subscription-Key': 'your-subscription-key',
'Authorization': 'Bearer your-apim-bearer-token'
}
});
// New
const response = await fetch('https://api2.buddypunch.com/employee', {
headers: { 'Authorization': 'Bearer bp_live_xxx' }
});
const { data, hasMore, totalCount } = await response.json();
Python
import requests
# Legacy
r = requests.get('https://api.buddypunch.com/employee',
headers={
'Ocp-Apim-Subscription-Key': 'your-subscription-key',
'Authorization': 'Bearer your-apim-bearer-token'
})
# New
r = requests.get('https://api2.buddypunch.com/employee',
headers={'Authorization': 'Bearer bp_live_xxx'})
employees = r.json()['data']
C#
// Legacy
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "your-subscription-key");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "your-apim-bearer-token");
var response = await client.GetAsync("https://api.buddypunch.com/employee");
// New
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "bp_live_xxx");
var response = await client.GetAsync("https://api2.buddypunch.com/employee");
FAQ
Q: Can I use my existing APIM subscription key or bearer token?
No. You need to create a new API key in the Buddy Punch dashboard. Both the APIM subscription key and the old bearer token are replaced by a single API key passed as a Bearer token (bp_live_…).
Q: Are the response fields the same?
Response fields use camelCase naming, matching the legacy API format. The main difference is that list responses are wrapped in a pagination envelope with data, hasMore, and totalCount fields.
Q: What about 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 per-key DoS guard at 1,000 rpm runs alongside. On a 429, the response includes Retry-After and the relevant X-RateLimit-* headers; trial accounts may also hit per-resource and daily write caps documented in the Errors guide.
Q: How does versioning work?
The API uses date-based versioning via the X-Api-Version header. If you don't send this header, you'll get the latest version. When breaking changes are introduced, a new version date will be published and you can pin to the previous version.
Q: Where can I get help? Contact support@buddypunch.com or visit buddypunch.com/support.
Related Guides
- Getting Started — zero-to-first-request walkthrough (read this first if you're new)
- Data Model — field-level reference for every resource
- Errors — full error-code catalog with retry recommendations
- Idempotency — safe retries for write requests
- Code Examples — copy-paste samples in curl / Node.js / Python / C#
- Webhooks — real-time event notifications
- Changelog — additions, changes, deprecations