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 from and to.
  • 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_amount is 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 from date 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=false to 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.