Skip to content
Integrations

Public API v3

Last updated: Jul 14, 2026

API v3 is Uku’s current public API: it covers far more of the product than the legacy v1.0 API, uses a simpler key-based login with no expiring tokens, and is the version to build any new integration against.

Company Owners and Company Admins on the Elite plan, with the Public API app activated. Company Members cannot see or create API keys.

The Public API app opens on three tabs: Overview, (old) API for the legacy v1.0 API, and Public API — the current v3 API, and where you’ll spend your time. It’s the tab the app opens on.

Uku has two live APIs, and v3 is the one to build on. It isn’t a cosmetic version bump — v1.0 was a read-oriented window onto part of the product, and v3 is a full read-write interface to nearly all of it.

  • It reaches the whole product. Over 30 resources against v1.0’s 16 — projects, budgets, notes, custom fields, attachments, flextime, delegations and more simply have no v1.0 endpoint.
  • It writes, not just reads. v1.0 can create clients, contacts, tasks, and webhooks and nothing else. v3 creates, updates, and deletes across most of Uku, billing included.
  • Signing in is two headers. No /login call, no JWT expiring every 10 minutes, no refresh logic to get wrong — just send your Company UUID and key on each request.
  • Failures tell you what went wrong. Every error carries a stable code your integration can branch on, instead of a sentence to string-match.
  • Keys can be narrowed. Scope a reporting tool to read-only, lock a key to your servers by IP address, give it an expiry date — none of which v1.0 offers.
  • Your billing data is protected. v3 refuses a write to an invoice that changed since your integration last read it, so an automation can’t silently overwrite what someone just fixed in Uku.
  • Uku can call you. Subscribe to events across clients, contacts, tasks, projects, invoices, and time entries, each delivery signed with its own secret and retried if your endpoint is down — instead of polling us for changes.
  • The documentation is live. Try every endpoint in the browser, and hand the machine-readable spec straight to your developer or AI coding tool.
API v3 (Recommended)API v1.0 (Legacy)
Base URLhttps://app.getuku.com/api/v3/https://app.getuku.com/api/v1.0/
Signing inTwo headers, no expiring tokenKey + secret exchanged for a JWT that expires in 10 minutes
CoverageOver 30 resources16 resources
Writing dataCreate, update, and delete across most resourcesMostly read-only — only clients, contacts, tasks, and webhooks can be created
DocumentationInteractive Swagger + machine-readable OpenAPISandbox docs

The legacy API keeps running and your existing integrations keep working — it is frozen, not switched off. Keys are not interchangeable, though: a v1.0 key won’t authenticate against v3, and a v3 key won’t work on v1.0. Both can be active side by side while you migrate.

Each key belongs to your company and to the person who created it, and is shown in full only once.

Path: Settings & AppsPublic APIPublic API

  1. Copy your Company UUID from the field at the top of the tab — this is the X-Uku-Company value every request needs.
  2. Enter a Key name that says what will use it, for example “Power BI reporting” or “Warehouse sync”.
  3. Choose the Scopes the integration needs:
    • Read — read data only.
    • Edit — read, plus create, update, and delete on everyday records such as clients, contacts, tasks, and time entries.
    • All — everything above, plus billing data and managing API keys (see below).
  4. Optionally open Advanced options to set an Expires at (optional) date, or an IP allowlist (optional) (comma-separated addresses or CIDR ranges, for example 192.168.1.1, 10.0.0.0/24) so the key only works from your servers.
  5. Click Generate API key. The full key (it starts with uku_live_) is copied to your clipboard and shown once — “Copy this key now. It will not be shown again.” Store it in your password manager or secrets vault, then click Done.

After that, the table lists the key’s Name, Key prefix, Scopes, Last used, Expires, and Created — never the key itself. The trash icon deletes a key, which revokes it immediately for any integration still using it.

Anything that moves money needs the All scope — to read it as well as to write it. Edit covers most of Uku but stops at the financial surface, and a request that crosses it returns 403 FINANCIAL_SCOPE_REQUIRED.

Reading. An All key reads everything. With a lesser key:

  • Wholly financial resources — invoices, contracts, agreements, invoice sellers, monitors, taxes, budgets, flextime, and reclaims — return 403 even on a GET.
  • Mixed resources — members and products — are still readable, but their money fields come back empty: a member’s credit balances, and a product’s prices, tax settings, billing basis, and accounting routing codes.

Writing. The line falls in two places depending on the record:

  • All for every write — invoices and their rows, member agreements, product prices, and new budgets (a budget’s amount is required, so creating one always crosses the money line). An Edit key cannot create, change, or delete any of these. Creating or deleting a flextime entry also needs All.
  • All for the money fields only — on contracts, monitors, products, taxes, invoice sellers, flextime, and existing budgets, an Edit key can change the ordinary fields while the money ones need All. The money fields are: a contract’s invoicing period, invoice day, and start and end dates; a monitor’s thresholds; a tax rate; a product’s tax settings, billing basis, and accounting routing codes; a flextime entry’s duration and status (confirming or declining it moves the member’s credit balance); a budget’s amount, warning rate, recurrence, or status; and — on an invoice seller — everything except its status and logo, since its legal name, address, company number, VAT number, and bank details all identify who gets paid. (A contract’s own status is no longer something you set: Uku derives it from the dates — pending before the range, active within it, finished after.)

The invoice lifecycle actions from the Uku UI are available as endpoints, each needing the All scope and the If-Match version stamp:

  • POST /invoices/{id}/send queues the invoice for delivery (email, PDF, e-invoice) and returns 202 — it goes out in the background, and status flips to sent once that finishes. Eligible while created, sent, or paid, else 409 INVOICE_NOT_SENDABLE.
  • POST /invoices/{id}/mark-paid records payment and moves the invoice to paid. Eligible only while created or sent, else 409 INVOICE_NOT_PAYABLE.

To reverse a reclaim, POST /reclaims/{id}/undo moves the reclaimed time back to its source (scope All; 409 RECLAIM_NOT_UNDOABLE if it’s already undone, the source task is gone, or the period’s invoice is locked).

Every request carries both headers. Nothing else is needed — there is no login call and no token to refresh.

Terminal window
curl https://app.getuku.com/api/v3/clients \
-H "X-Uku-Company: your-company-uuid" \
-H "X-API-Key: uku_live_..."

The response wraps the records in a data array with a meta block for paging:

{
"data": [
{ "id": 123, "name": "Apple Ltd" }
],
"meta": { "total": 1, "limit": 50, "offset": 0, "has_more": false },
"warnings": null
}

Errors come back in the same shape every time, with a code your integration can branch on:

{
"error": {
"code": "MISSING_COMPANY",
"message": "X-Uku-Company header is required"
}
}

Some errors — a rejected request body, for instance — add a details list naming the fields at fault.

Uku publishes a live, interactive sandbox for API v3 — every endpoint, with its request and response schemas, and an Try it out button that fires real calls against your own company using your key. It is the fastest way to see an endpoint’s exact shape before writing any code, and it is always current because it is generated from the running API.

The full, always-current list of endpoints is in the interactive sandbox above.

  • Clients and contacts — clients, contacts, client groups, notes, and client members (each person’s role on a client, including their portal-user flag).
  • Work — tasks, task relations, task automations, delegations, projects, topics, workflow roles, roles.
  • Time — time entries, calendar (working hours and holidays), flextime.
  • Billing — invoices and rows, contracts and rows, products and prices, taxes, invoice sellers, budgets, monitors, reclaims.
  • People — members and their agreements.
  • Setup — custom fields, product fields, suppliers, content templates.
  • Files — attachments on tasks and notes (up to 150 MB per file; executables are rejected).

Some endpoints are labelled Preview in the documentation and return an X-Api-Preview: true header. They are fully available — the label means the shape may still change, so pin your integration to what you tested.

Every list endpoint in API v3 takes the same query parameters for paging, filtering, and sorting, so what you learn on one resource works on all of them.

  • Paging?limit= (1–200, default 50) and ?offset= (starts at 0). meta.has_more tells you when to fetch the next page. Beyond 10,000 records, page with ?cursor= instead and follow meta.next_cursor.
  • Filtering — filter by field (?client_id=123), pass several values comma-separated, search text with ?q=, and compare with suffixes such as ?date.gte=2026-01-01.
  • Sorting?sort=name ascending, ?sort=-created_at descending.

Always filter heavy endpoints (tasks, time entries, invoices) by date or by parent record rather than pulling everything.

Each key is allowed 120 read requests and 30 write requests per minute. Every response tells you where you stand:

  • X-RateLimit-Limit — your allowance for this type of request.
  • X-RateLimit-Remaining — how much is left this minute.
  • X-RateLimit-Reset — when the counter resets.

Go over it and the API answers 429 with a Retry-After header. Wait for that period instead of retrying immediately. Requests without a key are limited more tightly (60 a minute per IP address, since they can only ever fail authentication), and a per-IP ceiling of 1000 a minute sits behind everything as an abuse backstop — well above what a normal integration sends.

When a create request times out, you often can’t tell whether it went through — retrying might make a duplicate invoice. To retry safely, send an Idempotency-Key header (any unique string you generate) on POST /invoices, /contracts, /agreements, or /time-entries. A repeat with the same key and an identical body replays the original response instead of creating a second record, and the reply carries Idempotency-Replayed: true. Keys are remembered for 24 hours. Two things return a 409 instead: retrying while the first request is still running (IDEMPOTENCY_CONFLICT — wait and try again), and reusing a key with a different body (IDEMPOTENCY_KEY_REUSED — use a fresh key for each distinct request).

Rather than polling Uku for changes, you can have Uku call you: subscribe a URL to an event and every time that event happens we POST the record to your endpoint, signed and retried. Events cover clients, contacts, tasks, projects, invoices, and time entries.

Subscriptions are created through the API and need a key with the All scope, but you can watch them from Uku: the Public API tab has a Connected webhooks list showing every subscription’s status, failure count, and recent deliveries, with buttons to re-enable a disabled one or replay a failed delivery. The full contract — subscribing, the delivery payload, signature verification, retries and replay — is in Webhooks.

Money-related records — invoices, contracts and their rows, member agreements, member credits, and client monitors — are protected against two systems overwriting each other.

When you read one of these records, the response includes an ETag header, which is simply a stamp of when the record last changed. To change or delete the record, send that stamp back as an If-Match header:

  • Leave it out and the API refuses with 428 — it wants to know which version you’re editing.
  • Send an old stamp, because someone edited the record in Uku or through another integration in the meantime, and the API refuses with 412. Read the record again and retry with the fresh stamp.

Invoices and agreements want the stamp on every change to an existing record; contracts, members, and monitors want it when the change touches a money field. Creating a brand-new record never needs one — there’s no previous version to match. Ordinary records — tasks, clients, contacts, time entries — never do.

An API v3 key opens your company’s live data to whatever holds it, so treat it as a credential of the same weight as a password to Uku itself.

  • Treat the key like a password. Never commit it to version control, never put it in a browser or mobile app where a user could read it.
  • One key per integration. If one leaks, delete it without breaking anything else.
  • Give the smallest scope that works. A reporting tool needs Read, not All.
  • Lock keys to your servers with the IP allowlist, and set an Expires at date for anything temporary.
  • Delete keys you no longer use. Deleting revokes access immediately.
  • HTTPS only. Plain HTTP requests are rejected.
  • A key only ever reaches your own company’s data. Records belonging to another company return 404, not an error that reveals they exist.

I only see the Overview tab in the Public API app

Section titled “I only see the Overview tab in the Public API app”

The Public API and (old) API tabs appear only for Company Owners and Company Admins, and only once the Public API app is activated for your company on the Elite plan. If you land on Overview with nothing beside it, that’s why: activate the app from that screen, or ask a Company Owner to. Company Members never see API keys at all.

A v1.0 key can’t authenticate against v3 — the two versions use separate credentials. Generate a new key on the Public API tab and switch to the two-header login (X-Uku-Company + X-API-Key); there is no /login call and no JWT in v3.

Why do I get a “company header required” error (MISSING_COMPANY)?

Section titled “Why do I get a “company header required” error (MISSING_COMPANY)?”

The X-Uku-Company header is missing or empty on the request. Copy the Company UUID from the top of the Public API tab and send it with every request, alongside the API key. Sending the key on its own is never enough — v3 always needs both headers.

On a write, the key’s scope is too narrow: a Read key can’t create or change anything, so generate a new key with Edit.

If the error code is specifically FINANCIAL_SCOPE_REQUIRED, the request touched financial data — reading or writing an invoice, contract, agreement, invoice seller, monitor, tax, budget, flextime, reclaim, or a money field on a member or product. All of that needs the All scope, as described under Billing data needs the All scope.

Do you have a test or sandbox environment?

Section titled “Do you have a test or sandbox environment?”

There is an interactive sandbox at app.getuku.com/api/v3/docs where you can call every endpoint from the browser — but it runs against your real company, because API v3 issues live keys only (uku_live_). There is no separate test company and no test key. While you build, write against a small, clearly-named client you can delete afterwards.

Can I rotate a key without breaking my integration?

Section titled “Can I rotate a key without breaking my integration?”

You can rotate a key without breaking your integration, but only through the API — there’s no rotate button in Uku. A key with the All scope can call POST /api/v3/auth/keys/{key_id}/rotate, which issues a replacement and keeps the old key working for a 24-hour grace period, so you can deploy the new value without downtime. Deleting a key in the interface, by contrast, revokes it immediately.