Print settings: A4 paper · Disable browser headers and footers

API INTEGRATION GUIDE

Timecard Query API · Open Time Clock · Product Q3

Endpoint details, request parameters, response structures, and JavaScript integration examples

OpenTimeClock JSON GET / POST Server-Side 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

3.2 Pagination and Sorting

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

  1. Confirm that companyId and apiKey belong to the same company.
  2. Confirm that the employee name is correct. Deleted employees are excluded; archived (former) employees are included.
  3. Use YYYY-MM-DD dates. Ensure that the start date is not later than the end date and that the difference does not exceed 366 days.
  4. Send all seven entry type parameters in every request: time, hours, lunch, break, ptoPending, ptoApproved, and ptoDenied, using true or false. Ensure that at least one of them is true.
  5. Do not send unsupported parameters such as limit or pageSize.
  6. Check both the HTTP status and the application status.
  7. Distinguish between API key verification failure and employee not found. Treat an empty data array with status SUCCESS as a normal result with no timecard records, not as an error.
  8. Continue requesting pages while hasMore is true. Do not assume the data is complete when the pagination limit is reached.
  9. Confirm field types, date/time formats, time zones, and monetary units before going live. In particular, convert updatedDateTime and createdDateTime from UTC+0 to the time zone you need before displaying or exporting them.
  10. Store API keys only on the server and redact sensitive information from logs.