Buddy Punch

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:

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

  1. Log in to Buddy Punch
  2. Navigate to Settings > Integrations > Developer API > Manage API Keys
  3. Click Create API Key
  4. Select the permission scopes your integration needs (e.g., employees:read, locations:write)
  5. 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:

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}/times to 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.