Code Examples
Copy-paste samples for the six most common flows, each in curl, Node.js (18+, built-in fetch), Python (requests), and C# (HttpClient).
Replace bp_live_xxxxxxxxxxxxxxxx with your API key. See the Getting Started guide for how to create one.
Convention: all samples assume the key is in an environment variable. Never hard-code keys in source files.
1. Authentication Ping
Check that your key works. GET /employee with limit=1 is a lightweight authenticated round-trip.
curl
curl -sS https://api2.buddypunch.com/employee?limit=1 \
-H "Authorization: Bearer $BP_API_KEY"
Node.js
const res = await fetch('https://api2.buddypunch.com/employee?limit=1', {
headers: { 'Authorization': `Bearer ${process.env.BP_API_KEY}` },
});
console.log(res.status, await res.json());
Python
import os, requests
res = requests.get(
'https://api2.buddypunch.com/employee',
headers={'Authorization': f'Bearer {os.environ["BP_API_KEY"]}'},
params={'limit': 1},
)
print(res.status_code, res.json())
C#
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("BP_API_KEY"));
var res = await client.GetAsync("https://api2.buddypunch.com/employee?limit=1");
Console.WriteLine($"{(int)res.StatusCode} {await res.Content.ReadAsStringAsync()}");
2. List Employees (with Full Pagination)
Loop through every page until hasMore is false.
curl
cursor=""
while : ; do
url="https://api2.buddypunch.com/employee?limit=100${cursor:+&startingAfter=$cursor}"
page=$(curl -sS "$url" -H "Authorization: Bearer $BP_API_KEY")
echo "$page" | jq '.data[]'
[ "$(echo "$page" | jq -r '.hasMore')" = "true" ] || break
cursor=$(echo "$page" | jq -r '.data[-1].id')
done
Node.js
async function listAllEmployees() {
const all = [];
let cursor = null;
while (true) {
const url = new URL('https://api2.buddypunch.com/employee');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('startingAfter', cursor);
const res = await fetch(url, {
headers: { 'Authorization': `Bearer ${process.env.BP_API_KEY}` },
});
const page = await res.json();
all.push(...page.data);
if (!page.hasMore) break;
cursor = page.data[page.data.length - 1].id;
}
return all;
}
Python
import os
import requests
def list_all_employees():
all_items = []
cursor = None
headers = {'Authorization': f'Bearer {os.environ["BP_API_KEY"]}'}
while True:
params = {'limit': 100}
if cursor:
params['startingAfter'] = cursor
page = requests.get(
'https://api2.buddypunch.com/employee',
headers=headers, params=params,
).json()
all_items.extend(page['data'])
if not page['hasMore']:
return all_items
cursor = page['data'][-1]['id']
C#
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
async Task<List<JsonElement>> ListAllEmployeesAsync(HttpClient client)
{
var all = new List<JsonElement>();
int? cursor = null;
while (true)
{
var url = $"https://api2.buddypunch.com/employee?limit=100" +
(cursor is int c ? $"&startingAfter={c}" : "");
using var doc = JsonDocument.Parse(await client.GetStringAsync(url));
var page = doc.RootElement;
var data = page.GetProperty("data");
foreach (var item in data.EnumerateArray())
all.Add(item.Clone());
if (!page.GetProperty("hasMore").GetBoolean()) return all;
// [^1] = last element; avoids requiring System.Linq just for .Last()
cursor = data[data.GetArrayLength() - 1].GetProperty("id").GetInt32();
}
}
3. Create a Punch
Record a completed punch (start + end) with an idempotency key so a retry after a timeout never creates a duplicate.
curl
curl -X POST https://api2.buddypunch.com/punch \
-H "Authorization: Bearer $BP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"employeeId": 4521,
"timeCardId": 5678,
"punchInDatetime": "2026-03-17T08:00:00",
"punchOutDatetime": "2026-03-17T17:00:00",
"locationId": 301,
"note": "Worked full shift"
}'
Node.js
import { randomUUID } from 'node:crypto';
const res = await fetch('https://api2.buddypunch.com/punch', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.BP_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
employeeId: 4521,
timeCardId: 5678,
punchInDatetime: '2026-03-17T08:00:00',
punchOutDatetime: '2026-03-17T17:00:00',
locationId: 301,
note: 'Worked full shift',
}),
});
console.log(res.status, await res.json());
Python
import os
import uuid
import requests
res = requests.post(
'https://api2.buddypunch.com/punch',
headers={
'Authorization': f'Bearer {os.environ["BP_API_KEY"]}',
'Content-Type': 'application/json',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
'employeeId': 4521,
'timeCardId': 5678,
'punchInDatetime': '2026-03-17T08:00:00',
'punchOutDatetime': '2026-03-17T17:00:00',
'locationId': 301,
'note': 'Worked full shift',
},
)
C#
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading.Tasks;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("BP_API_KEY"));
var req = new HttpRequestMessage(HttpMethod.Post, "https://api2.buddypunch.com/punch")
{
Content = JsonContent.Create(new {
employeeId = 4521,
timeCardId = 5678,
punchInDatetime = "2026-03-17T08:00:00",
punchOutDatetime = "2026-03-17T17:00:00",
locationId = 301,
note = "Worked full shift",
}),
};
req.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var res = await client.SendAsync(req);
Omit punchOutDatetime to create an open punch that can be closed later via PUT /punch/{id}/out.
4. Create a Time Off Request
curl
curl -X POST https://api2.buddypunch.com/timeOff \
-H "Authorization: Bearer $BP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"employeeId": 4521,
"ptoHoursTuples": [
{
"date": "2026-04-10",
"endDate": "2026-04-10",
"ptoEarningCodeId": 12,
"hours": 8.0,
"note": "Family trip"
}
],
"isDateRange": false
}'
Node.js
import { randomUUID } from 'node:crypto';
const res = await fetch('https://api2.buddypunch.com/timeOff', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.BP_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
employeeId: 4521,
ptoHoursTuples: [{
date: '2026-04-10',
endDate: '2026-04-10',
ptoEarningCodeId: 12,
hours: 8.0,
note: 'Family trip',
}],
isDateRange: false,
}),
});
Python
import os
import uuid
import requests
res = requests.post(
'https://api2.buddypunch.com/timeOff',
headers={
'Authorization': f'Bearer {os.environ["BP_API_KEY"]}',
'Content-Type': 'application/json',
'Idempotency-Key': str(uuid.uuid4()),
},
json={
'employeeId': 4521,
'ptoHoursTuples': [{
'date': '2026-04-10',
'endDate': '2026-04-10',
'ptoEarningCodeId': 12,
'hours': 8.0,
'note': 'Family trip',
}],
'isDateRange': False,
},
)
C#
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading.Tasks;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("BP_API_KEY"));
var req = new HttpRequestMessage(HttpMethod.Post, "https://api2.buddypunch.com/timeOff")
{
Content = JsonContent.Create(new {
employeeId = 4521,
ptoHoursTuples = new[] {
new {
date = "2026-04-10",
endDate = "2026-04-10",
ptoEarningCodeId = 12,
hours = 8.0,
note = "Family trip",
}
},
isDateRange = false,
}),
};
req.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var res = await client.SendAsync(req);
5. List Webhook Endpoints
curl
curl -sS https://api2.buddypunch.com/webhookEndpoint \
-H "Authorization: Bearer $BP_API_KEY"
Node.js
const res = await fetch('https://api2.buddypunch.com/webhookEndpoint', {
headers: { 'Authorization': `Bearer ${process.env.BP_API_KEY}` },
});
const { data } = await res.json();
console.log(`${data.length} endpoints registered`);
Python
import os
import requests
res = requests.get(
'https://api2.buddypunch.com/webhookEndpoint',
headers={'Authorization': f'Bearer {os.environ["BP_API_KEY"]}'},
)
for endpoint in res.json()['data']:
print(endpoint['id'], endpoint['url'], endpoint['status'])
C#
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Threading.Tasks;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("BP_API_KEY"));
var res = await client.GetAsync("https://api2.buddypunch.com/webhookEndpoint");
using var doc = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
foreach (var endpoint in doc.RootElement.GetProperty("data").EnumerateArray())
Console.WriteLine($"{endpoint.GetProperty("id")} {endpoint.GetProperty("url")}");
6. Verify a Webhook Signature
Every webhook POST includes an X-BuddyPunch-Signature header of the form t=<unix_timestamp>,v1=<hmac_hex>. The signed payload is "<timestamp>.<raw_body>".
Reject requests where the timestamp is older than five minutes (replay protection). Details and event payload shapes are in the Webhooks guide.
Node.js
import crypto from 'node:crypto';
function verifyWebhook(rawBody, headerSignature, secret) {
if (!headerSignature) return false;
const parts = Object.fromEntries(
headerSignature.split(',').map(p => {
const [key, value] = p.split('=', 2);
return [key?.trim(), value?.trim()];
})
);
const timestamp = parts.t;
const provided = parts.v1;
if (!timestamp || !provided) return false;
const ts = Number(timestamp);
if (!Number.isFinite(ts)) return false;
// Replay window: reject if > 5 minutes old (or far in the future).
if (Math.abs(Math.floor(Date.now() / 1000) - ts) > 300) return false;
const signed = `${timestamp}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
const expectedBuf = Buffer.from(expected);
const providedBuf = Buffer.from(provided);
// timingSafeEqual throws on length mismatch — check first so a malformed
// header returns false instead of a 500.
if (expectedBuf.length !== providedBuf.length) return false;
return crypto.timingSafeEqual(expectedBuf, providedBuf);
}
Python
import hmac, hashlib, time
def verify_webhook(raw_body: bytes, header_signature: str, secret: str) -> bool:
if not header_signature:
return False
# Tolerant parser: trim every key/value, ignore malformed segments with no '='.
parts = {}
for segment in header_signature.split(','):
if '=' not in segment:
continue
key, value = segment.split('=', 1)
parts[key.strip()] = value.strip()
timestamp = parts.get('t')
provided = parts.get('v1')
if not timestamp or not provided:
return False
try:
ts = int(timestamp)
except ValueError:
return False
# Replay window: reject if > 5 minutes off in either direction.
if abs(int(time.time()) - ts) > 300:
return False
signed = f"{timestamp}.{raw_body.decode()}"
expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
# compare_digest is length-safe: returns False on length mismatch, no exception.
return hmac.compare_digest(expected, provided)
C#
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
bool VerifyWebhook(string rawBody, string headerSignature, string secret)
{
if (string.IsNullOrWhiteSpace(headerSignature)) return false;
// Tolerant parser: trim keys/values, skip malformed segments (missing '=',
// more than one '=' in a value is fine because Split has count=2).
var parts = new Dictionary<string, string>(StringComparer.Ordinal);
foreach (var segment in headerSignature.Split(','))
{
var kv = segment.Split('=', 2);
if (kv.Length != 2) continue;
parts[kv[0].Trim()] = kv[1].Trim();
}
if (!parts.TryGetValue("t", out var timestampStr) ||
!parts.TryGetValue("v1", out var provided) ||
!long.TryParse(timestampStr, out var timestamp))
{
return false;
}
// Replay window: reject if > 5 minutes off in either direction.
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - timestamp) > 300) return false;
var signed = $"{timestamp}.{rawBody}";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
// ToLowerInvariant (not ToLower) — lowercase-of-hex-digits must be
// culture-independent, otherwise Turkish-locale hosts compute a different
// string for the same bytes and every signature fails to verify.
var expected = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(signed))).ToLowerInvariant();
// FixedTimeEquals is length-safe: returns false on length mismatch, no exception.
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(provided));
}
Related Guides
- Getting Started — auth and first request
- Data Model — field reference for every resource
- Errors — full error catalog
- Idempotency — safe retry semantics
- Webhooks — event delivery and signature details
- Migration Guide — moving from the legacy API