{"openapi":"3.1.0","info":{"title":"PaySuiteTT time tracking API","version":"1.0.0","description":"Log, correct and report on tracked time in PaySuiteTT with an API key. Human-readable reference: https://paysuitett.com/developers/api","contact":{"name":"PaySuiteTT support","url":"https://paysuitett.com/support","email":"support@illusionxmedia.com"}},"servers":[{"url":"https://paysuitett.com/api/v1"}],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"tags":[{"name":"Account"},{"name":"Clients and projects"},{"name":"Teams"},{"name":"Timer"},{"name":"Time entries"},{"name":"Kiosk shifts"},{"name":"Reports"},{"name":"Billing"}],"paths":{"/customers":{"get":{"operationId":"customers","tags":["Billing"],"summary":"Find billing customers","description":"Sales contacts of type customer or both, sorted by name, with whether a billing profile exists.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"search","in":"query","required":false,"description":"Name contains this text, ignoring case.","schema":{"type":"string","description":"Name contains this text, ignoring case."}}],"responses":{"200":{"description":"Find billing customers","content":{"application/json":{"example":{"customers":[{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"Customer","email":"customer@example.com","billingProfile":true}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/billing-profiles":{"get":{"operationId":"billing-profiles","tags":["Billing"],"summary":"List billing profiles","description":"Rates, time sources, cadence and unbilled hours for customers with billing profiles.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","responses":{"200":{"description":"List billing profiles","content":{"application/json":{"example":{"profiles":[{"customerId":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","customerName":"Customer","hourlyRate":150,"clientNames":["Agency"],"projectIds":[],"groupBy":"project","cadenceDays":14,"currencyCode":"TTD","exchangeRate":null,"invoiceNumberPrefix":null,"lastServiceEnd":"2026-09-30","unbilledHours":2,"earliestUnbilled":"2026-10-01"}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/billing-profiles/{customerId}":{"put":{"operationId":"billing-profile-save","tags":["Billing"],"summary":"Save a billing profile","description":"Upserts a customer's profile. Customer and project IDs must belong to this business. Unknown body fields are rejected.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"customerId","in":"path","required":true,"description":"Customer ID.","schema":{"type":"string","description":"Customer ID.","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"hourlyRate":{"type":"number","description":"Nonnegative hourly rate in the profile's currency, or null for project rates.","example":150},"clientNames":{"type":"array","description":"Time client names.","example":["Agency"]},"projectIds":{"type":"array","description":"Project UUIDs in this business.","example":[]},"groupBy":{"type":"string","description":"How to group invoice lines.","enum":["project","project_task","single"],"example":"project"},"cadenceDays":{"type":"integer","description":"Days per period, 7–31.","example":14},"currency":{"type":"string","description":"Currency this customer is billed in; kept when omitted, TTD for a new profile.","example":"USD"},"exchangeRate":{"type":"number","description":"Default TTD for one unit of the currency, used for drafts from time; null clears it. Required for a foreign currency when drafting.","example":6.78},"conversionFeePercent":{"type":"number","description":"Default conversion fee percentage for this customer's foreign-currency drafts; null for none."},"conversionFeeTtd":{"type":"number","description":"Default fixed conversion fee in TTD for this customer's foreign-currency drafts; null for none."},"invoiceNumberPrefix":{"type":"string","description":"Up to 5 letters or digits. Posted invoices with a service period are numbered PREFIX-YYYYMMDD-MMDD (a letter is added for a second invoice for the same period); null returns to INV- numbers.","example":"ACME"}},"required":["hourlyRate","clientNames","projectIds","groupBy","cadenceDays"]}}}},"responses":{"200":{"description":"Save a billing profile","content":{"application/json":{"example":{"profile":{"customerId":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","customerName":"Customer","hourlyRate":150,"clientNames":["Agency"],"projectIds":[],"groupBy":"project","cadenceDays":14,"currencyCode":"TTD","exchangeRate":null,"invoiceNumberPrefix":null,"lastServiceEnd":"2026-09-30","unbilledHours":2,"earliestUnbilled":"2026-10-01"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"delete":{"operationId":"billing-profile-delete","tags":["Billing"],"summary":"Delete a billing profile","description":"Removes the profile without changing invoices or time entries.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"customerId","in":"path","required":true,"description":"Customer ID.","schema":{"type":"string","description":"Customer ID.","format":"uuid"}}],"responses":{"200":{"description":"Delete a billing profile","content":{"application/json":{"example":{"deleted":true}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/billing/preview":{"post":{"operationId":"billing-preview","tags":["Billing"],"summary":"Preview time to bill","description":"Approved and awaiting-approval billable time, excluding linked entries. Missing fields use the customer's profile. Missing dates use its next service period; 409 NOTHING_TO_BILL when no period is available. Amount is tracked hours × rate, rounded to two decimals, or zero for a missing rate or currency mismatch. Unknown fields are rejected.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Customer whose billing profile supplies missing fields.","format":"uuid","example":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b"},"clientNames":{"type":"array","description":"Time client names; defaults to the profile's names."},"projectIds":{"type":"array","description":"Time project UUIDs; defaults to the profile's projects. A client OR project match includes time."},"start":{"type":"string","description":"First service date; defaults to nextServicePeriod from the customer's profile.","format":"date"},"end":{"type":"string","description":"Last service date, inclusive; defaults to nextServicePeriod (no later than yesterday).","format":"date"},"groupBy":{"type":"string","description":"Defaults to the profile's grouping, then project.","enum":["project","project_task","single"]},"rateSource":{"type":"string","description":"Defaults to customer when a rate is known, otherwise project.","enum":["customer","project"]},"rate":{"type":"number","description":"Nonnegative hourly rate in the invoice currency; defaults to the customer's profile rate."},"currency":{"type":"string","description":"Invoice currency (three letters); defaults to the profile's, then TTD. Project rates in another currency are flagged currencyMismatch."},"invoiceId":{"type":"string","description":"Current invoice, whose existing links are excluded without counting as billed elsewhere.","format":"uuid"}}}}}},"responses":{"200":{"description":"Preview time to bill","content":{"application/json":{"example":{"period":{"start":"2026-10-01","end":"2026-10-14","text":"1–14 Oct 2026"},"rate":150,"rateSource":"customer","groupBy":"project","lines":[{"key":"p:a1b2c3d4-e5f6-4789-90ab-cdef12345678","title":"Development","needsInvoiceName":false,"projectIds":["a1b2c3d4-e5f6-4789-90ab-cdef12345678"],"trackedHours":2,"unapprovedHours":0,"rate":150,"currencyMismatch":false,"entryIds":["7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71"],"taskTitles":["Updated checkout"],"plainDescription":"Development: Updated checkout","amount":300}],"totals":{"trackedHours":2,"amount":300},"unapprovedHours":0,"alreadyBilledHours":0,"alreadyBilledOn":[],"currency":"TTD","exchangeRate":null,"invoiceNumberPrefix":null}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/billing/descriptions":{"post":{"operationId":"billing-descriptions","tags":["Billing"],"summary":"Write time line descriptions","description":"Uses the same avoid-names list and optional AI writer as the app. Without a server Claude key, enabled is false and every line receives its plain fallback. Unknown fields are rejected.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"string","description":"Service period text.","example":"1–14 Oct 2026"},"clientNames":{"type":"array","description":"Internal client names to avoid."},"lines":{"type":"array","description":"Up to 100 {key, title, hours, taskTitles} objects; up to 12 task titles each.","example":[{"key":"all","title":"Development","hours":2,"taskTitles":["Updated checkout"]}]}},"required":["period","lines"]}}}},"responses":{"200":{"description":"Write time line descriptions","content":{"application/json":{"example":{"descriptions":{"all":{"description":"Development: Updated checkout","fallback":true}},"enabled":false}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/invoices":{"get":{"operationId":"invoices-list","tags":["Billing"],"summary":"List invoices","description":"Newest issue dates first. Invoice objects omit lines; money is in the invoice's currency (books holds the TTD amounts), with two decimal precision. paid excludes reversed payments; balanceDue subtracts posted credits and is zero for drafts/voids. URLs point to this installation's APP_URL host.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"status","in":"query","required":false,"description":"Filter status.","schema":{"type":"string","description":"Filter status.","enum":["draft","posted","void"]}},{"name":"customerId","in":"query","required":false,"description":"Filter customer.","schema":{"type":"string","description":"Filter customer.","format":"uuid"}},{"name":"from","in":"query","required":false,"description":"First issue date, inclusive.","schema":{"type":"string","description":"First issue date, inclusive.","format":"date"}},{"name":"to","in":"query","required":false,"description":"Last issue date, inclusive.","schema":{"type":"string","description":"Last issue date, inclusive.","format":"date"}},{"name":"limit","in":"query","required":false,"description":"Default 50, maximum 200.","schema":{"type":"integer","description":"Default 50, maximum 200."}}],"responses":{"200":{"description":"List invoices","content":{"application/json":{"example":{"invoices":[{"id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","number":"INV-DRAFT","numberEnteredManually":false,"status":"draft","currency":"TTD","exchangeRate":null,"customer":{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"Customer","email":"customer@example.com"},"issueDate":"2026-10-15","dueDate":"2026-11-14","servicePeriod":{"start":"2026-10-01","end":"2026-10-14","text":"1–14 Oct 2026"},"qtyLabel":"hours","notes":null,"subtotal":300,"vatTotal":37.5,"total":337.5,"books":{"currency":"TTD","subtotal":300,"vatTotal":37.5,"total":337.5,"conversionFeePercent":null,"conversionFeeTtd":null,"totalTyped":false},"paid":0,"balanceDue":0,"urls":{"app":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","pdf":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/pdf","timeSchedule":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/time-schedule","workReport":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/work-report"}}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"post":{"operationId":"invoice-create","tags":["Billing"],"summary":"Create a draft invoice","description":"Computes amounts and VAT server-side, then saves via save_invoice_draft. Emailing, payments and voiding stay in the app. Unknown fields are rejected.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Customer in this business.","format":"uuid","example":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b"},"issueDate":{"type":"string","description":"Defaults to today's business date on creation.","format":"date"},"dueDate":{"type":"string","description":"Defaults to issue date plus 30 days on creation; cannot precede issue date.","format":"date"},"servicePeriod":{"type":"object","description":"{start, end}, YYYY-MM-DD, start ≤ end; null clears it."},"qtyLabel":{"type":"string","description":"Quantity label; defaults to qty.","enum":["qty","hours"]},"notes":{"type":"string","description":"Invoice notes; empty string clears them."},"currency":{"type":"string","description":"Currency the customer is billed in (TTD, USD, ...); defaults to TTD, or the draft's. Line prices are in it.","example":"USD"},"exchangeRate":{"type":"number","description":"TTD for one unit of a foreign currency, required with it (kept on PATCH when the currency stays). Each line's TTD amount, used in the books, is its amount × rate rounded to cents.","example":6.78},"invoiceNumber":{"type":"string","description":"Drafts: a number to use instead of the next in sequence, up to 30 characters, unique in the business ignoring case (voided numbers included). It locks when the invoice is posted. Blank or null returns to the automatic number. Owner/Admin or Accountant only."},"conversionFeePercent":{"type":"number","description":"Foreign invoices: percentage taken off the TTD value booked (0 to under 100); null for none. On PATCH, omit all three books fields to keep the draft's."},"conversionFeeTtd":{"type":"number","description":"Foreign invoices: fixed TTD amount taken off the TTD value booked, after the percentage."},"booksTotal":{"type":"number","description":"Foreign invoices: the TTD total to book, typed instead of worked out (fees are then ignored). The document amounts don't change; the TTD amount is spread over the lines in proportion."},"lines":{"type":"array","description":"Replaces all lines: {description, qty, unitPrice, section?, taxTreatment?, vatRatePercent?, timeSourceKey?, timeEntryIds?}. VAT defaults to the business's setup. taxTreatment: standard|zero_rated|exempt|out_of_scope; vatRatePercent: 0–25 (12.5 means 12.5%). Nonnegative quantities (up to four decimals, so exact tracked hours bill to the cent) and prices in the invoice currency. Time entry IDs are UUIDs; save_invoice_draft enforces ownership, hour caps, links and order.","example":[{"description":"Development","qty":2,"unitPrice":150}]}},"required":["customerId","lines"]}}}},"responses":{"201":{"description":"Create a draft invoice","content":{"application/json":{"example":{"invoice":{"id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","number":"INV-DRAFT","numberEnteredManually":false,"status":"draft","currency":"TTD","exchangeRate":null,"customer":{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"Customer","email":"customer@example.com"},"issueDate":"2026-10-15","dueDate":"2026-11-14","servicePeriod":{"start":"2026-10-01","end":"2026-10-14","text":"1–14 Oct 2026"},"qtyLabel":"hours","notes":null,"subtotal":300,"vatTotal":37.5,"total":337.5,"books":{"currency":"TTD","subtotal":300,"vatTotal":37.5,"total":337.5,"conversionFeePercent":null,"conversionFeeTtd":null,"totalTyped":false},"paid":0,"balanceDue":0,"lines":[{"position":0,"section":null,"description":"Development: Updated checkout","qty":2,"unitPrice":150,"taxTreatment":"standard","vatRate":0.125,"net":300,"vat":37.5,"gross":337.5,"timeSourceKey":"p:a1b2c3d4-e5f6-4789-90ab-cdef12345678","trackedHours":2,"timeEntryCount":1}],"urls":{"app":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","pdf":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/pdf","timeSchedule":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/time-schedule","workReport":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/work-report"}}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/invoices/{id}":{"get":{"operationId":"invoice-get","tags":["Billing"],"summary":"Read an invoice","description":"Includes ordered lines, linked tracked hours and entry counts. A draft's number is INV-DRAFT until it is posted, unless one was typed (numberEnteredManually). timeSchedule and workReport are null without time links; all URLs are absolute app links on APP_URL.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"id","in":"path","required":true,"description":"Invoice ID.","schema":{"type":"string","description":"Invoice ID.","format":"uuid"}}],"responses":{"200":{"description":"Read an invoice","content":{"application/json":{"example":{"invoice":{"id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","number":"INV-DRAFT","numberEnteredManually":false,"status":"draft","currency":"TTD","exchangeRate":null,"customer":{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"Customer","email":"customer@example.com"},"issueDate":"2026-10-15","dueDate":"2026-11-14","servicePeriod":{"start":"2026-10-01","end":"2026-10-14","text":"1–14 Oct 2026"},"qtyLabel":"hours","notes":null,"subtotal":300,"vatTotal":37.5,"total":337.5,"books":{"currency":"TTD","subtotal":300,"vatTotal":37.5,"total":337.5,"conversionFeePercent":null,"conversionFeeTtd":null,"totalTyped":false},"paid":0,"balanceDue":0,"lines":[{"position":0,"section":null,"description":"Development: Updated checkout","qty":2,"unitPrice":150,"taxTreatment":"standard","vatRate":0.125,"net":300,"vat":37.5,"gross":337.5,"timeSourceKey":"p:a1b2c3d4-e5f6-4789-90ab-cdef12345678","trackedHours":2,"timeEntryCount":1}],"urls":{"app":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","pdf":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/pdf","timeSchedule":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/time-schedule","workReport":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/work-report"}}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"patch":{"operationId":"invoice-update","tags":["Billing"],"summary":"Update a draft invoice","description":"All fields are optional. Sending lines replaces every line. Omitting lines preserves and re-sends existing lines and their time links unchanged. Non-drafts return 409 CONFLICT. Unknown fields are rejected.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"id","in":"path","required":true,"description":"Invoice ID.","schema":{"type":"string","description":"Invoice ID.","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Customer in this business.","format":"uuid","example":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b"},"issueDate":{"type":"string","description":"Defaults to today's business date on creation.","format":"date"},"dueDate":{"type":"string","description":"Defaults to issue date plus 30 days on creation; cannot precede issue date.","format":"date"},"servicePeriod":{"type":"object","description":"{start, end}, YYYY-MM-DD, start ≤ end; null clears it."},"qtyLabel":{"type":"string","description":"Quantity label; defaults to qty.","enum":["qty","hours"]},"notes":{"type":"string","description":"Invoice notes; empty string clears them."},"currency":{"type":"string","description":"Currency the customer is billed in (TTD, USD, ...); defaults to TTD, or the draft's. Line prices are in it.","example":"USD"},"exchangeRate":{"type":"number","description":"TTD for one unit of a foreign currency, required with it (kept on PATCH when the currency stays). Each line's TTD amount, used in the books, is its amount × rate rounded to cents.","example":6.78},"invoiceNumber":{"type":"string","description":"Drafts: a number to use instead of the next in sequence, up to 30 characters, unique in the business ignoring case (voided numbers included). It locks when the invoice is posted. Blank or null returns to the automatic number. Owner/Admin or Accountant only."},"conversionFeePercent":{"type":"number","description":"Foreign invoices: percentage taken off the TTD value booked (0 to under 100); null for none. On PATCH, omit all three books fields to keep the draft's."},"conversionFeeTtd":{"type":"number","description":"Foreign invoices: fixed TTD amount taken off the TTD value booked, after the percentage."},"booksTotal":{"type":"number","description":"Foreign invoices: the TTD total to book, typed instead of worked out (fees are then ignored). The document amounts don't change; the TTD amount is spread over the lines in proportion."},"lines":{"type":"array","description":"Replaces all lines: {description, qty, unitPrice, section?, taxTreatment?, vatRatePercent?, timeSourceKey?, timeEntryIds?}. VAT defaults to the business's setup. taxTreatment: standard|zero_rated|exempt|out_of_scope; vatRatePercent: 0–25 (12.5 means 12.5%). Nonnegative quantities (up to four decimals, so exact tracked hours bill to the cent) and prices in the invoice currency. Time entry IDs are UUIDs; save_invoice_draft enforces ownership, hour caps, links and order.","example":[{"description":"Development","qty":2,"unitPrice":150}]}}}}}},"responses":{"200":{"description":"Update a draft invoice","content":{"application/json":{"example":{"invoice":{"id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","number":"INV-DRAFT","numberEnteredManually":false,"status":"draft","currency":"TTD","exchangeRate":null,"customer":{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"Customer","email":"customer@example.com"},"issueDate":"2026-10-15","dueDate":"2026-11-14","servicePeriod":{"start":"2026-10-01","end":"2026-10-14","text":"1–14 Oct 2026"},"qtyLabel":"hours","notes":null,"subtotal":300,"vatTotal":37.5,"total":337.5,"books":{"currency":"TTD","subtotal":300,"vatTotal":37.5,"total":337.5,"conversionFeePercent":null,"conversionFeeTtd":null,"totalTyped":false},"paid":0,"balanceDue":0,"lines":[{"position":0,"section":null,"description":"Development: Updated checkout","qty":2,"unitPrice":150,"taxTreatment":"standard","vatRate":0.125,"net":300,"vat":37.5,"gross":337.5,"timeSourceKey":"p:a1b2c3d4-e5f6-4789-90ab-cdef12345678","trackedHours":2,"timeEntryCount":1}],"urls":{"app":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","pdf":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/pdf","timeSchedule":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/time-schedule","workReport":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/work-report"}}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/invoices/from-time":{"post":{"operationId":"invoice-from-time","tags":["Billing"],"summary":"Create a draft from tracked time","description":"Runs the preview with the customer's profile, then creates hours lines at exact tracked hours in the profile's currency and sets the service period. Skips missing rates and currency mismatches; 409 NOTHING_TO_BILL when no lines remain. Default billed hours are tracked hours; overrides cannot exceed tracked hours. Unknown fields are rejected.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","description":"Customer whose billing profile supplies missing fields.","format":"uuid","example":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b"},"start":{"type":"string","description":"First service date; defaults to nextServicePeriod from the customer's profile.","format":"date"},"end":{"type":"string","description":"Last service date, inclusive; defaults to nextServicePeriod (no later than yesterday).","format":"date"},"groupBy":{"type":"string","description":"Defaults to the profile's grouping, then project.","enum":["project","project_task","single"]},"rateSource":{"type":"string","description":"Defaults to customer when a rate is known, otherwise project.","enum":["customer","project"]},"rate":{"type":"number","description":"Nonnegative hourly rate in the invoice currency; defaults to the customer's profile rate."},"currency":{"type":"string","description":"Invoice currency (three letters); defaults to the profile's, then TTD. Project rates in another currency are flagged currencyMismatch."},"section":{"type":"string","description":"Section on every generated line."},"billedHours":{"type":"object","description":"Map of preview line keys to billed hours (nonnegative, up to four decimals, no more than tracked hours)."},"exchangeRate":{"type":"number","description":"TTD for one unit of the invoice currency; defaults to the customer's profile rate."},"conversionFeePercent":{"type":"number","description":"Foreign invoices: percentage taken off the TTD value booked (0 to under 100); null for none. On PATCH, omit all three books fields to keep the draft's. Defaults to the customer's profile fees."},"conversionFeeTtd":{"type":"number","description":"Foreign invoices: fixed TTD amount taken off the TTD value booked, after the percentage. Defaults to the customer's profile fees."},"booksTotal":{"type":"number","description":"Foreign invoices: the TTD total to book, typed instead of worked out (fees are then ignored). The document amounts don't change; the TTD amount is spread over the lines in proportion. Defaults to the customer's profile fees."},"invoiceNumber":{"type":"string","description":"Drafts: a number to use instead of the next in sequence, up to 30 characters, unique in the business ignoring case (voided numbers included). It locks when the invoice is posted. Blank or null returns to the automatic number. Owner/Admin or Accountant only."},"taxTreatment":{"type":"string","description":"VAT treatment for every line, such as zero_rated for exported services; defaults to the business's VAT setup.","enum":["standard","zero_rated","exempt","out_of_scope"]},"descriptions":{"type":"object","description":"Map of preview line keys to descriptions; otherwise plainDescription."},"issueDate":{"type":"string","description":"Defaults to today's business date on creation.","format":"date"},"dueDate":{"type":"string","description":"Defaults to issue date plus 30 days on creation; cannot precede issue date.","format":"date"},"notes":{"type":"string","description":"Invoice notes; empty string clears them."}},"required":["customerId"]}}}},"responses":{"201":{"description":"Create a draft from tracked time","content":{"application/json":{"example":{"invoice":{"id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","number":"INV-DRAFT","numberEnteredManually":false,"status":"draft","currency":"TTD","exchangeRate":null,"customer":{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"Customer","email":"customer@example.com"},"issueDate":"2026-10-15","dueDate":"2026-11-14","servicePeriod":{"start":"2026-10-01","end":"2026-10-14","text":"1–14 Oct 2026"},"qtyLabel":"hours","notes":null,"subtotal":300,"vatTotal":37.5,"total":337.5,"books":{"currency":"TTD","subtotal":300,"vatTotal":37.5,"total":337.5,"conversionFeePercent":null,"conversionFeeTtd":null,"totalTyped":false},"paid":0,"balanceDue":0,"lines":[{"position":0,"section":null,"description":"Development: Updated checkout","qty":2,"unitPrice":150,"taxTreatment":"standard","vatRate":0.125,"net":300,"vat":37.5,"gross":337.5,"timeSourceKey":"p:a1b2c3d4-e5f6-4789-90ab-cdef12345678","trackedHours":2,"timeEntryCount":1}],"urls":{"app":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","pdf":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/pdf","timeSchedule":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/time-schedule","workReport":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/work-report"}},"preview":{"period":{"start":"2026-10-01","end":"2026-10-14","text":"1–14 Oct 2026"},"rate":150,"rateSource":"customer","groupBy":"project","lines":[{"key":"p:a1b2c3d4-e5f6-4789-90ab-cdef12345678","title":"Development","needsInvoiceName":false,"projectIds":["a1b2c3d4-e5f6-4789-90ab-cdef12345678"],"trackedHours":2,"unapprovedHours":0,"rate":150,"currencyMismatch":false,"entryIds":["7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71"],"taskTitles":["Updated checkout"],"plainDescription":"Development: Updated checkout","amount":300}],"totals":{"trackedHours":2,"amount":300},"unapprovedHours":0,"alreadyBilledHours":0,"alreadyBilledOn":[],"currency":"TTD","exchangeRate":null,"invoiceNumberPrefix":null}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/invoices/{id}/post":{"post":{"operationId":"invoice-post","tags":["Billing"],"summary":"Post a draft invoice","description":"Posts the draft to the books (a journal in TTD) and assigns its number: PREFIX-YYYYMMDD-MMDD when the customer's profile has a prefix and the invoice a service period, else INV-YYYY-NNNNN. Posting a posted invoice returns it unchanged; void returns 409 CONFLICT. Emailing stays in the app.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"id","in":"path","required":true,"description":"Invoice ID.","schema":{"type":"string","description":"Invoice ID.","format":"uuid"}}],"responses":{"200":{"description":"Post a draft invoice","content":{"application/json":{"example":{"invoice":{"id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","number":"INV-2026-00042","numberEnteredManually":false,"status":"posted","currency":"TTD","exchangeRate":null,"customer":{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"Customer","email":"customer@example.com"},"issueDate":"2026-10-15","dueDate":"2026-11-14","servicePeriod":{"start":"2026-10-01","end":"2026-10-14","text":"1–14 Oct 2026"},"qtyLabel":"hours","notes":null,"subtotal":300,"vatTotal":37.5,"total":337.5,"books":{"currency":"TTD","subtotal":300,"vatTotal":37.5,"total":337.5,"conversionFeePercent":null,"conversionFeeTtd":null,"totalTyped":false},"paid":0,"balanceDue":337.5,"lines":[{"position":0,"section":null,"description":"Development: Updated checkout","qty":2,"unitPrice":150,"taxTreatment":"standard","vatRate":0.125,"net":300,"vat":37.5,"gross":337.5,"timeSourceKey":"p:a1b2c3d4-e5f6-4789-90ab-cdef12345678","trackedHours":2,"timeEntryCount":1}],"urls":{"app":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","pdf":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/pdf","timeSchedule":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/time-schedule","workReport":"https://paysuitett.com/admin/sales/invoices/6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d/work-report"}}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/invoices/{id}/pdf":{"get":{"operationId":"invoice-pdf","tags":["Billing"],"summary":"Download invoice PDF","description":"Returns application/pdf bytes using the app's renderer. with=work-report appends the work report, one PDF.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"id","in":"path","required":true,"description":"Invoice ID.","schema":{"type":"string","description":"Invoice ID.","format":"uuid"}},{"name":"with","in":"query","required":false,"description":"Append the work report.","schema":{"type":"string","description":"Append the work report.","enum":["work-report"]}}],"responses":{"200":{"description":"Download invoice PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/invoices/{id}/time-schedule":{"get":{"operationId":"invoice-time-schedule","tags":["Billing"],"summary":"Download time schedule PDF","description":"Hours per day per line. 404 without time links.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"id","in":"path","required":true,"description":"Invoice ID.","schema":{"type":"string","description":"Invoice ID.","format":"uuid"}}],"responses":{"200":{"description":"Download time schedule PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/invoices/{id}/work-report":{"get":{"operationId":"invoice-work-report","tags":["Billing"],"summary":"Download work report PDF","description":"The Time Reports summary for exactly the time the invoice bills: hours per day and each project's time-entry descriptions, under invoice names. Amounts at the invoice's rates and currency, shown only when every line bills all its tracked time. 404 without time links.\n\nAccess: Master key, or a personal key held by an owner/admin or accountant. The business must have the invoices service. Runs as the holder under their row-level security.","parameters":[{"name":"id","in":"path","required":true,"description":"Invoice ID.","schema":{"type":"string","description":"Invoice ID.","format":"uuid"}}],"responses":{"200":{"description":"Download work report PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/projects/{id}":{"get":{"operationId":"project-get","tags":["Clients and projects"],"summary":"Read a project","description":"Returns a visible project, including its customer-facing invoiceName (null when unset).\n\nAccess: Any key, for projects the holder can see.","parameters":[{"name":"id","in":"path","required":true,"description":"Project ID.","schema":{"type":"string","description":"Project ID.","format":"uuid"}}],"responses":{"200":{"description":"Read a project","content":{"application/json":{"example":{"project":{"id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","name":"Development","client":"Agency","active":true,"invoiceName":"Development services"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"patch":{"operationId":"project-update","tags":["Clients and projects"],"summary":"Change or archive a project","description":"Changes a project's name, client, description, budget or billing rate, or archives it (`active: false`) and brings it back. Send only what changes. A new client name joins the directory.\n\nAccess: Billing access for invoiceName alone. Other fields require a master key. In businesses that allow it, a personal key may assign the client (`client` only) on projects its holder can see.","parameters":[{"name":"id","in":"path","required":true,"description":"The project's ID.","schema":{"type":"string","description":"The project's ID.","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The new name.","example":"Recruiting Metrics Dashboard v2"},"client":{"type":"string","description":"The client's name; empty to remove the client."},"description":{"type":"string","description":"A note about the project; empty to clear."},"invoiceName":{"type":"string","description":"Customer-facing name, trimmed, max 120 characters; null clears it. Billing access required (master or personal owner/accountant, invoices service)."},"budget_hours":{"type":"number","description":"Budgeted hours; null to clear."},"hourly_rate":{"type":"number","description":"Billing rate per hour in the project's currency, e.g. 10.5; null to clear."},"currency":{"type":"string","description":"Three-letter currency code, e.g. TTD or USD."},"active":{"type":"boolean","description":"false archives the project (no new time on it, its hours stay); true brings it back."}}}}}},"responses":{"200":{"description":"Change or archive a project","content":{"application/json":{"example":{"project":{"id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","name":"Recruiting Metrics Dashboard v2","client":"21PG Recruiting","active":true,"invoiceName":null,"description":null,"budget_hours":null,"hourly_rate":10,"currency":"USD"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"delete":{"operationId":"project-delete","tags":["Clients and projects"],"summary":"Delete a project","description":"Deletes a project nobody tracked time on, with its tasks. A project with time can't be deleted: archive it instead (PATCH with `active: false`), so its hours stay with it.\n\nAccess: Master key. In businesses that allow it, a personal key may delete projects its holder can see.","parameters":[{"name":"id","in":"path","required":true,"description":"The project's ID.","schema":{"type":"string","description":"The project's ID.","format":"uuid"}}],"responses":{"200":{"description":"Delete a project","content":{"application/json":{"example":{"deleted":{"id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","name":"Website refresh","client":"Qosmos"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/me":{"get":{"operationId":"me","tags":["Account"],"summary":"Who the key belongs to","description":"The key's holder, their business, the key itself and, in plain words, what it can do. Includes billing: whether this key has billing access (role and invoices service). A good first call to check a key works.\n\nAccess: Any key.","responses":{"200":{"description":"Who the key belongs to","content":{"application/json":{"example":{"user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","name":"Kyle Mahadeo","business_id":"33b7145b-a41c-4d2d-a48d-7fc8121d36c2","billing":false,"key":{"id":"5d6e7f80-91a2-4b3c-8d4e-5f6a7b8c9d0e","name":"Claude on my laptop","kind":"personal","prefix":"pstt_live_zG1U"},"can":["add clients, projects and tasks","start, stop and describe your timer","add, correct and delete your own unapproved time","reports on all Staff time a manager has approved (read only, hours only)"]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/people":{"get":{"operationId":"people","tags":["Account"],"summary":"People reports cover","description":"The Staff whose time reports cover, for the `person` report filter. A master key gets everyone in the business, with their role.\n\nAccess: Any key. Personal keys get Staff only, without roles.\n\nWith a master key each person also has `role` (owner_admin, accountant, project_time_manager, staff…).","responses":{"200":{"description":"People reports cover","content":{"application/json":{"example":{"people":[{"user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","name":"Kyle Mahadeo","active":true}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/clients":{"get":{"operationId":"clients-list","tags":["Clients and projects"],"summary":"List clients","description":"The business's client directory, each client with the projects the key's holder can track time on. `id` is the directory entry's, for PATCH /clients/{id}; null for a client named only on a project.\n\nAccess: Any key.","responses":{"200":{"description":"List clients","content":{"application/json":{"example":{"clients":[{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"21PG Recruiting","projects":[{"id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","name":"Recruiting Metrics Dashboard"}]}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"post":{"operationId":"clients-create","tags":["Clients and projects"],"summary":"Add a client","description":"Adds a client to the directory, or returns the existing one with that name (ignoring case).\n\nAccess: Any key.\n\nReturns 200 with `created: false` when the client already exists.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The client's name, up to 200 characters.","example":"Qosmos"}},"required":["name"]}}}},"responses":{"201":{"description":"Add a client","content":{"application/json":{"example":{"client":{"name":"Qosmos"},"created":true}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/clients/{id}":{"patch":{"operationId":"client-update","tags":["Clients and projects"],"summary":"Change a client","description":"Changes a client in the directory. Send only what changes. Projects name their client, so a new name is carried onto the client's projects too.\n\nAccess: Master key only.","parameters":[{"name":"id","in":"path","required":true,"description":"The client's ID, from GET /clients.","schema":{"type":"string","description":"The client's ID, from GET /clients.","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The new name, up to 200 characters. Refused if another client has it.","example":"21PG Recruiting Ltd"},"email":{"type":"string","description":"Email address; empty to clear."},"phone":{"type":"string","description":"Phone number; empty to clear."},"address":{"type":"string","description":"Address; empty to clear."}}}}}},"responses":{"200":{"description":"Change a client","content":{"application/json":{"example":{"client":{"id":"4e5f6a7b-8c9d-4e0f-9a1b-2c3d4e5f6a7b","name":"21PG Recruiting Ltd","email":null,"phone":null,"address":null}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/projects":{"get":{"operationId":"projects-list","tags":["Clients and projects"],"summary":"List projects","description":"Active projects the key's holder can track time on; archived ones too with `archived=true`.\n\nAccess: Any key.","parameters":[{"name":"client","in":"query","required":false,"description":"Only this client's projects (exact name, ignoring case).","schema":{"type":"string","description":"Only this client's projects (exact name, ignoring case).","example":"21PG Recruiting"}},{"name":"archived","in":"query","required":false,"description":"true to include archived projects (`active: false`).","schema":{"type":"boolean","description":"true to include archived projects (`active: false`)."}}],"responses":{"200":{"description":"List projects","content":{"application/json":{"example":{"projects":[{"id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","name":"Recruiting Metrics Dashboard","client":"21PG Recruiting","active":true,"invoiceName":null}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"post":{"operationId":"projects-create","tags":["Clients and projects"],"summary":"Add a project","description":"Adds a project for the key's holder, as the tracker app does: its client joins the directory, it's shared with the holder's teams, and it gets a first task (`task`, or \"General\"). A project with that name the holder can already see is returned instead of a duplicate.\n\nAccess: Any key, whatever the holder's own page permissions.\n\nReturns 200 with `created: false` when the project already exists.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The project's name.","example":"Website refresh"},"client":{"type":"string","description":"The client's name; added to the directory if new.","example":"Qosmos"},"task":{"type":"string","description":"The first task's name. Defaults to \"General\".","example":"Design"},"description":{"type":"string","description":"A note about the project."}},"required":["name"]}}}},"responses":{"201":{"description":"Add a project","content":{"application/json":{"example":{"project":{"id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","name":"Website refresh","client":"Qosmos"},"created":true}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/projects/{id}/tasks":{"get":{"operationId":"tasks-list","tags":["Clients and projects"],"summary":"List a project's tasks","description":"The project's active tasks.\n\nAccess: Any key, for projects its holder can see.","parameters":[{"name":"id","in":"path","required":true,"description":"The project's ID.","schema":{"type":"string","description":"The project's ID.","format":"uuid"}}],"responses":{"200":{"description":"List a project's tasks","content":{"application/json":{"example":{"tasks":[{"id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","name":"General","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678"}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"post":{"operationId":"tasks-create","tags":["Clients and projects"],"summary":"Find or add a task","description":"Finds the task by name in the project, or adds it.\n\nAccess: Any key, for projects its holder can see.","parameters":[{"name":"id","in":"path","required":true,"description":"The project's ID.","schema":{"type":"string","description":"The project's ID.","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The task's name.","example":"Sourcing"}},"required":["name"]}}}},"responses":{"201":{"description":"Find or add a task","content":{"application/json":{"example":{"task":{"id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","name":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/teams":{"get":{"operationId":"teams-list","tags":["Teams"],"summary":"List teams","description":"The business's teams: each with its members (user IDs) and the projects its members can track time on.\n\nAccess: Master key only.","responses":{"200":{"description":"List teams","content":{"application/json":{"example":{"teams":[{"id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","name":"Developers","archived":false,"members":["1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10"],"projects":["a1b2c3d4-e5f6-4789-90ab-cdef12345678"]}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/teams/{id}/members":{"post":{"operationId":"team-member-add","tags":["Teams"],"summary":"Add a person to a team","description":"Adds a person to the team, so they can track time on the team's projects.\n\nAccess: Master key only.","parameters":[{"name":"id","in":"path","required":true,"description":"The team's ID, from GET /teams.","schema":{"type":"string","description":"The team's ID, from GET /teams.","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","description":"The person's user_id, from GET /people.","format":"uuid","example":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10"}},"required":["user_id"]}}}},"responses":{"200":{"description":"Add a person to a team","content":{"application/json":{"example":{"team_id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","member":true}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"delete":{"operationId":"team-member-remove","tags":["Teams"],"summary":"Take a person out of a team","description":"Takes a person out of the team. Their time stays.\n\nAccess: Master key only.","parameters":[{"name":"id","in":"path","required":true,"description":"The team's ID.","schema":{"type":"string","description":"The team's ID.","format":"uuid"}},{"name":"user_id","in":"query","required":true,"description":"The person's user_id.","schema":{"type":"string","description":"The person's user_id.","format":"uuid","example":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10"}}],"responses":{"200":{"description":"Take a person out of a team","content":{"application/json":{"example":{"team_id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","member":false}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/projects/{id}/teams":{"post":{"operationId":"project-team-add","tags":["Teams"],"summary":"Give a team a project","description":"Lets the team's members track time on the project.\n\nAccess: Master key only.","parameters":[{"name":"id","in":"path","required":true,"description":"The project's ID.","schema":{"type":"string","description":"The project's ID.","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"team_id":{"type":"string","description":"The team's ID, from GET /teams.","format":"uuid","example":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d"}},"required":["team_id"]}}}},"responses":{"200":{"description":"Give a team a project","content":{"application/json":{"example":{"project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","team_id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","access":true}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"delete":{"operationId":"project-team-remove","tags":["Teams"],"summary":"Take a project from a team","description":"Stops the team's members tracking new time on the project through the team. Time already tracked stays.\n\nAccess: Master key only.","parameters":[{"name":"id","in":"path","required":true,"description":"The project's ID.","schema":{"type":"string","description":"The project's ID.","format":"uuid"}},{"name":"team_id","in":"query","required":true,"description":"The team's ID.","schema":{"type":"string","description":"The team's ID.","format":"uuid","example":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d"}}],"responses":{"200":{"description":"Take a project from a team","content":{"application/json":{"example":{"project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","team_id":"6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d","access":false}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/timer":{"get":{"operationId":"timer-get","tags":["Timer"],"summary":"Running timers","description":"The key holder's running or paused timers (usually one).\n\nAccess: Any key, its holder's own timer; a master key anyone's with `user_id`.","parameters":[{"name":"user_id","in":"query","required":false,"description":"Master key only: this person's timers.","schema":{"type":"string","description":"Master key only: this person's timers.","format":"uuid"}}],"responses":{"200":{"description":"Running timers","content":{"application/json":{"example":{"running":[{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"running","started_at":"2026-10-05T09:00:00-04:00","ended_at":null,"seconds":1260,"hours":0.35,"description":"LinkedIn search","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"patch":{"operationId":"timer-update","tags":["Timer"],"summary":"Change the running timer","description":"Changes the running timer's description, billable flag or project without stopping it.\n\nAccess: Any key; always its holder's own timer.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"entry_id":{"type":"string","description":"Which running timer. Optional with one timer running; required when several are running (people allowed simultaneous timers).","format":"uuid"},"description":{"type":"string","description":"What the work is, up to 2,000 characters.","example":"LinkedIn search"},"billable":{"type":"boolean","description":"Billable to the client. Defaults to true.","example":true},"task_id":{"type":"string","description":"File the time under this task. Takes precedence over the fields below.","format":"uuid"},"project_id":{"type":"string","description":"The project, by ID. With `task` (a task name) or, for a project with one task, alone.","format":"uuid"},"project":{"type":"string","description":"The project, by its exact name (ignoring case). A name nobody has yet is added on the spot only when `client` is sent too, so a typo doesn't make a project."},"client":{"type":"string","description":"The client, by name. Only used with `project` to add a new project (and the client, if new)."},"task":{"type":"string","description":"A task name in the project: found, or added if it doesn't exist. Not needed when the project has one task."}}}}}},"responses":{"200":{"description":"Change the running timer","content":{"application/json":{"example":{"entry":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"running","started_at":"2026-10-05T09:00:00-04:00","ended_at":null,"seconds":9000,"hours":2.5,"description":"Homepage layout","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/timer/start":{"post":{"operationId":"timer-start","tags":["Timer"],"summary":"Start a timer","description":"Starts a timer for the key's holder. Say where the time goes with one of the ways below.\n\nAccess: Any key; always its holder's own timer.\n\nA business allows one running timer per person unless it has changed that; starting another while one runs is refused with 400.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"task_id":{"type":"string","description":"File the time under this task. Takes precedence over the fields below.","format":"uuid"},"project_id":{"type":"string","description":"The project, by ID. With `task` (a task name) or, for a project with one task, alone.","format":"uuid"},"project":{"type":"string","description":"The project, by its exact name (ignoring case). A name nobody has yet is added on the spot only when `client` is sent too, so a typo doesn't make a project."},"client":{"type":"string","description":"The client, by name. Only used with `project` to add a new project (and the client, if new)."},"task":{"type":"string","description":"A task name in the project: found, or added if it doesn't exist. Not needed when the project has one task."},"description":{"type":"string","description":"What the work is, up to 2,000 characters.","example":"LinkedIn search"},"billable":{"type":"boolean","description":"Billable to the client. Defaults to true.","example":true},"start":{"type":"string","description":"When it started, if not now. ISO 8601.","format":"date-time","example":"2026-10-05T09:00:00-04:00"},"client_ref":{"type":"string","description":"A UUID you choose. Sending the same one again returns the timer already started instead of starting another, so retries are safe.","format":"uuid"}}}}}},"responses":{"201":{"description":"Start a timer","content":{"application/json":{"example":{"entry":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"running","started_at":"2026-10-05T09:00:00-04:00","ended_at":null,"seconds":0,"hours":0,"description":"LinkedIn search","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/timer/stop":{"post":{"operationId":"timer-stop","tags":["Timer"],"summary":"Stop the timer","description":"Stops the running timer. The body is optional.\n\nAccess: Any key, its holder's own timer; a master key anyone's with `user_id`.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","description":"Master key only: stop this person's timer.","format":"uuid"},"entry_id":{"type":"string","description":"Which running timer. Optional with one timer running; required when several are running (people allowed simultaneous timers).","format":"uuid"},"end":{"type":"string","description":"When it ended, if not now. ISO 8601.","format":"date-time"},"description":{"type":"string","description":"Replace the description as it stops."}}}}}},"responses":{"200":{"description":"Stop the timer","content":{"application/json":{"example":{"entry":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"stopped","started_at":"2026-10-05T09:00:00-04:00","ended_at":"2026-10-05T11:30:00-04:00","seconds":9000,"hours":2.5,"description":"LinkedIn search","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/time-entries":{"get":{"operationId":"entries-list","tags":["Time entries"],"summary":"List time entries","description":"Time entries between two dates, newest first. Dates are business days in Trinidad.\n\nAccess: A personal key lists its holder's own entries; a master key anyone's.","parameters":[{"name":"start","in":"query","required":false,"description":"First day, YYYY-MM-DD.","schema":{"type":"string","description":"First day, YYYY-MM-DD.","format":"date","example":"2026-10-01"}},{"name":"end","in":"query","required":false,"description":"Last day, YYYY-MM-DD.","schema":{"type":"string","description":"Last day, YYYY-MM-DD.","format":"date","example":"2026-10-07"}},{"name":"project_id","in":"query","required":false,"description":"Only this project.","schema":{"type":"string","description":"Only this project.","format":"uuid"}},{"name":"user_id","in":"query","required":false,"description":"Master key only: one person's entries.","schema":{"type":"string","description":"Master key only: one person's entries.","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Only entries in this state.","schema":{"type":"string","description":"Only entries in this state.","enum":["running","paused","stopped"]}},{"name":"limit","in":"query","required":false,"description":"Most entries to return, 1 to 5,000. Defaults to 500.","schema":{"type":"integer","description":"Most entries to return, 1 to 5,000. Defaults to 500.","example":500}},{"name":"offset","in":"query","required":false,"description":"Entries to skip first, for the next page: send the number already read. Defaults to 0.","schema":{"type":"integer","description":"Entries to skip first, for the next page: send the number already read. Defaults to 0."}}],"responses":{"200":{"description":"List time entries","content":{"application/json":{"example":{"entries":[{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"stopped","started_at":"2026-10-05T09:00:00-04:00","ended_at":"2026-10-05T11:30:00-04:00","seconds":9000,"hours":2.5,"description":"LinkedIn search","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"post":{"operationId":"entries-create","tags":["Time entries"],"summary":"Add finished time","description":"Adds a finished block of time for the key's holder, as \"Add time\" does in the app. A master key adds it for someone else with `user_id`.\n\nAccess: Any key, its holder's own time; a master key anyone's.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","description":"Master key only: whose time it is (from GET /people). The person must track time on projects; the time can't be in the future.","format":"uuid"},"start":{"type":"string","description":"Start, ISO 8601 with the offset (Trinidad is -04:00).","format":"date-time","example":"2026-10-05T09:00:00-04:00"},"end":{"type":"string","description":"End, after the start.","format":"date-time","example":"2026-10-05T11:30:00-04:00"},"task_id":{"type":"string","description":"File the time under this task. Takes precedence over the fields below.","format":"uuid"},"project_id":{"type":"string","description":"The project, by ID. With `task` (a task name) or, for a project with one task, alone.","format":"uuid"},"project":{"type":"string","description":"The project, by its exact name (ignoring case). A name nobody has yet is added on the spot only when `client` is sent too, so a typo doesn't make a project."},"client":{"type":"string","description":"The client, by name. Only used with `project` to add a new project (and the client, if new)."},"task":{"type":"string","description":"A task name in the project: found, or added if it doesn't exist. Not needed when the project has one task."},"description":{"type":"string","description":"What the work is, up to 2,000 characters.","example":"LinkedIn search"},"billable":{"type":"boolean","description":"Billable to the client. Defaults to true.","example":true},"client_ref":{"type":"string","description":"A UUID you choose. Sending the same one again returns the entry already added instead of adding it twice, so retries are safe.","format":"uuid"}},"required":["start","end"]}}}},"responses":{"201":{"description":"Add finished time","content":{"application/json":{"example":{"entry":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"stopped","started_at":"2026-10-05T09:00:00-04:00","ended_at":"2026-10-05T11:30:00-04:00","seconds":9000,"hours":2.5,"description":"LinkedIn search","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/time-entries/{id}":{"get":{"operationId":"entry-get","tags":["Time entries"],"summary":"Get a time entry","description":"One time entry.\n\nAccess: A personal key its holder's own entries; a master key anyone's.","parameters":[{"name":"id","in":"path","required":true,"description":"The entry's ID.","schema":{"type":"string","description":"The entry's ID.","format":"uuid"}}],"responses":{"200":{"description":"Get a time entry","content":{"application/json":{"example":{"entry":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"stopped","started_at":"2026-10-05T09:00:00-04:00","ended_at":"2026-10-05T11:30:00-04:00","seconds":9000,"hours":2.5,"description":"LinkedIn search","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"patch":{"operationId":"entry-update","tags":["Time entries"],"summary":"Correct a time entry","description":"Changes an entry's description, billable flag, times or project. A corrected entry counts as one block from start to end. Send only what changes.\n\nAccess: A personal key its holder's own unapproved entries. A master key anyone's, approved time too. Time in a pay run is never changed.\n\nA master key correcting approved time rebuilds its payroll hours, so payroll sees the change.","parameters":[{"name":"id","in":"path","required":true,"description":"The entry's ID.","schema":{"type":"string","description":"The entry's ID.","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"description":{"type":"string","description":"What the work is, up to 2,000 characters.","example":"LinkedIn search"},"billable":{"type":"boolean","description":"Billable to the client. Defaults to true.","example":true},"start":{"type":"string","description":"New start, ISO 8601.","format":"date-time"},"end":{"type":"string","description":"New end, ISO 8601.","format":"date-time"},"task_id":{"type":"string","description":"File the time under this task. Takes precedence over the fields below.","format":"uuid"},"project_id":{"type":"string","description":"The project, by ID. With `task` (a task name) or, for a project with one task, alone.","format":"uuid"},"project":{"type":"string","description":"The project, by its exact name (ignoring case). A name nobody has yet is added on the spot only when `client` is sent too, so a typo doesn't make a project."},"client":{"type":"string","description":"The client, by name. Only used with `project` to add a new project (and the client, if new)."},"task":{"type":"string","description":"A task name in the project: found, or added if it doesn't exist. Not needed when the project has one task."}}}}}},"responses":{"200":{"description":"Correct a time entry","content":{"application/json":{"example":{"entry":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"stopped","started_at":"2026-10-05T09:00:00-04:00","ended_at":"2026-10-05T11:30:00-04:00","seconds":9000,"hours":2.5,"description":"LinkedIn search","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}},"delete":{"operationId":"entry-delete","tags":["Time entries"],"summary":"Delete a time entry","description":"Removes a wrong entry for good. Only unapproved time outside any pay run and without screenshots (screenshots are kept as evidence). Recorded in the business's audit log.\n\nAccess: A personal key its holder's own entries; a master key anyone's.","parameters":[{"name":"id","in":"path","required":true,"description":"The entry's ID.","schema":{"type":"string","description":"The entry's ID.","format":"uuid"}}],"responses":{"200":{"description":"Delete a time entry","content":{"application/json":{"example":{"deleted":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","started_at":"2026-10-05T09:00:00-04:00","ended_at":"2026-10-05T11:30:00-04:00","description":"LinkedIn search"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/time-entries/{id}/approve":{"post":{"operationId":"entry-approve","tags":["Time entries"],"summary":"Approve a time entry","description":"Approves a stopped entry, as Approve time does: it counts toward billable hours in reports and goes to payroll. An approver can't approve their own time unless they are the owner.\n\nAccess: Master key only.\n\nA running timer can't be approved; stop it first.","parameters":[{"name":"id","in":"path","required":true,"description":"The entry's ID.","schema":{"type":"string","description":"The entry's ID.","format":"uuid"}}],"responses":{"200":{"description":"Approve a time entry","content":{"application/json":{"example":{"entry":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"stopped","started_at":"2026-10-05T09:00:00-04:00","ended_at":"2026-10-05T11:30:00-04:00","seconds":9000,"hours":2.5,"description":"LinkedIn search","billable":true,"approved":true,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/time-entries/{id}/unapprove":{"post":{"operationId":"entry-unapprove","tags":["Time entries"],"summary":"Take an approval back","description":"Returns an approved entry to Needs approval and takes it out of payroll until it's approved again. Refused once its pay run is approved, finalized or paid; a draft pay run is marked to recompute.\n\nAccess: Master key only.","parameters":[{"name":"id","in":"path","required":true,"description":"The entry's ID.","schema":{"type":"string","description":"The entry's ID.","format":"uuid"}}],"responses":{"200":{"description":"Take an approval back","content":{"application/json":{"example":{"entry":{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","user_id":"1f2e3d4c-5b6a-4789-8a7b-6c5d4e3f2a10","status":"stopped","started_at":"2026-10-05T09:00:00-04:00","ended_at":"2026-10-05T11:30:00-04:00","seconds":9000,"hours":2.5,"description":"LinkedIn search","billable":true,"approved":false,"task_id":"c0a8012e-0d4b-4e3f-9b1a-7f6e5d4c3b2a","task":"Sourcing","project_id":"a1b2c3d4-e5f6-4789-90ab-cdef12345678","project":"Recruiting Metrics Dashboard","client":"21PG Recruiting","source":"api"}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/kiosk-shifts":{"get":{"operationId":"kiosk-shifts-list","tags":["Kiosk shifts"],"summary":"List kiosk shifts","description":"Clock-ins and clock-outs from the business's kiosks between two work dates, oldest first, with each worker's name, worked hours (breaks taken off), status and approval.\n\nAccess: Master key only.\n\n`missed_clock_out` shifts need a clock-out: correct them with PATCH /kiosk-shifts/{id}.","parameters":[{"name":"start","in":"query","required":true,"description":"First work date, YYYY-MM-DD.","schema":{"type":"string","description":"First work date, YYYY-MM-DD.","format":"date","example":"2026-10-05"}},{"name":"end","in":"query","required":true,"description":"Last work date, YYYY-MM-DD.","schema":{"type":"string","description":"Last work date, YYYY-MM-DD.","format":"date","example":"2026-10-11"}},{"name":"employee_id","in":"query","required":false,"description":"Only this worker's shifts.","schema":{"type":"string","description":"Only this worker's shifts.","format":"uuid"}},{"name":"status","in":"query","required":false,"description":"Only shifts in this state.","schema":{"type":"string","description":"Only shifts in this state.","enum":["open","on_break","closed","missed_clock_out"]}},{"name":"approved","in":"query","required":false,"description":"true or false: only approved, or only unapproved, shifts.","schema":{"type":"boolean","description":"true or false: only approved, or only unapproved, shifts."}},{"name":"limit","in":"query","required":false,"description":"Most shifts to return, 1 to 5,000. Defaults to 2,000.","schema":{"type":"integer","description":"Most shifts to return, 1 to 5,000. Defaults to 2,000."}}],"responses":{"200":{"description":"List kiosk shifts","content":{"application/json":{"example":{"shifts":[{"id":"9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","employee_id":"2b3c4d5e-6f70-4812-9a3b-4c5d6e7f8091","employee":"Ravi Persad","work_date":"2026-10-06","clock_in_at":"2026-10-06T11:58:00+00:00","clock_out_at":"2026-10-06T21:04:00+00:00","break_minutes":30,"worked_hours":8.6,"status":"closed","approved":false,"corrected":false,"note":null}]}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/kiosk-shifts/approve":{"post":{"operationId":"kiosk-shifts-approve","tags":["Kiosk shifts"],"summary":"Approve kiosk shifts","description":"Approves closed kiosk shifts for payroll, as the Kiosk shifts page does. Shifts already approved, still open or missing a clock-out are skipped, and so is the approver's own shift.\n\nAccess: Master key only.\n\n`approved` is how many shifts were approved by this call.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shift_ids":{"type":"array","description":"1 to 500 shift IDs.","example":["9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"]}},"required":["shift_ids"]}}}},"responses":{"200":{"description":"Approve kiosk shifts","content":{"application/json":{"example":{"approved":1}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/kiosk-shifts/{id}":{"patch":{"operationId":"kiosk-shift-update","tags":["Kiosk shifts"],"summary":"Correct a kiosk shift","description":"Changes a shift's clock-in, clock-out or break, with a note, and approves it; its payroll hours are rebuilt. Use it to fix a missed clock-out. Send only what changes.\n\nAccess: Master key only.\n\nNobody can correct or approve their own shift; an owner or accountant does it.","parameters":[{"name":"id","in":"path","required":true,"description":"The shift's ID.","schema":{"type":"string","description":"The shift's ID.","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"clock_in":{"type":"string","description":"New clock-in, ISO 8601 with the offset.","format":"date-time","example":"2026-10-06T08:00:00-04:00"},"clock_out":{"type":"string","description":"New clock-out, after the clock-in and not in the future. Required when the shift has none.","format":"date-time","example":"2026-10-06T17:00:00-04:00"},"break_minutes":{"type":"integer","description":"Total break minutes, shorter than the shift."},"note":{"type":"string","description":"Why it changed; shown on the Kiosk shifts page.","example":"Forgot to clock out; confirmed with the site lead."}}}}}},"responses":{"200":{"description":"Correct a kiosk shift","content":{"application/json":{"example":{"shift":{"id":"9c8b7a6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","employee_id":"2b3c4d5e-6f70-4812-9a3b-4c5d6e7f8091","employee":"Ravi Persad","work_date":"2026-10-06","clock_in_at":"2026-10-06T12:00:00+00:00","clock_out_at":"2026-10-06T21:00:00+00:00","break_minutes":30,"worked_hours":8.5,"status":"closed","approved":true,"corrected":true,"note":"Forgot to clock out; confirmed with the site lead."}}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}},"/reports":{"get":{"operationId":"reports","tags":["Reports"],"summary":"Time report","description":"Every Staff member's time for a client, project or period, as Time Reports shows it: totals, a summary by group, and every entry. Owners', accountants' and managers' own time is left out, unless a master key sends `scope=everyone`. A personal key reports approved time only, so its per-client reports are final; a master key reports every status.\n\nAccess: Any key, read only. Hours only; a master key also gets amounts, at each project's billing rate.\n\nBillable hours count approved time only; billable time not yet approved is `awaiting_approval_billable_hours`.\n\nWith a master key, `totals`, `summary` and `entries` also carry `amount` (and `totals.currency`) for projects with a billing rate (set once on the project). When the amounts are in more than one currency, `totals` and that `summary` group leave out `amount` and give `amounts_by_currency` instead.","parameters":[{"name":"billing","in":"query","required":false,"description":"Time-report billing filter; defaults to all. Unbilled/billed includes only billable time.","schema":{"type":"string","description":"Time-report billing filter; defaults to all. Unbilled/billed includes only billable time.","enum":["all","unbilled","billed"]}},{"name":"range","in":"query","required":false,"description":"A preset period. Weeks run Monday to Sunday. Or send start and end.","schema":{"type":"string","description":"A preset period. Weeks run Monday to Sunday. Or send start and end.","enum":["today","this_week","last_week","this_month","last_month","this_year"],"example":"last_week"}},{"name":"start","in":"query","required":false,"description":"First day, YYYY-MM-DD (instead of range).","schema":{"type":"string","description":"First day, YYYY-MM-DD (instead of range).","format":"date"}},{"name":"end","in":"query","required":false,"description":"Last day, YYYY-MM-DD (instead of range).","schema":{"type":"string","description":"Last day, YYYY-MM-DD (instead of range).","format":"date"}},{"name":"client","in":"query","required":false,"description":"A client's name. Repeat for several.","explode":true,"schema":{"type":"array","items":{"type":"string","description":"A client's name. Repeat for several.","example":"21PG Recruiting"}}},{"name":"project","in":"query","required":false,"description":"A project's ID. Repeat for several.","explode":true,"schema":{"type":"array","items":{"type":"string","description":"A project's ID. Repeat for several.","format":"uuid"}}},{"name":"person","in":"query","required":false,"description":"A person's user_id (from GET /people). Repeat for several.","explode":true,"schema":{"type":"array","items":{"type":"string","description":"A person's user_id (from GET /people). Repeat for several.","format":"uuid"}}},{"name":"task","in":"query","required":false,"description":"A task's ID. Repeat for several.","explode":true,"schema":{"type":"array","items":{"type":"string","description":"A task's ID. Repeat for several."}}},{"name":"description","in":"query","required":false,"description":"Entries whose description contains this text.","schema":{"type":"string","description":"Entries whose description contains this text."}},{"name":"billable","in":"query","required":false,"description":"Defaults to all.","schema":{"type":"string","description":"Defaults to all.","enum":["all","billable","non_billable"]}},{"name":"status","in":"query","required":false,"description":"Defaults to all.","schema":{"type":"string","description":"Defaults to all.","enum":["all","approved","unapproved"]}},{"name":"tab","in":"query","required":false,"description":"Which report, for CSV and PDF. Defaults to summary.","schema":{"type":"string","description":"Which report, for CSV and PDF. Defaults to summary.","enum":["summary","detailed","weekly"]}},{"name":"group","in":"query","required":false,"description":"Summary grouping. Defaults to project.","schema":{"type":"string","description":"Summary grouping. Defaults to project.","enum":["project","client","person","date","task","description"]}},{"name":"subgroup","in":"query","required":false,"description":"Summary sub-grouping. Defaults to description.","schema":{"type":"string","description":"Summary sub-grouping. Defaults to description.","enum":["project","client","person","date","task","description"]}},{"name":"format","in":"query","required":false,"description":"Defaults to json.","schema":{"type":"string","description":"Defaults to json.","enum":["json","csv","pdf"]}},{"name":"scope","in":"query","required":false,"description":"Master key only: `everyone` includes owners', accountants' and managers' own time, as the owner's Time Reports page does. Defaults to staff.","schema":{"type":"string","description":"Master key only: `everyone` includes owners', accountants' and managers' own time, as the owner's Time Reports page does. Defaults to staff.","enum":["staff","everyone"]}}],"responses":{"200":{"description":"Time report","content":{"application/json":{"example":{"range":{"start":"2026-09-28","end":"2026-10-04"},"filters":{"clients":["21PG Recruiting"],"projects":[],"people":[]},"totals":{"hours":41.5,"billable_hours":38,"awaiting_approval_billable_hours":3.5,"entries":27},"summary":[{"project":"Recruiting Metrics Dashboard","hours":22.25,"billable_hours":20,"awaiting_approval_billable_hours":2.25,"by_description":[{"description":"Sourcing","hours":9.5}]}],"entries":[{"id":"7b8f0c2e-3c4d-4a51-9a0e-2f1b6c9d8e71","date":"2026-10-01","start":"2026-10-01T13:00:00.000Z","end":"2026-10-01T15:30:00.000Z","person":"Kyle Mahadeo","client":"21PG Recruiting","project":"Recruiting Metrics Dashboard","task":"Sourcing","description":"LinkedIn search","billable":true,"approved":true,"hours":2.5}]}},"text/csv":{"schema":{"type":"string"}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/Error400"},"401":{"$ref":"#/components/responses/Error401"},"403":{"$ref":"#/components/responses/Error403"},"404":{"$ref":"#/components/responses/Error404"},"409":{"$ref":"#/components/responses/Error409"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Authorization: Bearer pstt_live_…"},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-Api-Key","description":"The same key in X-Api-Key, for tools built for Clockify."}},"schemas":{"TimeEntry":{"type":"object","properties":{"id":{"type":"string","description":"The entry's ID.","format":"uuid"},"user_id":{"type":"string","description":"Whose time it is.","format":"uuid"},"status":{"type":"string","description":"Timer state.","enum":["running","paused","stopped"]},"started_at":{"type":"string","description":"Start, ISO 8601.","format":"date-time"},"ended_at":{"type":"string","description":"End, ISO 8601; null while running.","format":"date-time"},"seconds":{"type":"integer","description":"Active seconds (paused time not counted)."},"hours":{"type":"number","description":"Active hours, two decimals."},"description":{"type":"string","description":"What the work was. Empty string when none."},"billable":{"type":"boolean","description":"Marked billable. Billable time counts only once a manager approves it."},"approved":{"type":"boolean","description":"Approved by a manager or accountant."},"task_id":{"type":"string","description":"The task the time is filed under; null when none.","format":"uuid"},"task":{"type":"string","description":"The task's name; null when none."},"project_id":{"type":"string","description":"The project; null when none.","format":"uuid"},"project":{"type":"string","description":"The project's name; null when none."},"client":{"type":"string","description":"The project's client; null when none."},"source":{"type":"string","description":"Where the entry came from: api, mobile_web (browser timer), desktop, kiosk, manual…"}}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","UNAUTHORIZED","API_OFF","FORBIDDEN","NOTHING_TO_BILL","CONFLICT","NOT_FOUND"]},"message":{"type":"string"}}}},"example":{"error":{"code":"INVALID_REQUEST","message":"Send start and end (ISO 8601, e.g. 2026-10-05T09:00:00-04:00)."}}}},"responses":{"Error400":{"description":"INVALID_REQUEST: The body or a parameter is missing or wrong. The message says what to fix.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Error401":{"description":"UNAUTHORIZED: No key, a malformed key, or a revoked key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Error403":{"description":"API_OFF: API keys aren't switched on for the business. FORBIDDEN: The key may not do this: someone else's time or a master-key-only endpoint with a personal key, approved time, time in a pay run, a holder who left the business, or a master key whose holder is no longer the owner.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Error409":{"description":"NOTHING_TO_BILL: No next service period or no billable invoice lines remain. CONFLICT: Only draft invoices can be changed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Error404":{"description":"NOT_FOUND: No such entry, project or task that the key's holder can see.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}