{"openapi":"3.0.3","info":{"title":"WiseCost API","version":"1.0.0","description":"Read access to a WiseCost company's time activities, the reference data they point at (employees, classes, projects, items, timesheets, payroll periods), and the labor-distribution journal lines posted to the general ledger, for external reporting and data pipelines.\n\n## Authentication\n\nSend your API key as `Authorization: Bearer wc_live_…` (`x-api-key: <key>` is also accepted for older integrations). Keys are created in **Company Setup → API keys**; the secret is shown exactly once. Every key belongs to exactly one company: there is no way to name a company in a request, and every response is scoped to the key's company.\n\n## Scopes\n\nEach key carries the scopes it was created with. An endpoint's required scope is listed on it; a key without it gets `403 FORBIDDEN_SCOPE`. This API is read-only.\n\n| Scope | Grants |\n|---|---|\n| `time_activities:read` | Read time activities and their deletion feed |\n| `catalog:read` | Read reference data: employees, classes, projects, items, timesheets, payroll periods |\n| `labor_distribution:read` | Read the journal lines Labor Distribution posted to the general ledger |\n\n## Response envelope\n\nSuccess: `{ \"data\": …, \"meta\": { … } }`. Errors: `{ \"error\": { \"code\", \"message\", \"details\"? } }` with codes `UNAUTHORIZED` (401), `FORBIDDEN_SCOPE` (403), `VALIDATION_ERROR` (400, with per-parameter `details`), `RATE_LIMITED` (429), `RATE_LIMIT_UNAVAILABLE` (503), `INTERNAL_ERROR` (500). Query strings are strict: an unrecognised parameter is a 400, not ignored.\n\n## Pagination\n\nList endpoints are keyset-paginated. Follow `meta.nextCursor` until `meta.hasMore` is `false`; `limit` is 1-500 (default 100). **A cursor is bound to the `sort` it was issued under.** Send it back with the same `sort` (and the same filters), or you get `400 VALIDATION_ERROR`. A cursor is opaque; don't parse or construct one.\n\n## Rate limits\n\n120 requests per minute per key (sliding window). Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`; a 429 adds `Retry-After` (seconds).\n\n## Sync guide: incremental mirroring\n\nGoal: keep a warehouse copy of `/time-activities` current without full reloads. A `TimeActivity` has no status of its own (it inherits its timesheet's), and status transitions (submit, approve, reject, reopen, lock at labor-distribution posting, unlock) bump the row's `updatedAt`, so one watermark covers both edits and workflow changes.\n\n1. **Initial load.** `GET /time-activities?sort=updatedAt&limit=500`, follow `meta.nextCursor` to the end. Upsert by `id`. Note the wall-clock time you *started* as your watermark **W**.\n2. **Each sync: changes.** `GET /time-activities?updatedSince=W&limit=500`, page through with `cursor` (the sort is `updatedAt` automatically). Upsert by `id`. `updatedSince` is inclusive, so a row stamped exactly at W shows up again; that overlap is intended and harmless.\n3. **Each sync: deletions.** `GET /time-activities/deletions?since=W`, page through, and delete each `timeActivityId` locally. A deletion is recorded in the same database transaction as the delete, so it can't be missed or arrive early.\n4. **Advance W** to the time you started step 2, not to the end of the run, and not to the latest `updatedAt` you saw. Overlap is safe; a gap is not.\n\nThings that will surprise you if you don't know them:\n\n- **Synthetic rows churn on every allocate.** Employees tracked by a fixed percentage split (`DISTRIBUTED`) never log time; when Labor Distribution runs, WiseCost *generates* their entries (`isSynthetic: true`), and it **deletes and recreates them on every allocate run**, on reset, and on a failed posting's rollback. Each run therefore produces a deletion (reason `DISTRIBUTED_RESYNTH`, `DISTRIBUTED_RESET` or `ALLOCATE_ROLLBACK`) *and* a fresh row with a new `id`. Don't treat that as data loss. If you only want human-entered time, filter `isSynthetic=false`; if you keep synthetic rows, key them on (employee, date, costObjective) rather than `id` when you compare runs. Deletions with reason `USER_DELETE` are real: a person removed a draft entry.\n- **A cursor is bound to its sort.** A `nextCursor` from a `sort=date` page cannot continue a `sort=updatedAt` walk (400). Restart the walk instead.\n- **`class` vs `project`.** `costObjective` is what the entry actually charges. `meta.contractDimension` tells you how the company is set up: in `CLASS` mode `class` is the contract and `project` is null unless the row is a customer-only entry; in `PROJECT` mode `project` is the contract and `class` is an optional secondary tag. Read `costObjective` unless you specifically need the tag.\n- **Location is a third, independent dimension.** A time entry or journal line can carry a `location` (a QuickBooks Department) alongside its class or project; it never replaces either and is not part of `costObjective`. `meta.locationsEnabled` says whether the company uses locations at all; `/locations` lists them.\n- **Who keyed an entry.** `onBehalf` is the source of truth for delegated entry; `enteredBy` is a WiseCost user (not an employee) and `null` means unknown, never \"entered by the employee\". A timesheet's `hasDelegatedEntries` summarises this, and `approvedBy` says who approved it and in what capacity.\n- **Hours are decimal** (`1.5` = 1h30m, up to 4dp). No rates, costs or amounts are exposed on this API: not on time activities, employees, or items.\n- **Catalog endpoints sync the same way.** `/employees`, `/classes`, `/projects`, `/items`, `/timesheets` and `/payroll-periods` all accept `updatedSince` + `cursor` with the same semantics. They're small; a full reload on each sync is also fine. Reference rows are never hard-deleted (QuickBooks archives them; watch `active`), so there's no deletion feed for them.\n- **Journal lines and reversals.** `/journal-lines` returns one row per line of the entries Labor Distribution generated (`source=wisecost`, the default; `source=qbo` for entries synced from QuickBooks). Lines are immutable. **A reversal is a new entry with `journalEntry.reversalOfId` pointing at the original; the original is never modified or deleted.** Reopening a posted payroll period therefore shows up as a new entry whose lines mirror the original with debit and credit swapped, and a later re-posting as yet another new entry; net an entry against the entries that reverse it. Sync incrementally with `updatedSince` and `cursor` like everything else (an entry's `updatedAt` moves when it is posted). There is no deletion feed: journal lines are never deleted.\n- **`status` vocabulary:** `draft` (open), `pending` (submitted, awaiting review), `approved`, `rejected`, `locked` (posted to the general ledger). `posted` is separate: whether the entry itself was pushed to QuickBooks as a TimeActivity.","contact":{"name":"WiseCost","url":"https://www.usewisecost.com"}},"servers":[{"url":"https://app.usewisecost.com/api/v1"}],"tags":[{"name":"Time activities","description":"Row-level time entries and their deletion feed."},{"name":"Catalog","description":"Reference data the time entries point at: employees, classes, projects, items, locations, timesheets, payroll periods."},{"name":"Labor distribution","description":"Journal lines Labor Distribution posted to the general ledger, and on request the entries synced from QuickBooks."}],"paths":{"/time-activities":{"get":{"operationId":"listTimeActivities","summary":"List time activities","description":"Row-level time entries with a status derived from their timesheet and a resolved cost objective (class, project or indirect account). Newest-first by default; pass `updatedSince` (or `sort=updatedAt`) to walk changes oldest-first for incremental sync (see the Sync guide). No compensation data is included.\n\n**Required scope:** `time_activities:read`","tags":["Time activities"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["time_activities:read"],"parameters":[{"name":"dateFrom","in":"query","required":false,"description":"Inclusive lower bound on the activity date (YYYY-MM-DD).","schema":{"description":"Inclusive lower bound on the activity date (YYYY-MM-DD).","type":"string","format":"date"}},{"name":"dateTo","in":"query","required":false,"description":"Inclusive upper bound on the activity date (YYYY-MM-DD).","schema":{"description":"Inclusive upper bound on the activity date (YYYY-MM-DD).","type":"string","format":"date"}},{"name":"employeeId","in":"query","required":false,"description":"Employee id(s). Repeat the parameter or comma-separate. Repeat the parameter or comma-separate values.","schema":{"description":"Employee id(s). Repeat the parameter or comma-separate.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"projectId","in":"query","required":false,"description":"Project id(s). Resolved against the company's contract dimension; a class's billing-customer tag never matches. Repeat the parameter or comma-separate values.","schema":{"description":"Project id(s). Resolved against the company's contract dimension; a class's billing-customer tag never matches.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"classId","in":"query","required":false,"description":"Class id(s). In PROJECT mode this is the optional secondary class tag. Repeat the parameter or comma-separate values.","schema":{"description":"Class id(s). In PROJECT mode this is the optional secondary class tag.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"locationId","in":"query","required":false,"description":"Location id(s). Location is a dimension independent of class and project; see meta.locationsEnabled. Repeat the parameter or comma-separate values.","schema":{"description":"Location id(s). Location is a dimension independent of class and project; see meta.locationsEnabled.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"status","in":"query","required":false,"description":"Row-level status derived from the timesheet: draft (open), pending (submitted, in review), approved, rejected, locked (posted to the GL). Repeat the parameter or comma-separate values.","schema":{"description":"Row-level status derived from the timesheet: draft (open), pending (submitted, in review), approved, rejected, locked (posted to the GL).","minItems":1,"type":"array","items":{"type":"string","enum":["draft","pending","approved","rejected","locked"]}},"style":"form","explode":true},{"name":"posted","in":"query","required":false,"description":"Only activities posted (true) / not posted (false) to QuickBooks.","schema":{"description":"Only activities posted (true) / not posted (false) to QuickBooks.","type":"string","enum":["true","false"]}},{"name":"isSynthetic","in":"query","required":false,"description":"Only activities generated by Labor Distribution for DISTRIBUTED employees (true) or real entries (false).","schema":{"description":"Only activities generated by Labor Distribution for DISTRIBUTED employees (true) or real entries (false).","type":"string","enum":["true","false"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"sort","in":"query","required":false,"description":"date = newest first (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"date = newest first (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["date","updatedAt"]}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TimeActivity"}},"meta":{"$ref":"#/components/schemas/TimeActivitiesMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/time-activities/deletions":{"get":{"operationId":"listTimeActivityDeletions","summary":"List deleted time activities","description":"Append-only feed of time activities that were hard-deleted, oldest-first from `since`. A row you previously synced can only be retracted through this feed; it no longer appears in `/time-activities`. Each entry carries the deleted row's id and a `reason` (see the Sync guide for the synthetic-row churn this produces).\n\n**Required scope:** `time_activities:read`","tags":["Time activities"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["time_activities:read"],"parameters":[{"name":"since","in":"query","required":true,"description":"Inclusive: deletions at or after this instant. Use the updatedSince watermark of the matching time-activities sync.","schema":{"type":"string","format":"date-time","description":"Inclusive: deletions at or after this instant. Use the updatedSince watermark of the matching time-activities sync."}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"schema":{"default":100,"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TimeActivityDeletion"}},"meta":{"$ref":"#/components/schemas/ListMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/employees":{"get":{"operationId":"listEmployees","summary":"List employees","description":"Employees and 1099 contractors tracked as workers. Alphabetical by default. No compensation data.\n\n**Required scope:** `catalog:read`","tags":["Catalog"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["catalog:read"],"parameters":[{"name":"active","in":"query","required":false,"description":"Only active (true) or archived (false) employee records. Note: a terminated employee stays active in QuickBooks; see releasedDate.","schema":{"description":"Only active (true) or archived (false) employee records. Note: a terminated employee stays active in QuickBooks; see releasedDate.","type":"string","enum":["true","false"]}},{"name":"timeTrackingMethod","in":"query","required":false,"description":"TIME_ENTRIES = logs individual time; DISTRIBUTED = fixed percentage split, entries are generated by Labor Distribution. Repeat the parameter or comma-separate values.","schema":{"description":"TIME_ENTRIES = logs individual time; DISTRIBUTED = fixed percentage split, entries are generated by Labor Distribution.","minItems":1,"type":"array","items":{"type":"string","enum":["TIME_ENTRIES","DISTRIBUTED"]}},"style":"form","explode":true},{"name":"sort","in":"query","required":false,"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["name","updatedAt"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Employee"}},"meta":{"$ref":"#/components/schemas/CatalogMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/classes":{"get":{"operationId":"listClasses","summary":"List classes","description":"QuickBooks classes (the contract dimension for CLASS-mode companies; an optional secondary tag in PROJECT mode). Alphabetical by default.\n\n**Required scope:** `catalog:read`","tags":["Catalog"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["catalog:read"],"parameters":[{"name":"active","in":"query","required":false,"description":"Only active (true) or inactive (false) classes.","schema":{"description":"Only active (true) or inactive (false) classes.","type":"string","enum":["true","false"]}},{"name":"sort","in":"query","required":false,"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["name","updatedAt"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Class"}},"meta":{"$ref":"#/components/schemas/CatalogMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/projects":{"get":{"operationId":"listProjects","summary":"List projects","description":"QuickBooks Projects: sub-customers with IsProject = true (the contract dimension for PROJECT-mode companies). Plain customers are not exposed. Alphabetical by default.\n\n**Required scope:** `catalog:read`","tags":["Catalog"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["catalog:read"],"parameters":[{"name":"active","in":"query","required":false,"description":"Only active (true) or inactive (false) projects.","schema":{"description":"Only active (true) or inactive (false) projects.","type":"string","enum":["true","false"]}},{"name":"sort","in":"query","required":false,"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["name","updatedAt"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"meta":{"$ref":"#/components/schemas/CatalogMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/items":{"get":{"operationId":"listItems","summary":"List items","description":"QuickBooks service items a time entry can carry. Alphabetical by default. No prices.\n\n**Required scope:** `catalog:read`","tags":["Catalog"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["catalog:read"],"parameters":[{"name":"active","in":"query","required":false,"description":"Only active (true) or inactive (false) items.","schema":{"description":"Only active (true) or inactive (false) items.","type":"string","enum":["true","false"]}},{"name":"sort","in":"query","required":false,"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["name","updatedAt"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Item"}},"meta":{"$ref":"#/components/schemas/CatalogMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/locations":{"get":{"operationId":"listLocations","summary":"List locations","description":"QuickBooks Departments, called Locations in WiseCost: a dimension independent of class and project that a time entry or journal line can carry. Alphabetical by default. Check meta.locationsEnabled: a company that does not use locations has none tagged.\n\n**Required scope:** `catalog:read`","tags":["Catalog"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["catalog:read"],"parameters":[{"name":"active","in":"query","required":false,"description":"Only active (true) or inactive (false) locations.","schema":{"description":"Only active (true) or inactive (false) locations.","type":"string","enum":["true","false"]}},{"name":"sort","in":"query","required":false,"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"name = alphabetical (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["name","updatedAt"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Location"}},"meta":{"$ref":"#/components/schemas/LocationsMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/timesheets":{"get":{"operationId":"listTimesheets","summary":"List timesheets","description":"One timesheet per employee × payroll period, with the workflow status that time activities inherit. Newest period first by default; `dateFrom`/`dateTo` match periods that overlap the range.\n\n**Required scope:** `catalog:read`","tags":["Catalog"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["catalog:read"],"parameters":[{"name":"employeeId","in":"query","required":false,"description":"Employee id(s). Repeat the parameter or comma-separate values.","schema":{"description":"Employee id(s).","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"payrollPeriodId","in":"query","required":false,"description":"Payroll period id(s). Repeat the parameter or comma-separate values.","schema":{"description":"Payroll period id(s).","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"status","in":"query","required":false,"description":"Timesheet workflow status. LOCKED = its period was posted to the general ledger. Repeat the parameter or comma-separate values.","schema":{"description":"Timesheet workflow status. LOCKED = its period was posted to the general ledger.","minItems":1,"type":"array","items":{"type":"string","enum":["OPEN","IN_REVIEW","APPROVED","REJECTED","LOCKED"]}},"style":"form","explode":true},{"name":"dateFrom","in":"query","required":false,"description":"Timesheets whose period overlaps [dateFrom, dateTo] (YYYY-MM-DD).","schema":{"description":"Timesheets whose period overlaps [dateFrom, dateTo] (YYYY-MM-DD).","type":"string","format":"date"}},{"name":"dateTo","in":"query","required":false,"schema":{"type":"string","format":"date"}},{"name":"sort","in":"query","required":false,"description":"startDate = newest period first (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"startDate = newest period first (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["startDate","updatedAt"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Timesheet"}},"meta":{"$ref":"#/components/schemas/CatalogMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/payroll-periods":{"get":{"operationId":"listPayrollPeriods","summary":"List payroll periods","description":"Payroll periods per frequency type, with their posting status. Newest first by default; `dateFrom`/`dateTo` match periods that overlap the range.\n\n**Required scope:** `catalog:read`","tags":["Catalog"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["catalog:read"],"parameters":[{"name":"status","in":"query","required":false,"description":"OPEN, LOCKED, POSTED (labor distribution posted to the GL), RE_OPENED. ALLOCATED is legacy. Repeat the parameter or comma-separate values.","schema":{"description":"OPEN, LOCKED, POSTED (labor distribution posted to the GL), RE_OPENED. ALLOCATED is legacy.","minItems":1,"type":"array","items":{"type":"string","enum":["OPEN","LOCKED","ALLOCATED","POSTED","RE_OPENED"]}},"style":"form","explode":true},{"name":"payrollFrequencyTypeId","in":"query","required":false,"description":"Payroll frequency type id(s); a company can run more than one schedule. Repeat the parameter or comma-separate values.","schema":{"description":"Payroll frequency type id(s); a company can run more than one schedule.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"dateFrom","in":"query","required":false,"description":"Periods overlapping [dateFrom, dateTo] (YYYY-MM-DD).","schema":{"description":"Periods overlapping [dateFrom, dateTo] (YYYY-MM-DD).","type":"string","format":"date"}},{"name":"dateTo","in":"query","required":false,"schema":{"type":"string","format":"date"}},{"name":"sort","in":"query","required":false,"description":"startDate = newest period first (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"startDate = newest period first (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["startDate","updatedAt"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: rows whose updatedAt is at or after this instant. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PayrollPeriod"}},"meta":{"$ref":"#/components/schemas/CatalogMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}},"/journal-lines":{"get":{"operationId":"listJournalLines","summary":"List journal lines","description":"One row per journal line, with the entry's fields repeated on each row. By default only the entries Labor Distribution generated (`source=wisecost`); `source=qbo` returns the entries synced from QuickBooks instead. Newest posting date first; pass `updatedSince` (or `sort=updatedAt`) to walk changes oldest-first. A reversal is a separate entry with `journalEntry.reversalOfId` set (see the Sync guide). The only amount on a row is the line's own; no rates, hours or other compensation data.\n\n**Required scope:** `labor_distribution:read`","tags":["Labor distribution"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"x-required-scopes":["labor_distribution:read"],"parameters":[{"name":"dateFrom","in":"query","required":false,"description":"Inclusive lower bound on the entry's posting date (YYYY-MM-DD).","schema":{"description":"Inclusive lower bound on the entry's posting date (YYYY-MM-DD).","type":"string","format":"date"}},{"name":"dateTo","in":"query","required":false,"description":"Inclusive upper bound on the entry's posting date (YYYY-MM-DD).","schema":{"description":"Inclusive upper bound on the entry's posting date (YYYY-MM-DD).","type":"string","format":"date"}},{"name":"payrollPeriodId","in":"query","required":false,"description":"Payroll period id(s) of the entry. Entries synced from QuickBooks have no payroll period and never match. Repeat the parameter or comma-separate values.","schema":{"description":"Payroll period id(s) of the entry. Entries synced from QuickBooks have no payroll period and never match.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"employeeId","in":"query","required":false,"description":"Employee id(s) on the line. Balancing and credit lines carry no employee. Repeat the parameter or comma-separate values.","schema":{"description":"Employee id(s) on the line. Balancing and credit lines carry no employee.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"projectId","in":"query","required":false,"description":"Project id(s). Resolved against the company's contract dimension, the same way as time activities. Repeat the parameter or comma-separate values.","schema":{"description":"Project id(s). Resolved against the company's contract dimension, the same way as time activities.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"classId","in":"query","required":false,"description":"Class id(s) on the line. In PROJECT mode this is the optional secondary class tag. Repeat the parameter or comma-separate values.","schema":{"description":"Class id(s) on the line. In PROJECT mode this is the optional secondary class tag.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"locationId","in":"query","required":false,"description":"Location id(s) on the line. Location is a dimension independent of class and project; see meta.locationsEnabled. Repeat the parameter or comma-separate values.","schema":{"description":"Location id(s) on the line. Location is a dimension independent of class and project; see meta.locationsEnabled.","minItems":1,"maxItems":100,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"style":"form","explode":true},{"name":"source","in":"query","required":false,"description":"wisecost = entries generated by Labor Distribution (default); qbo = entries synced from QuickBooks.","schema":{"default":"wisecost","description":"wisecost = entries generated by Labor Distribution (default); qbo = entries synced from QuickBooks.","type":"string","enum":["wisecost","qbo"]}},{"name":"updatedSince","in":"query","required":false,"description":"Inclusive: lines whose entry was updated at or after this instant. An entry's updatedAt moves when it is posted. Implies sort=updatedAt unless sort is given.","schema":{"description":"Inclusive: lines whose entry was updated at or after this instant. An entry's updatedAt moves when it is posted. Implies sort=updatedAt unless sort is given.","type":"string","format":"date-time"}},{"name":"sort","in":"query","required":false,"description":"postingDate = newest entry first (default); updatedAt = oldest change first, for incremental sync.","schema":{"description":"postingDate = newest entry first (default); updatedAt = oldest change first, for incremental sync.","type":"string","enum":["postingDate","updatedAt"]}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","schema":{"description":"Opaque cursor from a previous response's meta.nextCursor. Must be used with the same filters and sort.","type":"string","minLength":1,"maxLength":2000}},{"name":"limit","in":"query","required":false,"description":"Page size, 1-500. Default 100.","schema":{"default":100,"description":"Page size, 1-500. Default 100.","type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."}},"content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/JournalLine"}},"meta":{"$ref":"#/components/schemas/JournalLinesMeta"}}}}}},"400":{"description":"Invalid query (unknown parameter, bad value, or a cursor issued for a different sort).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"VALIDATION_ERROR","message":"Invalid query parameters."}}}}},"401":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"UNAUTHORIZED","message":"Invalid API key."}}}}},"403":{"description":"The key lacks a required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"FORBIDDEN_SCOPE","message":"This API key is missing the required scope(s): time_activities:read"}}}}},"429":{"description":"Rate limit exceeded.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Requests allowed per minute for this key."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Requests left in the current window."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time (seconds) at which the window resets."},"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"RATE_LIMITED","message":"Rate limit exceeded. Retry after the window resets."}}}}},"500":{"description":"Unexpected server error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"},"example":{"error":{"code":"INTERNAL_ERROR","message":"Something went wrong on our side. Please retry."}}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"wc_live_… API key","description":"Preferred. `Authorization: Bearer <api key>`."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"Accepted for older integrations."}},"schemas":{"TimeActivity":{"type":"object","properties":{"id":{"type":"string"},"date":{"type":"string","format":"date"},"hours":{"type":"number","description":"Decimal hours, 4dp."},"description":{"nullable":true,"type":"string"},"employee":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"costObjective":{"type":"object","properties":{"type":{"type":"string","enum":["class","project","indirect"],"description":"What the entry charges: a class (CLASS mode contract), a project (PROJECT mode contract, or a customer-only row), or an indirect/internal account."},"id":{"type":"string"},"name":{"type":"string"}},"required":["type","id","name"],"additionalProperties":false},"class":{"nullable":true,"description":"The class tag on the row: the contract itself in CLASS mode, the optional secondary class in PROJECT mode.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"project":{"nullable":true,"description":"Set only when costObjective.type is \"project\".","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"item":{"nullable":true,"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"location":{"nullable":true,"description":"Location (QuickBooks Department) the entry is tagged with. Independent of costObjective: a class or project entry can also carry a location, and a location never replaces either. Null when the company does not use locations (meta.locationsEnabled) or the entry has none.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"status":{"type":"string","enum":["draft","pending","approved","rejected","locked"]},"posted":{"type":"boolean","description":"Posted to QuickBooks as a TimeActivity."},"allocated":{"type":"boolean","description":"Included in a Labor Distribution run."},"isSynthetic":{"type":"boolean","description":"Generated by Labor Distribution for a DISTRIBUTED employee. These are deleted and recreated on every run, so expect delete+create churn in the deletions feed."},"billable":{"type":"boolean"},"qboId":{"nullable":true,"type":"string"},"onBehalf":{"type":"boolean","description":"True when somebody other than the employee keyed this entry. This flag is the source of truth: do not derive it by comparing enteredBy with the employee (an employee without a linked user account has no id to compare against)."},"enteredBy":{"nullable":true,"description":"The WiseCost USER who keyed the entry (a user id, not an employee id). Null means unknown: nobody recorded who keyed entries created before this was tracked, self-entered and delegated alike. Never read null as \"entered by the employee\".","type":"object","properties":{"id":{"type":"string"},"name":{"nullable":true,"type":"string"}},"required":["id","name"],"additionalProperties":false},"delegateReason":{"nullable":true,"description":"Why the entry was keyed on the employee's behalf: ABSENCE (the employee was absent), NO_CONNECTIVITY (the employee had no access to the app, including on-site work) or OTHER (see delegateReasonNote). Null when onBehalf is false. NO_CONNECTIVITY was named FIELD_WORK until 2026-09-18; existing entries were relabeled in place without changing their updatedAt, so a copy synced before that date may still hold FIELD_WORK for the same reason.","type":"string","enum":["ABSENCE","NO_CONNECTIVITY","OTHER"]},"delegateReasonNote":{"nullable":true,"description":"Free-text explanation. Required when delegateReason is OTHER, optional for the other reasons.","type":"string"},"timesheet":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["OPEN","IN_REVIEW","APPROVED","REJECTED","LOCKED"]},"employeeCertified":{"type":"boolean","description":"True only when the owning employee submitted (certified) the timesheet themselves."},"payrollPeriod":{"type":"object","properties":{"id":{"type":"string"},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["id","startDate","endDate"],"additionalProperties":false}},"required":["id","status","employeeCertified","payrollPeriod"],"additionalProperties":false},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time","description":"Also bumped when the parent timesheet changes status; use it as the sync watermark."}},"required":["id","date","hours","description","employee","costObjective","class","project","item","location","status","posted","allocated","isSynthetic","billable","qboId","onBehalf","enteredBy","delegateReason","delegateReasonNote","timesheet","createdAt","updatedAt"],"additionalProperties":false},"TimeActivityDeletion":{"type":"object","properties":{"id":{"type":"string"},"timeActivityId":{"type":"string","description":"Id of the row that no longer exists."},"employeeId":{"type":"string"},"timesheetId":{"type":"string"},"date":{"type":"string","format":"date"},"hours":{"type":"number"},"isSynthetic":{"type":"boolean"},"reason":{"type":"string","enum":["USER_DELETE","DISTRIBUTED_RESYNTH","DISTRIBUTED_RESET","ALLOCATE_ROLLBACK"]},"deletedAt":{"type":"string","format":"date-time"}},"required":["id","timeActivityId","employeeId","timesheetId","date","hours","isSynthetic","reason","deletedAt"],"additionalProperties":false},"Employee":{"type":"object","properties":{"id":{"type":"string"},"qboId":{"type":"string","description":"QuickBooks Employee.Id (or Vendor.Id for a 1099 contractor)."},"name":{"type":"string"},"email":{"nullable":true,"type":"string"},"active":{"type":"boolean","description":"Mirrors QuickBooks Active: false means the record is archived, NOT that the person left."},"isContractor":{"type":"boolean","description":"True for a 1099 vendor tracked as a worker."},"releasedDate":{"nullable":true,"description":"Termination date from QuickBooks, when set. Independent of active.","type":"string","format":"date"},"timeTrackingMethod":{"type":"string","enum":["TIME_ENTRIES","DISTRIBUTED"]},"payrollFrequencyTypeId":{"nullable":true,"type":"string"},"approver":{"nullable":true,"description":"The employee named to approve this employee's timesheets (an employee id), if any.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"backupApprover":{"nullable":true,"description":"The backup approver, if any.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"expectedHoursPerWeek":{"nullable":true,"description":"Expected effort per week, used for total effort reporting. Hours, not compensation.","type":"number"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","qboId","name","email","active","isContractor","releasedDate","timeTrackingMethod","payrollFrequencyTypeId","approver","backupApprover","expectedHoursPerWeek","createdAt","updatedAt"],"additionalProperties":false},"Class":{"type":"object","properties":{"id":{"type":"string"},"qboId":{"type":"string","description":"QuickBooks Class.Id. Prefixed \"LOCAL-\" for a class that exists only in WiseCost (see isLocal)."},"name":{"type":"string"},"fullyQualifiedName":{"nullable":true,"description":"Parent > child path, as in QuickBooks.","type":"string"},"active":{"type":"boolean"},"subClass":{"type":"boolean"},"parentQboId":{"nullable":true,"type":"string"},"billable":{"type":"boolean"},"billingCustomer":{"nullable":true,"description":"The customer this class is billed to, when tagged. Not a contract; see the time-activities costObjective.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"periodStart":{"nullable":true,"description":"Period of performance start; null = no restriction.","type":"string","format":"date"},"periodEnd":{"nullable":true,"type":"string","format":"date"},"isLocal":{"type":"boolean","description":"True when the class exists only in WiseCost (QuickBooks Essentials companies without the Classes feature)."},"budgetHours":{"nullable":true,"description":"Award budget ceiling in hours, as a decimal string; null when none is set.","type":"string","pattern":"^\\d+\\.\\d{2}$"},"budgetDollars":{"nullable":true,"description":"Award budget ceiling in dollars, as a decimal string; null when none is set. This is the contract ceiling, not compensation.","type":"string","pattern":"^\\d+\\.\\d{2}$"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","qboId","name","fullyQualifiedName","active","subClass","parentQboId","billable","billingCustomer","periodStart","periodEnd","isLocal","budgetHours","budgetDollars","createdAt","updatedAt"],"additionalProperties":false},"Project":{"type":"object","properties":{"id":{"type":"string"},"qboId":{"type":"string","description":"QuickBooks Customer.Id of the project (a sub-customer with IsProject = true)."},"name":{"type":"string"},"active":{"type":"boolean"},"periodStart":{"nullable":true,"description":"Period of performance start; null = no restriction.","type":"string","format":"date"},"periodEnd":{"nullable":true,"type":"string","format":"date"},"budgetHours":{"nullable":true,"description":"Award budget ceiling in hours, as a decimal string; null when none is set.","type":"string","pattern":"^\\d+\\.\\d{2}$"},"budgetDollars":{"nullable":true,"description":"Award budget ceiling in dollars, as a decimal string; null when none is set. This is the contract ceiling, not compensation.","type":"string","pattern":"^\\d+\\.\\d{2}$"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","qboId","name","active","periodStart","periodEnd","budgetHours","budgetDollars","createdAt","updatedAt"],"additionalProperties":false},"Item":{"type":"object","properties":{"id":{"type":"string"},"qboId":{"type":"string","description":"QuickBooks Item.Id."},"name":{"type":"string"},"fullyQualifiedName":{"nullable":true,"type":"string"},"type":{"type":"string","description":"QuickBooks item type, e.g. Service, NonInventory."},"active":{"type":"boolean"},"subItem":{"type":"boolean"},"parentQboId":{"nullable":true,"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","qboId","name","fullyQualifiedName","type","active","subItem","parentQboId","createdAt","updatedAt"],"additionalProperties":false},"Location":{"type":"object","properties":{"id":{"type":"string"},"qboId":{"type":"string","description":"QuickBooks Department.Id."},"name":{"type":"string"},"fullyQualifiedName":{"nullable":true,"description":"Parent:child path, as in QuickBooks.","type":"string"},"active":{"type":"boolean"},"subLocation":{"type":"boolean"},"parentQboId":{"nullable":true,"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","qboId","name","fullyQualifiedName","active","subLocation","parentQboId","createdAt","updatedAt"],"additionalProperties":false},"Timesheet":{"type":"object","properties":{"id":{"type":"string"},"employeeId":{"type":"string"},"payrollPeriodId":{"type":"string"},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"status":{"type":"string","enum":["OPEN","IN_REVIEW","APPROVED","REJECTED","LOCKED"]},"submittedAt":{"nullable":true,"type":"string","format":"date-time"},"reviewedAt":{"nullable":true,"type":"string","format":"date-time"},"employeeCertified":{"type":"boolean","description":"True only when the owning employee submitted (certified) the timesheet themselves."},"approvedWithoutCertification":{"type":"boolean","description":"True when an admin approved an uncertified draft (pre-DCAA mode). Never treat such a timesheet as employee-certified."},"hasDelegatedEntries":{"type":"boolean","description":"True when at least one entry on this timesheet was keyed on the employee's behalf (see time activities onBehalf)."},"overExpectedHours":{"nullable":true,"description":"Hours above the employee's expected effort for the period, recorded at submit when the total came in ABOVE; null otherwise. Total effort reporting, 2 CFR 200.430.","type":"number"},"approvedBy":{"nullable":true,"description":"The approval that currently stands, from the latest APPROVED review. Null unless the timesheet is APPROVED or LOCKED: a reopened timesheet's earlier approval no longer applies.","type":"object","properties":{"userId":{"type":"string","description":"The WiseCost user who approved (a user id, not an employee id)."},"name":{"nullable":true,"type":"string"},"capacity":{"nullable":true,"description":"In what capacity: the named approver, the backup approver, or an administrator overriding. Null on approvals recorded before capacities were tracked.","type":"string","enum":["APPROVER","BACKUP_APPROVER","ADMIN_OVERRIDE"]}},"required":["userId","name","capacity"],"additionalProperties":false},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","employeeId","payrollPeriodId","startDate","endDate","status","submittedAt","reviewedAt","employeeCertified","approvedWithoutCertification","hasDelegatedEntries","overExpectedHours","approvedBy","createdAt","updatedAt"],"additionalProperties":false},"PayrollPeriod":{"type":"object","properties":{"id":{"type":"string"},"payrollFrequencyType":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"frequency":{"type":"string","enum":["WEEKLY","BIWEEKLY","SEMIMONTHLY","MONTHLY"]}},"required":["id","label","frequency"],"additionalProperties":false},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"},"status":{"type":"string","enum":["OPEN","LOCKED","ALLOCATED","POSTED","RE_OPENED"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","payrollFrequencyType","startDate","endDate","status","createdAt","updatedAt"],"additionalProperties":false},"ListMeta":{"type":"object","properties":{"nextCursor":{"nullable":true,"type":"string"},"hasMore":{"type":"boolean"},"limit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["nextCursor","hasMore","limit"],"additionalProperties":false},"TimeActivitiesMeta":{"type":"object","properties":{"nextCursor":{"nullable":true,"type":"string"},"hasMore":{"type":"boolean"},"limit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sort":{"type":"string","enum":["date","updatedAt"]},"contractDimension":{"type":"string","enum":["CLASS","PROJECT"],"description":"The company's contract dimension, i.e. how to read class vs project on each row."},"locationsEnabled":{"type":"boolean","description":"Whether the company uses the Location dimension. When false, location is null on every row."}},"required":["nextCursor","hasMore","limit","sort","contractDimension","locationsEnabled"],"additionalProperties":false},"CatalogMeta":{"type":"object","properties":{"nextCursor":{"nullable":true,"type":"string"},"hasMore":{"type":"boolean"},"limit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sort":{"type":"string","enum":["name","startDate","updatedAt"]}},"required":["nextCursor","hasMore","limit","sort"],"additionalProperties":false},"LocationsMeta":{"type":"object","properties":{"nextCursor":{"nullable":true,"type":"string"},"hasMore":{"type":"boolean"},"limit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sort":{"type":"string","enum":["name","startDate","updatedAt"]},"locationsEnabled":{"type":"boolean","description":"Whether the company uses the Location dimension."}},"required":["nextCursor","hasMore","limit","sort","locationsEnabled"],"additionalProperties":false},"JournalLine":{"type":"object","properties":{"id":{"type":"string"},"journalEntry":{"type":"object","properties":{"id":{"type":"string"},"qboId":{"nullable":true,"description":"QuickBooks JournalEntry.Id once the entry is posted.","type":"string"},"postingDate":{"type":"string","format":"date"},"source":{"type":"string","enum":["wisecost","qbo"]},"reversalOfId":{"nullable":true,"description":"Set when this entry reverses another one. A reversal is always a new entry; the original is never modified.","type":"string"},"postedAt":{"nullable":true,"type":"string","format":"date-time"}},"required":["id","qboId","postingDate","source","reversalOfId","postedAt"],"additionalProperties":false},"payrollPeriod":{"nullable":true,"description":"Null for entries synced from QuickBooks.","type":"object","properties":{"id":{"type":"string"},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["id","startDate","endDate"],"additionalProperties":false},"employee":{"nullable":true,"description":"Null on balancing and credit lines.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"costObjective":{"nullable":true,"description":"What the line charges. Null when the line carries no class, project or indirect account (balancing and credit lines).","type":"object","properties":{"type":{"type":"string","enum":["class","project","indirect"],"description":"What the entry charges: a class (CLASS mode contract), a project (PROJECT mode contract, or a customer-only row), or an indirect/internal account."},"id":{"type":"string"},"name":{"type":"string"}},"required":["type","id","name"],"additionalProperties":false},"location":{"nullable":true,"description":"Location (QuickBooks Department) on the line. Independent of costObjective; null when the company does not use locations (meta.locationsEnabled) or the line has none.","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"additionalProperties":false},"account":{"type":"object","properties":{"id":{"type":"string"},"qboId":{"type":"string"},"name":{"type":"string"},"number":{"nullable":true,"type":"string"}},"required":["id","qboId","name","number"],"additionalProperties":false},"side":{"type":"string","enum":["debit","credit"]},"amount":{"type":"string","pattern":"^-?\\d+\\.\\d{2}$","description":"The line amount as a decimal string with two places. Positive; the direction is `side`."},"kind":{"nullable":true,"description":"labor = a debit backed by time; adjustment = a debit with no time behind it (employer taxes and benefits, period adjustments); credit = the balancing credit or an adjustment's own credit line. Null on lines of a reversal entry and of entries synced from QuickBooks, where the model cannot tell.","type":"string","enum":["labor","adjustment","credit"]},"timeActivityIds":{"type":"array","items":{"type":"string"},"description":"The time activities this line was built from. Empty for adjustment, credit, reversal and QuickBooks-synced lines."},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time","description":"The parent entry's updatedAt (lines themselves are immutable): the sync watermark."}},"required":["id","journalEntry","payrollPeriod","employee","costObjective","location","account","side","amount","kind","timeActivityIds","createdAt","updatedAt"],"additionalProperties":false},"JournalLinesMeta":{"type":"object","properties":{"nextCursor":{"nullable":true,"type":"string"},"hasMore":{"type":"boolean"},"limit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sort":{"type":"string","enum":["postingDate","updatedAt"]},"contractDimension":{"type":"string","enum":["CLASS","PROJECT"],"description":"The company's contract dimension, i.e. how to read costObjective on each row."},"locationsEnabled":{"type":"boolean","description":"Whether the company uses the Location dimension. When false, location is null on every row."}},"required":["nextCursor","hasMore","limit","sort","contractDimension","locationsEnabled"],"additionalProperties":false},"ApiError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["UNAUTHORIZED","FORBIDDEN_SCOPE","VALIDATION_ERROR","RATE_LIMITED","RATE_LIMIT_UNAVAILABLE","INTERNAL_ERROR"]},"message":{"type":"string"},"details":{"description":"Present on VALIDATION_ERROR only.","type":"array","items":{"type":"object","properties":{"path":{"type":"string","description":"Dotted path of the offending query parameter; empty for object-level issues."},"message":{"type":"string"}},"required":["path","message"],"additionalProperties":false}}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}},"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}]}