API INTEGRATION GUIDE
Timecard Query API · Open Time Clock · Product Q3
Endpoint details, request parameters, response structures, and JavaScript integration examples
This document is based on the preceding API description. No live endpoint testing has been performed. Request parsing by the outer routing layer, HTTP status codes, and database field serialization should be confirmed against the deployed implementation. Sample data is for illustration only.
1. API Overview
| Item | Description |
|---|---|
| Endpoint |
https://api1.opentimeclock.com/Q3OTC/api
|
| HTTP Methods | GET / POST |
| Response Format | JSON |
| Routing Command | Q3/api/getTimecardsApi |
| Purpose | Retrieve timecard records by company, employee name, date range, and selected entry types. |
Request format: The examples in this document use GET query parameters. The POST request body format and Content-Type must be confirmed with the outer routing layer. This document does not assume that POST accepts JSON or form data.
2. Request Parameters
| Parameter | Required | Description |
|---|---|---|
cmd |
Yes |
Must be Q3/api/getTimecardsApi.
|
companyId |
Yes | Company ID. Must be a positive safe integer. Numeric strings are accepted. |
apiKey |
Yes | Company API key. Case-sensitive; leading and trailing spaces are significant. |
time |
Yes | Include clock-in and clock-out records. |
hours |
Yes | Include manually entered hour records. |
lunch |
Yes | Include lunch records. |
break |
Yes | Include break records. |
ptoPending |
Yes | Include PTO requests awaiting approval. |
ptoApproved |
Yes | Include approved PTO records. |
ptoDenied |
Yes | Include denied PTO records. |
employeeFullName |
Yes | Employee's full name. Length must be 1–100 after trimming leading and trailing whitespace. |
startDate |
Yes | Start date in YYYY-MM-DD format. |
endDate |
Yes | End date in YYYY-MM-DD format. |
offset |
No |
Defaults to 0.
Allowed range: 0–100000.
Numeric strings are accepted.
|
Parameter restrictions:
Custom parameters such as limit and
pageSize are not supported.
Unknown parameters are rejected.
Although ip is allowed, this handler does not use it.
2.1 Example Parameter Values
The following illustrates parameter values only. It does not define the POST request body format.
cmd = Q3/api/getTimecardsApi
companyId = 123456
apiKey = YOUR_API_KEY
time = true
hours = true
lunch = true
break = true
ptoPending = true
ptoApproved = true
ptoDenied = true
employeeFullName = John Smith
startDate = 2026-08-01
endDate = 2026-08-15
offset = 0
Entry type flags:
All seven entry type parameters are required in every request.
Their names are identical to the entryType values
returned in each record, so no name mapping is needed.
Accepted values are true and false.
The strings true, false,
1, and 0 are also accepted and are
case-insensitive. At least one parameter must be
true; otherwise the request returns a parameter
validation error. Query strings always carry these values as
strings, so use true and false in
GET requests.
3. Query and Pagination Rules
3.1 Query Scope
-
Records are filtered by
entryDate. Both the start date and the end date are included. - The difference between the start date and the end date must not exceed 366 days.
- Only employees who are not deleted are included. Archived (former) employees are included, so their timecards remain available for payroll after they leave the company. Deleted timecard records are excluded.
-
PTO approval status is part of the entry type. Use
ptoPending,ptoApproved, andptoDeniedto select the PTO records you need. There is no separate approval status filter. - All selected entry types are queried together, and every returned record carries its own actual entry type.
3.2 Pagination and Sorting
- Each page returns a maximum of 1,000 records.
-
For standard pagination, use
offset=0, 1000, 2000, …. - The 1,000-record limit applies to all selected entry types combined, not to each type separately.
-
Records are sorted in ascending order by
entryDate, then byrecordId. -
The response includes a
hasMoreflag.truemeans at least one more page remains for the same query. -
Stop pagination when
hasMoreisfalse. The last page may contain anywhere from 0 to 1,000 records.
Consistency considerations:
If records are added or deleted during retrieval,
offset-based pagination may produce duplicate or missing records.
If hasMore is still true at the
maximum allowed offset, additional records remain.
Narrow the date range and query again.
4. Successful Response
4.1 Response Structure
{
"data": [
{
"recordId": 12345,
"entryType": "ptoApproved",
"entryDate": "2026-08-05",
"hours": 8,
"employeeNote": "Annual leave",
"managerComment": "",
"ptoName": "Vacation",
"updatedDateTime": "2026-09-12 09:18:58",
"createdDateTime": "2026-09-12 09:18:58"
}
],
"status": "SUCCESS",
"message": "",
"hasMore": false
}
| Top-Level Field | Description |
|---|---|
data |
Array of timecard records. It is an empty array when no records match the request. This is a successful response, not an error. |
status |
SUCCESS for a successful request. |
message |
An empty string in the successful response example. |
hasMore |
true when at least one more page remains
for the same query; false when the current
page is the last page. Every ERROR response
also carries hasMore false,
so callers can handle both response shapes uniformly.
|
4.2 Common Fields for All Records
| Field | Description |
|---|---|
recordId |
Timecard record ID. |
entryType |
Actual entry type. Values are
time,
hours,
lunch,
break,
ptoPending,
ptoApproved, and
ptoDenied. Values are case-sensitive.
|
entryDate |
Record date. |
employeeNote |
Employee note. |
managerComment |
Manager comment. |
updatedDateTime |
Last modification time of the timecard record, in
YYYY-MM-DD HH:MM:SS format and expressed in
UTC+0. Returned for every entry type.
|
createdDateTime |
Creation time of the timecard record, in
YYYY-MM-DD HH:MM:SS format and expressed in
UTC+0. Returned for every entry type.
|
4.3 Additional Fields by Entry Type
| Entry Type | Additional Response Fields |
|---|---|
time |
inDateTime,
outDateTime,
extraPay,
mileage,
shiftName,
departmentName,
jobName,
customerName,
projectName,
taskName
|
hours |
hours,
extraPay,
mileage,
shiftName,
departmentName,
jobName,
customerName,
projectName,
taskName
|
ptoPending |
hours, ptoName |
ptoApproved |
hours, ptoName |
ptoDenied |
hours, ptoName |
lunch |
inDateTime,
outDateTime,
shiftName
|
break |
inDateTime,
outDateTime,
shiftName
|
Related names: If a related name cannot be found, an empty string is returned. The corresponding related entity IDs are not returned.
Timestamp fields:
updatedDateTime and createdDateTime
are returned as UTC+0 strings in
YYYY-MM-DD HH:MM:SS format, for example
2026-09-12 09:18:58. They carry no time zone
designator and no offset. They are server timestamps, not the
employee's local time, and they do not follow the employee's
configured time zone. Convert them on your side using the time
zone that applies to your own report or payroll period.
Types and units: The handler does not standardize database field types or specify date/time serialization formats, time zones, or monetary units. Confirm these details against the database configuration and outer JSON serialization layer. Do not infer all field types from the sample response.
5. Error Handling
5.1 Parameter Validation Error Example
{
"data": [],
"status": "ERROR",
"message": "time is required and must be true or false",
"errorCode": "INVALID_PARAMETER",
"field": "time",
"hasMore": false
}
5.2 Error Messages
| message | Meaning |
|---|---|
| Parameter validation message |
An invalid parameter was provided.
The response also includes errorCode
and field.
|
Incorrect API Key |
Company or API key verification failed. |
Employee Not Found |
No employee matches the query criteria. |
Internal Server Error |
An internal server error occurred. |
No records is not an error:
When no records match the query, the API returns
status SUCCESS with
data as an empty array, not an
ERROR response. Treat an empty array as a valid
result. Do not treat any ERROR response as an
empty result.
HTTP status vs. application status:
This handler does not define HTTP status codes.
Check both the HTTP response status and the
status field in the JSON response.
6. JavaScript Integration Examples
The following code is intended for a server-side JavaScript
environment with fetch support. It includes
single-page retrieval, automatic pagination, and a usage example.
API key security: Load the API key from a server-side environment variable or secret management service. Do not embed it in browser-side code. GET requests place the API key in the URL, so ensure that logs, proxies, and monitoring systems do not record the full request URL.
6.1 Retrieve a Single Page
const API_URL =
'https://api1.opentimeclock.com/Q3OTC/api';
const PAGE_SIZE = 1000;
const MAX_OFFSET = 100000;
// Retrieve one page.
// Return { records, hasMore }. records is [] when no records
// are found; hasMore is true when more pages remain.
// Throw an exception for application errors.
async function getTimecards({
companyId,
apiKey,
employeeFullName,
startDate,
endDate,
offset = 0,
time = true,
hours = true,
lunch = true,
// break is a reserved word, so it cannot be used as a binding name.
break: includeBreak = true,
ptoPending = true,
ptoApproved = true,
ptoDenied = true
}) {
if (typeof apiKey !== 'string' || apiKey.length === 0) {
throw new Error('Missing API key');
}
// All seven flags are required. At least one must be true.
const url = new URL(API_URL);
url.search = new URLSearchParams({
cmd: 'Q3/api/getTimecardsApi',
companyId: String(companyId),
apiKey,
employeeFullName,
startDate,
endDate,
time: String(time),
hours: String(hours),
lunch: String(lunch),
break: String(includeBreak),
ptoPending: String(ptoPending),
ptoApproved: String(ptoApproved),
ptoDenied: String(ptoDenied),
offset: String(offset)
}).toString();
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const result = await response.json();
if (
!result ||
typeof result !== 'object' ||
Array.isArray(result)
) {
throw new Error('Unexpected API response');
}
if (result.status === 'ERROR') {
const error = new Error(
result.message || 'API request failed'
);
error.code = result.errorCode;
error.field = result.field;
throw error;
}
if (
result.status !== 'SUCCESS' ||
!Array.isArray(result.data)
) {
throw new Error('Unexpected API response');
}
return {
records: result.data,
hasMore: result.hasMore === true
};
}
The break parameter:
break is a JavaScript reserved word. It is valid as
an object property and as a query string name, but it cannot be
used as a variable or destructured binding name. Rename it while
destructuring, as shown above, or read it as
params['break'].
6.2 Automatic Pagination
// Retrieve all records for the query, starting at offset 0.
async function getAllTimecards(params) {
const records = [];
for (
let offset = 0;
offset <= MAX_OFFSET;
offset += PAGE_SIZE
) {
const { records: page, hasMore } = await getTimecards({
...params,
offset
});
for (const record of page) {
records.push(record);
}
// Stop when the API reports that no more pages remain.
if (!hasMore) {
return records;
}
}
// hasMore is still true at the maximum offset, so more
// records remain. Do not silently return incomplete results.
throw new Error(
'Pagination limit reached; completeness cannot be ' +
'confirmed. Narrow the date range and query again.'
);
}
6.3 Usage Example
Save the functions above and the following code in a single
timecards.js file. Configure the
OPENTIMECLOCK_API_KEY environment variable
on the server before running the script.
Replace the company ID, employee name, and dates with actual values.
async function main() {
const records = await getAllTimecards({
companyId: 123456,
apiKey: process.env.OPENTIMECLOCK_API_KEY,
employeeFullName: 'John Smith',
time: true,
hours: true,
lunch: true,
break: true,
ptoPending: true,
ptoApproved: true,
ptoDenied: true,
startDate: '2026-08-01',
endDate: '2026-08-15'
});
console.log(`Retrieved ${records.length} records`);
// Process the records here:
// save to a database, generate a report, or export a file.
}
main().catch(error => {
// Do not log the request URL, API key, or raw request object.
console.error('Query failed:', error.message);
process.exitCode = 1;
});
6.4 Pagination Flow
Start with offset = 0
|
v
Request one page
|
v
Check HTTP status and JSON status
|
+-- Error: throw an exception
|
+-- Success: append records
|
v
hasMore false?
| (the last page may hold 0-1,000 records)
+-- Yes: return all collected records
|
+-- No: increase offset by 1,000
|
v
Exceeds maximum offset?
|
+-- No: request the next page
|
+-- Yes: require a narrower date range
These examples focus on the core retrieval logic. They do not include request timeouts, retries, rate-limit handling, persistence, or resumable retrieval. Add these features as required by your production environment.
7. Integration Checklist
-
Confirm that
companyIdandapiKeybelong to the same company. - Confirm that the employee name is correct. Deleted employees are excluded; archived (former) employees are included.
-
Use
YYYY-MM-DDdates. Ensure that the start date is not later than the end date and that the difference does not exceed 366 days. -
Send all seven entry type parameters in every request:
time,hours,lunch,break,ptoPending,ptoApproved, andptoDenied, usingtrueorfalse. Ensure that at least one of them istrue. -
Do not send unsupported parameters such as
limitorpageSize. -
Check both the HTTP status and the application
status. -
Distinguish between API key verification failure and employee
not found. Treat an empty
dataarray withstatusSUCCESSas a normal result with no timecard records, not as an error. -
Continue requesting pages while
hasMoreistrue. Do not assume the data is complete when the pagination limit is reached. -
Confirm field types, date/time formats, time zones,
and monetary units before going live. In particular, convert
updatedDateTimeandcreatedDateTimefrom UTC+0 to the time zone you need before displaying or exporting them. - Store API keys only on the server and redact sensitive information from logs.