Invoices API

All examples use:

https://app.keito.ai/api/v2

Add Authorization: Bearer <kto_...> and Keito-Account-Id: <company_id> to every API key request.

Creating, updating and sending invoices requires manager or administrator permissions. Deleting requires an administrator, and only draft invoices can be deleted. API keys inherit the permissions of the member who created them.

Create an Invoice from Tracked Time

POST /api/v2/invoices

The most common billing workflow: turn a period’s billable time and expenses into a draft invoice in a single call. Pass line_items_import and Keito builds the line items for you, applying each project’s billing rates, your workspace’s time-rounding rules, and your approval policy.

Only billable, not-yet-invoiced work is included. Everything imported is marked as billed, so the same hours cannot be invoiced twice.

This mirrors Harvest’s line_items_import, so an existing Harvest integration ports across with minimal changes.

Request Body

Field Type Required Description
client_id string Yes Client ID
line_items_import object Yes Import specification, described below
number string No Custom invoice number. Must be unique in the workspace.
subject string No Invoice subject
issue_date string No YYYY-MM-DD, defaults to today
due_date string No YYYY-MM-DD, defaults to 30 days after the issue date
payment_term string No upon_receipt, net_15, net_30, net_45, net_60, or custom
currency string No Three-letter code, defaults to the workspace currency
purchase_order string No PO number
tax number No Percentage applied to the subtotal
tax2 number No Second tax percentage
discount number No Discount percentage
notes string No Notes shown on the invoice

line_items_import

Field Type Required Description
project_ids string[] No Restrict the import to these projects. Omit for all of the client’s projects.
time object No Import tracked time
expenses object No Import expenses

At least one of time or expenses is required.

Each of time and expenses accepts:

Field Type Required Description
summary_type string Yes How the work is grouped into line items
from string No YYYY-MM-DD. Defaults to the earliest uninvoiced record.
to string No YYYY-MM-DD. Defaults to the latest uninvoiced record.

summary_type for time is project, task, people, or detailed. For expenses it is project, category, people, or detailed. detailed creates one line item per record.

Omit from and to to invoice all outstanding work regardless of date.

Example Request

curl -X POST https://app.keito.ai/api/v2/invoices \
  -H "Authorization: Bearer kto_xxxxx" \
  -H "Keito-Account-Id: your_company_id" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "client_id_here",
    "number": "MS-2026-08",
    "subject": "August 2026 services",
    "issue_date": "2026-08-22",
    "due_date": "2026-09-21",
    "line_items_import": {
      "project_ids": ["project_id_here"],
      "time": {
        "summary_type": "task",
        "from": "2026-08-01",
        "to": "2026-08-21"
      },
      "expenses": {
        "summary_type": "category",
        "from": "2026-08-01",
        "to": "2026-08-21"
      }
    }
  }'

The response is the created invoice, including the generated line_items and their IDs. Keep those IDs when the integration needs to clean up the client-facing descriptions or rates before sending:

{
  "id": "invoice_id_here",
  "number": "MS-2026-08",
  "state": "draft",
  "line_items": [
    {
      "id": "line_item_id_here",
      "kind": "SERVICE",
      "description": "Project: Task (01/08/2026 - 21/08/2026)",
      "quantity": 8,
      "unit_price": 125,
      "amount": 1000
    }
  ]
}

If the import fails, no invoice is left behind — Keito removes the draft before returning the error.

Create an Invoice with Explicit Line Items

POST /api/v2/invoices

To calculate amounts yourself, pass line_items instead. line_items and line_items_import are mutually exclusive; sending both returns 400.

Line Item Fields

Field Type Required Description
kind string No Item category, defaults to Service
description string No Line description
quantity number No Defaults to 1. Must be non-negative.
unit_price number No Defaults to 0. Must be non-negative.
effect string No charge or credit, defaults to charge
taxed boolean No Apply the first tax rate
taxed2 boolean No Apply the second tax rate
project_id string No Attribute the line to a project
sort_order number No Display order, defaults to array position

Use "effect": "credit" for credit lines rather than negative numbers.

Line items supplied this way are not linked to time entries or expenses, so the underlying work is not marked as billed.

Example Request

curl -X POST https://app.keito.ai/api/v2/invoices \
  -H "Authorization: Bearer kto_xxxxx" \
  -H "Keito-Account-Id: your_company_id" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "client_id_here",
    "subject": "Consulting retainer",
    "line_items": [
      {
        "kind": "Service",
        "description": "Retainer - March",
        "quantity": 1,
        "unit_price": 4000,
        "project_id": "project_id_here"
      },
      {
        "kind": "Service",
        "description": "Goodwill credit",
        "quantity": 1,
        "unit_price": 250,
        "effect": "credit"
      }
    ]
  }'

List Invoices

GET /api/v2/invoices

Query Parameters

Parameter Type Description
client_id string Filter by client
state string draft, open, paid, closed, or voided
from string Issue date lower bound (YYYY-MM-DD)
to string Issue date upper bound (YYYY-MM-DD)
updated_since string ISO timestamp lower bound
page number Page number, starting at 1
per_page number Results per page, default 100, max 2000

Results are returned under an invoices key alongside page, per_page, total_pages, total_entries, and links.

Example Request

curl "https://app.keito.ai/api/v2/invoices?state=open&from=2026-01-01" \
  -H "Authorization: Bearer kto_xxxxx" \
  -H "Keito-Account-Id: your_company_id"

Get an Invoice

GET /api/v2/invoices/:id

Returns the invoice with its client, creator, and line items.

Update an Invoice

PATCH /api/v2/invoices/:id

Only draft invoices can be updated. Invoice-level fields include number, subject, issue_date, due_date, purchase_order, tax, tax2, discount, notes, period_start, period_end, service_period_start, and service_period_end.

curl -X PATCH https://app.keito.ai/api/v2/invoices/invoice_id_here \
  -H "Authorization: Bearer kto_xxxxx" \
  -H "Keito-Account-Id: your_company_id" \
  -H "Content-Type: application/json" \
  -d '{ "due_date": "2026-05-15", "notes": "Payment by bank transfer" }'

Edit Imported Line Items

Patch imported lines in place by supplying the line item id from the create or get-invoice response and any of description, unit_price, or kind:

curl --fail-with-body -X PATCH \
  https://app.keito.ai/api/v2/invoices/invoice_id_here \
  -H "Authorization: Bearer kto_xxxxx" \
  -H "Keito-Account-Id: your_company_id" \
  -H "Content-Type: application/json" \
  -d '{
    "line_items": [
      {
        "id": "line_item_id_here",
        "description": "Consulting services",
        "unit_price": 150,
        "kind": "Service"
      }
    ]
  }'

Keito updates the existing line and recalculates invoice totals. The links to its imported time entries or expenses are preserved, so those source records remain billed and cannot be selected again next month.

Every object must include an ID belonging to the invoice. This endpoint does not accept a full replacement array, line_items_attributes, a new line without an ID, or a nested /line_items/:id route.

Download an Invoice PDF

GET /api/v2/invoices/:id/pdf

Download the invoice exactly as it is currently rendered in Keito:

curl --fail --output "MS-2026-08.pdf" \
  https://app.keito.ai/api/v2/invoices/invoice_id_here/pdf \
  -H "Authorization: Bearer kto_xxxxx" \
  -H "Keito-Account-Id: your_company_id"

A successful response has Content-Type: application/pdf and a Content-Disposition filename. The response is private and is not cached. The API key must have access to the invoice’s workspace; invoices in another workspace return 404.

Validation and Billed State

Invoice create and update requests reject unsupported fields with 400 Bad Request instead of returning a false success. The response message identifies the unsupported field.

Billing flags are server-owned. Do not PATCH is_billed on time entries or expenses. Create the invoice with line_items_import to mark source work as billed; deleting the draft releases eligible source records through the invoice workflow.

Clients can detect the new behavior in the X-Keito-Features response header:

invoice-line-item-edit,invoice-pdf

Delete an Invoice

DELETE /api/v2/invoices/:id

Administrators only, and only for draft invoices. Deleting a draft releases the time entries and expenses it captured, so they become invoiceable again.

Send an Invoice

POST /api/v2/invoices/:id/messages

Emails the invoice to its recipients and moves it from draft to open.

Request Body

Field Type Required Description
event_type string No send, reminder, or thank_you. Defaults to send.
recipients array Yes Objects with email and optional name
cc_recipients array No Objects with email and optional name
subject string No Email subject
body string No Email body
attach_pdf boolean No Attach the invoice PDF, defaults to true
attach_expense_receipts boolean No Include expense receipts, defaults to true
send_me_a_copy boolean No Copy the key owner

Use reminder to chase an unpaid invoice, and thank_you once it is paid. Thank-you messages are only valid for paid invoices and do not accept CC recipients.

Example Request

curl -X POST https://app.keito.ai/api/v2/invoices/invoice_id_here/messages \
  -H "Authorization: Bearer kto_xxxxx" \
  -H "Keito-Account-Id: your_company_id" \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "send",
    "recipients": [{ "name": "Accounts", "email": "accounts@example.com" }],
    "subject": "Invoice for March 2026",
    "body": "Please find March'"'"'s invoice attached.",
    "attach_pdf": true
  }'

List Invoice Messages

GET /api/v2/invoices/:id/messages

Returns the send, reminder, and thank-you history for an invoice under an invoice_messages key.

Not Yet Available

These exist in the Keito web app but are not yet exposed over the API:

  • Recording and listing invoice payments
  • Adding new lines to, or removing lines from, an existing invoice
  • Invoice item categories

If any of these are on your critical path, get in touch — customer demand sets the order we build them in.