Real-Time Clocking
Clock in and clock out users in real-time. By default, the server captures the punch time when the request is received. Pass an optional timestamp (Unix seconds) to record a punch that already happened — typically when a physical clock device flushes queued punches after reconnecting from offline mode.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /time-clock/v1/time-clocks/{timeClockId}/clock-in | Clock in a user |
| POST | /time-clock/v1/time-clocks/{timeClockId}/clock-out | Clock out a user |
Offline Mode (Retroactive Punches)
Physical clock devices (kiosks, NFC stations, biometric terminals) usually call clock-in/out the moment an employee punches. When a device loses connectivity, punches queue locally. On reconnect, pass the original punch time via timestamp so the timesheet reflects when the employee actually clocked — not when the API call arrived.
Prefer clock-in/out over time activities for offline syncUse clock-in/out with
timestamp, not Create time activities. Time activities write a completed shift. If the employee later clocks out online via the normal clock-out flow, there is no open shift to close. Retroactive clock-in/out preserves the open-shift lifecycle.
Timestamp rules (whentimestampis supplied)
- Unix time in seconds
- Not in the future
- No more than 12 hours in the past
- Clock-out only: must be strictly after the open shift's clock-in time
- Clock-out only: the punch day must not be locked or approved
- When omitted, behavior is unchanged (server captures time at request receipt)
Typical offline flush order for one shift:
POST .../clock-inwith the queued clock-intimestampPOST .../clock-outwith the queued clock-outtimestamp
For punches older than 12 hours, use Create time activities instead.
Clock In
Record the start of a work period. By default the start time is captured when the call is executed. Pass an optional timestamp to record a clock-in that occurred earlier.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| timeClockId | integer | Yes | Time clock ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| userId | integer | Yes | User's ID (must be assigned to the time clock) |
| jobId | string | Conditional | Job or sub-job ID (required if job enforcement is enabled) |
| timezone | string | No | Tz format (e.g., America/New_York). Defaults to time clock setting |
| schedulerShiftId | string | No | Link to a scheduled shift |
| locationData | object | No | GPS coordinates and address |
| timestamp | integer | No | Clock-in time in Unix seconds. Omitted = server-now. Max 12 hours in the past; not in the future. Use when flushing offline punches. |
User Assignment RequiredThe user must be assigned to the specified time clock before they can clock in. Use the Users API to manage assignments.
Example: Basic Clock In
curl --request POST \
--url https://api.connecteam.com/time-clock/v1/time-clocks/12345/clock-in \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"userId": 9170357
}'Example: Clock In with Job
curl --request POST \
--url https://api.connecteam.com/time-clock/v1/time-clocks/12345/clock-in \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"userId": 9170357,
"jobId": "job-123",
"timezone": "America/New_York"
}'Example: Clock In with Location
curl --request POST \
--url https://api.connecteam.com/time-clock/v1/time-clocks/12345/clock-in \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"userId": 9170357,
"jobId": "job-123",
"timezone": "America/New_York",
"locationData": {
"address": "123 Main St, New York, NY 10001",
"latitude": 40.7128,
"longitude": -74.0060
}
}'Example: Clock In with Scheduler Shift
curl --request POST \
--url https://api.connecteam.com/time-clock/v1/time-clocks/12345/clock-in \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"userId": 9170357,
"schedulerShiftId": "sched-shift-789",
"timezone": "America/New_York"
}'Example: Retroactive Clock In (Offline Mode)
curl --request POST \
--url https://api.connecteam.com/time-clock/v1/time-clocks/12345/clock-in \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"userId": 9170357,
"jobId": "job-123",
"timezone": "America/New_York",
"timestamp": 1748345820
}'The returned data.shift.start.timestamp equals 1748345820.
Response
{
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data": {
"shift": {
"id": "shift-abc123",
"userId": 9170357,
"start": {
"timestamp": 1704110400,
"timezone": "America/New_York",
"locationData": {
"address": "123 Main St, New York, NY 10001",
"latitude": 40.7128,
"longitude": -74.0060
},
"source": {
"type": "api"
}
},
"jobId": "job-123",
"schedulerShiftId": null,
"createdAt": 1704110400,
"modifiedAt": 1704110400
}
}
}Clock Out
Record the end of a work period. By default the end time is captured when the call is executed. Pass an optional timestamp to record a clock-out that occurred earlier. Associates with the user's current open shift.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| timeClockId | integer | Yes | Time clock ID |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| userId | integer | Yes | User's ID |
| timezone | string | No | Tz format. Defaults to time clock setting |
| timestamp | integer | No | Clock-out time in Unix seconds. Omitted = server-now. Max 12 hours in the past; not in the future; must be after the open shift's clock-in. Use when flushing offline punches. |
| locationData | object | No | GPS coordinates and address |
Open Shift RequiredThe user must have an open shift (clocked in but not out) in this time clock. The clock-out will be associated with the most recent open shift.
Example: Basic Clock Out
curl --request POST \
--url https://api.connecteam.com/time-clock/v1/time-clocks/12345/clock-out \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"userId": 9170357
}'Example: Clock Out with Location
curl --request POST \
--url https://api.connecteam.com/time-clock/v1/time-clocks/12345/clock-out \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"userId": 9170357,
"timezone": "America/New_York",
"locationData": {
"address": "456 Oak Ave, New York, NY 10002",
"latitude": 40.7145,
"longitude": -74.0055
}
}'Example: Retroactive Clock Out (Offline Mode)
curl --request POST \
--url https://api.connecteam.com/time-clock/v1/time-clocks/12345/clock-out \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: YOUR_API_KEY' \
--data '{
"userId": 9170357,
"timezone": "America/New_York",
"timestamp": 1748374620
}'The returned data.shift.end.timestamp equals 1748374620.
Response
{
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"data": {
"shift": {
"id": "shift-abc123",
"userId": 9170357,
"start": {
"timestamp": 1704110400,
"timezone": "America/New_York",
"source": {
"type": "api"
}
},
"end": {
"timestamp": 1704139200,
"timezone": "America/New_York",
"locationData": {
"address": "456 Oak Ave, New York, NY 10002",
"latitude": 40.7145,
"longitude": -74.0055
},
"source": {
"type": "api"
}
},
"jobId": "job-123",
"createdAt": 1704110400,
"modifiedAt": 1704139200
}
}
}Integration Example
Complete clock-in/clock-out workflow, including flushing an offline queue:
class TimeClockIntegration {
constructor(apiKey, timeClockId) {
this.apiKey = apiKey;
this.timeClockId = timeClockId;
this.baseUrl = 'https://api.connecteam.com/time-clock/v1/time-clocks';
}
async clockIn(userId, { jobId, location, timestamp } = {}) {
const body = {
userId,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
};
if (jobId) body.jobId = jobId;
if (location) body.locationData = location;
if (timestamp != null) body.timestamp = timestamp;
const response = await fetch(`${this.baseUrl}/${this.timeClockId}/clock-in`, {
method: 'POST',
headers: { 'X-API-KEY': this.apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
return response.json();
}
async clockOut(userId, { location, timestamp } = {}) {
const body = {
userId,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
};
if (location) body.locationData = location;
if (timestamp != null) body.timestamp = timestamp;
const response = await fetch(`${this.baseUrl}/${this.timeClockId}/clock-out`, {
method: 'POST',
headers: { 'X-API-KEY': this.apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
return response.json();
}
/** Flush queued punches after device reconnects (oldest first). */
async flushOfflineQueue(events) {
for (const event of events) {
if (event.type === 'clock-in') {
await this.clockIn(event.userId, {
jobId: event.jobId,
location: event.location,
timestamp: event.timestamp,
});
} else if (event.type === 'clock-out') {
await this.clockOut(event.userId, {
location: event.location,
timestamp: event.timestamp,
});
}
}
}
}
// Live punch (no timestamp — server-now)
const tc = new TimeClockIntegration('YOUR_API_KEY', 12345);
await tc.clockIn(9170357, { jobId: 'job-123' });
await tc.clockOut(9170357);
// Offline flush after reconnect
await tc.flushOfflineQueue([
{ type: 'clock-in', userId: 9170357, jobId: 'job-123', timestamp: 1748345820 },
{ type: 'clock-out', userId: 9170357, timestamp: 1748374620 },
]);Error Responses
| HTTP Status | Error Code | When |
|---|---|---|
400 | INVALID_TIMESTAMP_FUTURE | Supplied timestamp is in the future |
400 | INVALID_TIMESTAMP_TOO_OLD | timestamp is more than 12 hours in the past |
400 | INVALID_TIMESTAMP_BEFORE_CLOCK_IN | (Clock-out only.) timestamp is at or before the open shift's clock-in time |
409 | HAS_LOCKED_DAYS | timestamp falls on a locked or approved timesheet day |
400 | — | User not assigned to the time clock |
400 | — | User already has an open shift (clock-in) |
400 | NO_OPEN_SHIFT | (Clock-out only.) No open shift to close |
400 | JOB_REQUIRED | (Clock-in only.) Job ID required by time clock settings |
400 | OPEN_BREAK | (Clock-in only.) User is clocked into a manual break |
400 | OUTSIDE_GEOFENCE | Punch location is outside the configured geofence |
All pre-existing error codes for these endpoints remain unchanged.
