API

API Reference

Build custom integrations against your Tidyflow tenant — sync clients, create jobs, post time logs, and pull reporting data.


Base URL & versioning

https://app.tidyflow.com/api/v1

There is currently one version (v1). Breaking changes will be introduced under a new prefix.


Authentication

The Tidyflow API uses personal access tokens sent as a Bearer header.

Generate a token

  1. Log in to Tidyflow.
  2. Open Account → API Tokens.
  3. Click + New token, give it a name (e.g. Zapier, Internal sync), and copy the token immediately — it will not be shown again.
  4. Revoke a token at any time by deleting it from the same page.

Each token inherits the permissions of the user that created it and is scoped to that user’s tenant. Tokens do not expire; revoke any token that may have been exposed.

Sending the token

Add the token to every request:

Authorization: Bearer <your-token>
Accept: application/json

A missing or invalid token returns 401 Unauthenticated.


Conventions

Identifiers

Every resource exposes a stable uuid — always use the UUID in URLs and references, not the numeric id.

Pagination

List endpoints return a standard Laravel paginator:

{
  "data": [ ... ],
  "links": { "first": "...", "last": "...", "prev": null, "next": "..." },
  "meta": { "current_page": 1, "last_page": 10, "per_page": 50, "total": 480 }
}

Control page size with ?limit= (clients, contacts) or ?per_page= (time logs). Default page size is large — set an explicit limit for predictable results.

Filtering

Filters are passed as query parameters. Array filters use repeated keys: ?tags[]=12&tags[]=15.

Errors

StatusMeaning
401Missing or invalid token
403Token does not have access to this record (wrong tenant or insufficient permission)
404Resource not found
422Validation error — errors object lists each field
429Rate limit exceeded — slow down and retry

Validation error body:

{
  "message": "The given data was invalid.",
  "errors": {
    "name": ["The name field is required."]
  }
}

Rate limits

Login is rate-limited; standard read/write endpoints are not throttled per-token today, but expect limits to be introduced as the API matures. Keep traffic reasonable and back off on 429.


Users

Read-only. Useful for resolving user IDs when assigning clients or jobs.

List users

GET /api/v1/users

Query parameters: search.

Get user by ID

GET /api/v1/users/{id}

Find user by email

GET /api/v1/users/email/{email}

Returns the user object, or { "data": null } if no user matches.

User shape

{
  "id": 42,
  "fullname": "Jane Doe",
  "firstname": "Jane",
  "lastname": "Doe",
  "email": "[email protected]",
  "initials": "JD",
  "avatar_url": "https://...",
  "role": "Admin",
  "timezone": "Australia/Sydney",
  "is_contractor": false,
  "is_owner": false,
  "archived_date": null
}

Clients

List clients

GET /api/v1/clients

Query parameters:

NameTypeDescription
searchstringMatch on name, registration number, etc.
tags[]int[]Tag IDs
country_idintCountry filter
limitintPage size

Search clients (autocomplete)

GET /api/v1/clients/search?q=acme

Returns up to 10 lightweight matches:

{ "clients": [ { "id": 7, "uuid": "...", "name": "Acme Pty Ltd" } ] }

Get a client

GET /api/v1/clients/{uuid}

Create a client

POST /api/v1/clients

Body:

FieldRequiredNotes
nameMax 100 chars
client_idYour internal ref (must be unique)
reg_numberBusiness registration number (must be unique)
website
country_idID from the countries reference
user_idAccount manager (Tidyflow user ID)
state, city, street_address, postal_code
client_groupsArray of client group IDs
contactOptional nested object — creates and links a primary contact in one call

Nested contact object:

FieldRequiredNotes
firstname
lastname
emailMust be unique across all contacts
titlee.g. Director
phone_number
include_for_client_requestsboolean

Returns the created client in the same shape as GET /clients/{uuid}.

Update a client

PUT /api/v1/clients/{uuid}

All fields are optional — send only what you want to change. Accepts the same fields as create (excluding the nested contact), plus tags (array of tag IDs).

Client shape

{
  "uuid": "...",
  "name": "Acme Pty Ltd",
  "client_id": "AC-001",
  "reg_number": "12 345 678 901",
  "website": "https://acme.com",
  "country": "Australia",
  "state": "NSW",
  "city": "Sydney",
  "street_address": "1 George St",
  "postal_code": "2000",
  "year_end": "06-30",
  "year_end_text": "30 June",
  "is_archived": false,
  "contacts": [ ... ]
}

Custom fields defined in your tenant are merged into the response keyed by field name.


Contacts

List contacts

GET /api/v1/contacts

Query parameters: search, limit.

Get a contact

GET /api/v1/contacts/{uuid}

Create a contact

POST /api/v1/contacts
FieldRequiredNotes
firstname
lastname
emailMust be unique across all contacts
preferred_nameDefaults to firstname
phone_number
portal_enabledboolean — defaults to true (contact gets a client portal login)

Update a contact

PUT /api/v1/contacts/{uuid}

All fields optional.

Contact shape

{
  "uuid": "...",
  "firstname": "Jane",
  "lastname": "Doe",
  "preferred_name": "Jane",
  "email": "[email protected]",
  "phone_number": "+61 400 000 000",
  "portal_enabled": true
}

Jobs

The Tidyflow UI calls these “Jobs”. The API path uses /jobs.

List jobs

GET /api/v1/jobs

Query parameters:

NameTypeDescription
searchstringMatch on job name
client_uuidstringFilter to one client
users[]int[]Assigned user IDs
tags[]int[]Tag IDs
statuses[]string[]Status slugs
start_date_rangestringDate range filter
due_date_rangestringDate range filter
include_completedboolDefault false
exclude_subtasksbool
exclude_client_requestsbool
sort_by, sort_directionstring

Get a job

GET /api/v1/jobs/{uuid}

Includes client, assignees, followers, tags, parent, and subtasks.

Create a job

POST /api/v1/jobs
FieldRequiredNotes
nameMax 150 chars
descriptionUp to 30,000 chars
client_uuidLink the job to a client
user_idAssignee
statusA status slug from your tenant
tagsArray of tag IDs
start_dateISO date, must be ≤ due_date
due_dateISO date
internal_due_date, extended_due_dateISO dates
estimated_hoursNumeric, 0–1000

Job shape (key fields)

{
  "uuid": "...",
  "name": "FY24 financials",
  "description": "...",
  "status": "in_progress",
  "status_name": "In progress",
  "client_uuid": "...",
  "client": { ... },
  "users": [ ... ],
  "tags": [ ... ],
  "start_date": "2026-05-01",
  "due_date": "2026-06-30",
  "estimated_hours": 12,
  "is_overdue": false,
  "is_completed": false,
  "completed_date": null,
  "is_repeating": false,
  "is_subtask": false,
  "parent_uuid": null,
  "sub_tasks": [ ... ]
}

Repeating-job fields are also returned — see the full payload in a real response if you need to replicate recurrence.


Time logs

Read-only listing of recorded time.

List time logs

GET /api/v1/time-logs

Query parameters:

NameTypeDescription
rangestringDate range
client_uuidstringFilter by client
tasks[]string[]Job UUIDs
users[]int[]User IDs
searchstringMatch on note
per_pageintPage size

Time log shape

{
  "uuid": "...",
  "client": { "uuid": "...", "name": "Acme Pty Ltd" },
  "task":   { "uuid": "...", "name": "FY24 financials" },
  "user":   { "id": 42, "fullname": "Jane Doe" },
  "note": "Reviewed trial balance",
  "hours": 1.5,
  "hours_time": "01:30",
  "date": "2026-05-01",
  "formatted_date": "1 May 2026",
  "created_at": "2026-05-01T09:14:22Z",
  "updated_at": "2026-05-01T09:14:22Z"
}

Was this page helpful?

What went wrong?

Thanks for your feedback.

Last updated May 2, 2026