Reports API
Keito exposes project-level financial reports for integrations that need to reconcile budgets and uninvoiced work without downloading raw financial records, and time reports by client, project, or task for scripts that previously called Harvest’s time report endpoints.
The two financial report endpoints require a full-access API key bound to a user with the relevant Keito permissions. A key never grants more access than its user has:
| Bound user | Project budget report | Uninvoiced report | Project visibility |
|---|---|---|---|
| Owner or Administrator | Yes | Yes | All workspace projects |
| Manager | Requires View billable rates | Requires View billable rates and View invoices | Actively managed projects only |
| Other member | No | No | None |
| Personal read-only sync key | No | No | None |
Keito does not currently provide a workspace-wide read-only reporting credential. Use a full-access integration key created for an appropriately permissioned user, store it as a secret, and rotate or revoke it when the integration is retired.
Project Budget Report
GET /api/v2/reports/project_budget
This is the supported equivalent of Harvest’s GET /v2/reports/project_budget.
Request
curl "https://app.keito-test.com/api/v2/reports/project_budget?is_active=true&per_page=2000" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID"
| Parameter | Type | Required | Description |
|---|---|---|---|
is_active |
boolean | No | Return active or inactive projects only. |
page |
integer | No | Page number, starting at 1. |
per_page |
integer | No | Results per page, default and maximum 2000. |
Response
{
"results": [
{
"project_id": "clxproject123",
"project_name": "Implementation",
"client_id": "clxclient123",
"client_name": "Example Client",
"budget_is_monthly": false,
"budget_by": "project",
"is_active": true,
"budget": 20000,
"budget_spent": 5250,
"budget_remaining": 14750
}
],
"per_page": 2000,
"total_pages": 1,
"total_entries": 1,
"next_page": null,
"previous_page": null,
"page": 1,
"links": {
"first": "/api/v2/reports/project_budget?page=1&is_active=true&per_page=2000",
"next": null,
"previous": null,
"last": "/api/v2/reports/project_budget?page=1&is_active=true&per_page=2000"
}
}
budget, budget_spent, and budget_remaining use the unit selected by budget_by. For fee budgets, spend is rounded time multiplied by the historical effective project, task, or person rate for the entry’s work date. A later change to a person’s default rate does not reprice earlier work. Invoice discounts, payments, taxes, and retainer credits do not change budget consumption.
For hours budgets, the values are hours. For project-cost budgets, they are internal cost and are protected by the same elevated financial permissions.
Uninvoiced Report
GET /api/v2/reports/uninvoiced
This is the supported equivalent of Harvest’s GET /v2/reports/uninvoiced?from=...&to=....
Request
curl "https://app.keito-test.com/api/v2/reports/uninvoiced?from=20260101&to=20260331&per_page=2000" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID"
| Parameter | Type | Required | Description |
|---|---|---|---|
from |
string | Yes | Inclusive start date in YYYYMMDD or YYYY-MM-DD format. |
to |
string | Yes | Inclusive end date in YYYYMMDD or YYYY-MM-DD format. The range cannot exceed 365 days. |
include_fixed_fee |
boolean | No | Include fixed-fee projects and their remaining fee balance. Defaults to true. |
page |
integer | No | Page number, starting at 1. |
per_page |
integer | No | Projects per page, default and maximum 2000. |
Multi-month ranges are supported within the 365-day limit; the example above requests a full quarter. You do not need to split a supported range into separate monthly requests. A report always applies the same calculation rules and permission checks, regardless of the requested period or page size.
Pagination counts projects, not time entries or expenses. Follow links.next until it is null to retrieve every project in the report, keeping the same authorization and account headers. The response fields and financial calculations are the same on every page.
Response
{
"results": [
{
"client_id": "clxclient123",
"client_name": "Example Client",
"project_id": "clxproject123",
"project_name": "Implementation",
"currency": "GBP",
"total_hours": 42.5,
"uninvoiced_hours": 12.5,
"uninvoiced_expenses": 125,
"uninvoiced_amount": 1375
}
],
"per_page": 2000,
"total_pages": 1,
"total_entries": 1,
"next_page": null,
"previous_page": null,
"page": 1,
"links": {
"first": "/api/v2/reports/uninvoiced?page=1&from=20260101&to=20260331&per_page=2000",
"next": null,
"previous": null,
"last": "/api/v2/reports/uninvoiced?page=1&from=20260101&to=20260331&per_page=2000"
}
}
Calculation Rules
- Time and expenses are filtered by their work or spent date, inclusive of
fromandto. - Only billable, unbilled work is included. Running and rejected time is excluded.
- If the workspace allows documented work before approval, unsubmitted, submitted, and approved time and expenses are included. Otherwise, only approved work is included.
- Time uses the workspace rounding rule and the historical effective billing rate for the entry’s work date.
- Expenses contribute their recorded billable cost.
- For time-and-materials projects,
uninvoiced_amountis unbilled time at those historical rates plus eligible unbilled expenses. - For fixed-fee projects, the service amount is the project fee minus non-voided service invoice lines. It becomes eligible when the report end date reaches the project’s start date, or its creation date when no start date exists. The
fromdate does not split that outstanding fixed-fee balance. - Payments, invoice discounts, taxes, retainer funding, and retainer credits do not reduce the fixed-fee balance. Set
include_fixed_fee=falseto omit fixed-fee projects.
Retainer Balances
The uninvoiced report and connected AI app billing summary do not contain retainer balances. Use the dedicated Retainers API for authorized current-corrected ledger balances and optional as_of activity-date filtering. The raw workspace backup remains a separate export.
Time Reports
GET /api/v2/reports/time/projects
This is the supported equivalent of Harvest’s GET /v2/reports/time/projects. Two sibling endpoints follow the same contract:
| Keito endpoint | Harvest endpoint | Row grouping |
|---|---|---|
GET /api/v2/reports/time/clients |
GET /v2/reports/time/clients |
One row per client |
GET /api/v2/reports/time/projects |
GET /v2/reports/time/projects |
One row per project, with its client |
GET /api/v2/reports/time/tasks |
GET /v2/reports/time/tasks |
One row per task, with its share of the period’s hours |
These are the web Time Report’s Clients, Projects, and Tasks breakdowns over the wire, with the period’s totals embedded as summary. Hours use the workspace time-rounding rule and amounts use the historical effective billing rate for each entry’s work date, so a script and the browser agree. Time reports are available on every plan.
Any internal member can call them with a full-access API key. Who sees whose time follows the web report:
| Bound user | Scope of the report | Amounts and rates |
|---|---|---|
| Owner or Administrator | The whole workspace | Yes |
| Manager | Own time, plus actively managed projects and assigned teammates subject to the corresponding time-viewing permissions | Requires View billable rates |
| Accountant with workspace business-record access | The whole workspace | Follows the accountant’s financial access |
| Other member | Own time, plus any project-specific access described below | Requires View billable rates |
| External client member | Refused with 403 |
No |
| Personal read-only sync key or AI app token | Refused | No |
Active project assignments with project-specific financial or internal-cost viewing grants also allow the user to see that project’s time. This expands the time scope; amounts still follow the separate billable-rate permission.
Request
curl "https://app.keito-test.com/api/v2/reports/time/projects?from=20260907&to=20260913&per_page=2000" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID"
| Parameter | Type | Required | Description |
|---|---|---|---|
from |
string | Yes | Inclusive start date in YYYYMMDD or YYYY-MM-DD format. |
to |
string | Yes | Inclusive end date in YYYYMMDD or YYYY-MM-DD format. The range cannot exceed 365 days. |
user_id |
string | No | Limit the report to one person’s time. Use me or the key user’s own ID for their time. Another person’s ID requires an Owner or Administrator key, otherwise the request is refused with 403. |
client_id |
string | No | Limit the report to projects under one client. |
project_id |
string | No | Limit the report to one project. |
include_fixed_fee |
boolean | No | Price time on fixed-fee projects at billable rates. Defaults to false. Hours on fixed-fee projects always count towards total_hours and fixed_fee_hours. |
active_only |
boolean | No | Only time on active projects. Defaults to false. |
page |
integer | No | Page number, starting at 1. |
per_page |
integer | No | Rows per page, from 1 to 2000. Defaults to 100. |
Any other query parameter is refused with 400 and the message Unexpected query parameter: <name>. Scripts that pass Harvest-only options such as include_forecast must drop them.
Response
{
"results": [
{
"id": "clxproject123",
"name": "Implementation",
"client_id": "clxclient123",
"client_name": "Example Client",
"code": "IMPL",
"is_fixed_fee": false,
"total_hours": 3.25,
"billable_hours": 2.75,
"non_billable_hours": 0.5,
"fixed_fee_hours": 0,
"currency": "GBP",
"billable_amount": 275,
"rate": 100,
"billable_amounts": [{ "currency": "GBP", "amount": 275 }],
"rates": [{ "currency": "GBP", "rate": 100 }]
}
],
"summary": {
"total_hours": 3.25,
"billable_hours": 2.75,
"non_billable_hours": 0.5,
"fixed_fee_hours": 0,
"billable_percent": 84.62,
"amounts_visible": true,
"billable_amounts": [{ "currency": "GBP", "amount": 275 }],
"rounding": { "mode": "min_15", "description": "rounded to the nearest 15 minutes" }
},
"per_page": 2000,
"total_pages": 1,
"total_entries": 1,
"next_page": null,
"previous_page": null,
"page": 1,
"links": {
"first": "/api/v2/reports/time/projects?page=1&from=20260907&to=20260913&per_page=2000",
"next": null,
"previous": null,
"last": "/api/v2/reports/time/projects?page=1&from=20260907&to=20260913&per_page=2000"
}
}
Rows are ordered by name. Hours, amounts, rates, and percentages are decimals to two places. Project rows carry client_id, client_name, code, and is_fixed_fee; task rows carry percent_of_total instead. summary is computed over every row of the report before paging, so it is the same on every page.
currency, billable_amount, rate, billable_amounts, rates, and summary.billable_amounts are present only when the key’s user may view billable rates. Otherwise they are omitted, not zeroed, and summary.amounts_visible is false. A row whose billable time spans more than one currency has currency, billable_amount, and rate set to null with the per-currency split in billable_amounts and rates.
Differences from Harvest
Harvest scripts need these adjustments:
| Harvest | Keito | Note |
|---|---|---|
project_id, project_name (client_id, client_name, task_id, task_name) |
id, name |
Each report names its grouping column id and name. Project rows additionally keep client_id and client_name. |
per_page defaults to 2000 |
per_page defaults to 100 |
Pass per_page=2000 explicitly or follow links.next until it is null. The maximum is the same. |
from and to in YYYYMMDD |
YYYYMMDD or YYYY-MM-DD |
All three Keito endpoints require both dates and cap the range at 365 days. |
billable_amount absent without the rates permission |
Same, plus rate, billable_amounts, and rates are absent |
Check summary.amounts_visible rather than treating a missing amount as zero. |
include_forecast, scheduled_hours |
Not supported | Unknown parameters return 400. |
| No period totals | summary object |
Also adds non_billable_hours and fixed_fee_hours per row. |
| Pagination links are absolute URLs | Pagination links are paths | Resolve links.* against https://app.keito-test.com. |
Empty results with total_entries of 0 means no time matched the filters for that user’s scope. A 403 means the key, bound user, or user_id is not allowed, not that the report is empty.
Task or Category Hours
GET /api/v2/reports/time/tasks is the supported equivalent of Harvest’s GET /v2/reports/time/tasks and returns rounded hours per task for the period, with each task’s percent_of_total. It accepts the same parameters as the project report.
When you need the individual entries behind those totals, list time entries for the same date range and group rounded_hours by task_id (or by task.name from the embedded task object):
curl "https://app.keito-test.com/api/v2/time_entries?from=2026-01-01&to=2026-01-31&per_page=2000" \
-H "Authorization: Bearer $KEITO_API_KEY" \
-H "Keito-Account-Id: $KEITO_ACCOUNT_ID"
Time-entry responses expose rounded_hours, budgeted, is_billed, approval_status, and an authorized stored historical billable_rate. They do not expose a calculated billable_amount; use the financial report endpoints for invoice-eligible monetary totals.
Capability Detection
Project budget and uninvoiced responses include financial-reports-v1, and the time reports include time-reports-v1, in the X-Keito-Features header. Treat a 403 as a credential or bound-user permission failure, not as an empty report.
Calendar exclusion
The uninvoiced and team-time reports accept exclude_calendar=true to remove calendar-derived time, including entries retaining calendar provenance after a source change, or exclude_calendar=unshared to remove only calendar-derived time that has not been shared with AI connections. The same option is supported by GET /api/v2/projects/{projectId}/billing_summary and normal time-entry listing. Send the parameter once, with exactly true, false or unshared. unshared responses include a calendar_sharing object naming people whose calendar time in the filtered scope isn’t shared, without hours or counts. Connected MCP credentials always use at least unshared; passing false cannot expand them.
Team time report
GET /api/v2/reports/time/team
This report returns per-user time totals for the requested from/to period, within the caller’s reporting scope. Its source filter accepts web, cli, api, agent, calendar, desktop, mobile, integration, clickup, and extension. A source filter narrows records; it does not grant financial or team visibility. The team-report-source-mobile feature advertises mobile-source support where applicable. Use OpenAPI for the exact parameters and response schema; the Clients/Projects/Tasks report group above uses its own stricter parameter contract.
Project summaries
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/api/v2/projects/{projectId}/budget_summary |
Hours, budget and authorized financial summary for one project |
GET |
/api/v2/projects/{projectId}/billing_summary |
Invoice-eligible work and billing context for one project |
These summaries apply current project and financial permissions. Billing summary accepts a paired from/to work window and exclude_calendar; omitted or hidden commercial fields are not zero balances. Neither summary sends an invoice or moves money. Follow the exact OpenAPI schemas and the separately confirmed invoice workflow for writes.