{"openapi":"3.1.0","info":{"title":"Tallyhand API","version":"v1","description":"Local-first freelance time-tracking & invoicing. All agent surfaces (REST, CLI, MCP) share these semantics: JSON envelope, bearer token auth, idempotent mutations, dry-run previews, and guard-railed deletes."},"servers":[{"url":"/api/v1","description":"Same-origin API base"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"clients"},{"name":"projects"},{"name":"tasks"},{"name":"expenses"},{"name":"invoices"},{"name":"recurring"},{"name":"retainers"},{"name":"scheduler"},{"name":"settings"},{"name":"meta"}],"paths":{"/mileage":{"get":{"summary":"List mileage","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Search"},{"$ref":"#/components/parameters/DateFrom"},{"$ref":"#/components/parameters/DateTo"},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"clientId","in":"query","schema":{"type":"string"}},{"name":"projectId","in":"query","schema":{"type":"string"}},{"name":"isBilled","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated envelope: { data, meta: { limit, nextCursor, total } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"post":{"summary":"Create a mileage record","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"date":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"miles":{"type":"number","exclusiveMinimum":0},"rate":{"type":"number","exclusiveMinimum":0},"purpose":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"origin":{"type":"string"},"destination":{"type":"string"},"vehicleNote":{"type":"string"},"isBilled":{"type":"boolean"},"invoiceId":{"type":"string","minLength":1},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["date","miles","purpose"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/mileage/{id}":{"parameters":[{"$ref":"#/components/parameters/Id"}],"get":{"summary":"Read a mileage record","responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"404":{"description":"Record not found"},"501":{"description":"The configured storage provider does not support this operation"}}},"patch":{"summary":"Update a mileage record","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"date":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"miles":{"type":"number","exclusiveMinimum":0},"rate":{"type":"number","exclusiveMinimum":0},"purpose":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"origin":{"type":"string"},"destination":{"type":"string"},"vehicleNote":{"type":"string"},"isBilled":{"type":"boolean"},"invoiceId":{"type":"string","minLength":1},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"delete":{"summary":"Delete a mileage record","parameters":[{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview; nothing deleted"},"204":{"description":"Deleted; no response body"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/mileage/bulk":{"post":{"summary":"Bulk create mileage","description":"Validate every item before creating any. Bookkeeping only; does not transfer money, file taxes or sign contracts.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["items"],"properties":{"items":{"type":"array","minItems":1,"maxItems":200,"items":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"date":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"miles":{"type":"number","exclusiveMinimum":0},"rate":{"type":"number","exclusiveMinimum":0},"purpose":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"origin":{"type":"string"},"destination":{"type":"string"},"vehicleNote":{"type":"string"},"isBilled":{"type":"boolean"},"invoiceId":{"type":"string","minLength":1},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["date","miles","purpose"],"additionalProperties":false}}}},{"type":"array","minItems":1,"maxItems":200,"items":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"date":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"miles":{"type":"number","exclusiveMinimum":0},"rate":{"type":"number","exclusiveMinimum":0},"purpose":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"origin":{"type":"string"},"destination":{"type":"string"},"vehicleNote":{"type":"string"},"isBilled":{"type":"boolean"},"invoiceId":{"type":"string","minLength":1},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["date","miles","purpose"],"additionalProperties":false}}]}}}},"responses":{"201":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/contracts":{"get":{"summary":"List contracts","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Search"},{"$ref":"#/components/parameters/DateFrom"},{"$ref":"#/components/parameters/DateTo"},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"clientId","in":"query","schema":{"type":"string"}},{"name":"type","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"archived","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated envelope: { data, meta: { limit, nextCursor, total } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"post":{"summary":"Create a contracts record","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"type":{"type":"string","enum":["sow","msa","nda","other"]},"title":{"type":"string","minLength":1},"startDate":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"endDate":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"renewalNoticeDays":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoRenew":{"type":"boolean"},"fileB64":{"type":"string"},"fileName":{"type":"string"},"notes":{"type":"string"},"archived":{"type":"boolean"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["clientId","type","title","startDate"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/contracts/{id}":{"parameters":[{"$ref":"#/components/parameters/Id"}],"get":{"summary":"Read a contracts record","responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"404":{"description":"Record not found"},"501":{"description":"The configured storage provider does not support this operation"}}},"patch":{"summary":"Update a contracts record","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"type":{"type":"string","enum":["sow","msa","nda","other"]},"title":{"type":"string","minLength":1},"startDate":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"endDate":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"renewalNoticeDays":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoRenew":{"type":"boolean"},"fileB64":{"type":"string"},"fileName":{"type":"string"},"notes":{"type":"string"},"archived":{"type":"boolean"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"delete":{"summary":"Delete a contracts record","parameters":[{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview; nothing deleted"},"204":{"description":"Deleted; no response body"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/contracts/bulk":{"post":{"summary":"Bulk create contracts","description":"Validate every item before creating any. Bookkeeping only; does not transfer money, file taxes or sign contracts.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["items"],"properties":{"items":{"type":"array","minItems":1,"maxItems":200,"items":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"type":{"type":"string","enum":["sow","msa","nda","other"]},"title":{"type":"string","minLength":1},"startDate":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"endDate":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"renewalNoticeDays":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoRenew":{"type":"boolean"},"fileB64":{"type":"string"},"fileName":{"type":"string"},"notes":{"type":"string"},"archived":{"type":"boolean"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["clientId","type","title","startDate"],"additionalProperties":false}}}},{"type":"array","minItems":1,"maxItems":200,"items":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"type":{"type":"string","enum":["sow","msa","nda","other"]},"title":{"type":"string","minLength":1},"startDate":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"endDate":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"renewalNoticeDays":{"type":"integer","minimum":0,"maximum":9007199254740991},"autoRenew":{"type":"boolean"},"fileB64":{"type":"string"},"fileName":{"type":"string"},"notes":{"type":"string"},"archived":{"type":"boolean"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["clientId","type","title","startDate"],"additionalProperties":false}}]}}}},"responses":{"201":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/tax-payments":{"get":{"summary":"List tax-payments","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Search"},{"$ref":"#/components/parameters/DateFrom"},{"$ref":"#/components/parameters/DateTo"},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"taxYear","in":"query","schema":{"type":"string"}},{"name":"quarter","in":"query","schema":{"type":"string"}},{"name":"jurisdiction","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated envelope: { data, meta: { limit, nextCursor, total } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"post":{"summary":"Create a tax-payments record","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"taxYear":{"type":"integer","minimum":2000,"maximum":2100},"quarter":{"anyOf":[{"type":"number","const":1},{"type":"number","const":2},{"type":"number","const":3},{"type":"number","const":4}]},"date":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"amount":{"type":"number","minimum":0},"jurisdiction":{"type":"string","enum":["federal","state"]},"method":{"type":"string"},"note":{"type":"string"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["taxYear","quarter","date","amount","jurisdiction"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/tax-payments/{id}":{"parameters":[{"$ref":"#/components/parameters/Id"}],"get":{"summary":"Read a tax-payments record","responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"404":{"description":"Record not found"},"501":{"description":"The configured storage provider does not support this operation"}}},"patch":{"summary":"Update a tax-payments record","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"taxYear":{"type":"integer","minimum":2000,"maximum":2100},"quarter":{"anyOf":[{"type":"number","const":1},{"type":"number","const":2},{"type":"number","const":3},{"type":"number","const":4}]},"date":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"amount":{"type":"number","minimum":0},"jurisdiction":{"type":"string","enum":["federal","state"]},"method":{"type":"string"},"note":{"type":"string"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"delete":{"summary":"Delete a tax-payments record","parameters":[{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview; nothing deleted"},"204":{"description":"Deleted; no response body"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/tax-payments/bulk":{"post":{"summary":"Bulk create tax-payments","description":"Validate every item before creating any. Bookkeeping only; does not transfer money, file taxes or sign contracts.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["items"],"properties":{"items":{"type":"array","minItems":1,"maxItems":200,"items":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"taxYear":{"type":"integer","minimum":2000,"maximum":2100},"quarter":{"anyOf":[{"type":"number","const":1},{"type":"number","const":2},{"type":"number","const":3},{"type":"number","const":4}]},"date":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"amount":{"type":"number","minimum":0},"jurisdiction":{"type":"string","enum":["federal","state"]},"method":{"type":"string"},"note":{"type":"string"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["taxYear","quarter","date","amount","jurisdiction"],"additionalProperties":false}}}},{"type":"array","minItems":1,"maxItems":200,"items":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"taxYear":{"type":"integer","minimum":2000,"maximum":2100},"quarter":{"anyOf":[{"type":"number","const":1},{"type":"number","const":2},{"type":"number","const":3},{"type":"number","const":4}]},"date":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"amount":{"type":"number","minimum":0},"jurisdiction":{"type":"string","enum":["federal","state"]},"method":{"type":"string"},"note":{"type":"string"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["taxYear","quarter","date","amount","jurisdiction"],"additionalProperties":false}}]}}}},"responses":{"201":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/rate-cards":{"get":{"summary":"List rate-cards","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Search"},{"$ref":"#/components/parameters/DateFrom"},{"$ref":"#/components/parameters/DateTo"},{"name":"sort","in":"query","schema":{"type":"string"}},{"name":"clientId","in":"query","schema":{"type":"string"}},{"name":"projectId","in":"query","schema":{"type":"string"}},{"name":"archived","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated envelope: { data, meta: { limit, nextCursor, total } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"post":{"summary":"Create a rate-cards record","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"defaultRate":{"type":"number","minimum":0},"lines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"rate":{"type":"number","minimum":0}},"required":["label","rate"],"additionalProperties":false}},"effectiveFrom":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"effectiveTo":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"archived":{"type":"boolean"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["clientId","name","defaultRate","effectiveFrom"],"additionalProperties":false}}}},"responses":{"201":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/rate-cards/{id}":{"parameters":[{"$ref":"#/components/parameters/Id"}],"get":{"summary":"Read a rate-cards record","responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"404":{"description":"Record not found"},"501":{"description":"The configured storage provider does not support this operation"}}},"patch":{"summary":"Update a rate-cards record","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"defaultRate":{"type":"number","minimum":0},"lines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"rate":{"type":"number","minimum":0}},"required":["label","rate"],"additionalProperties":false}},"effectiveFrom":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"effectiveTo":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"archived":{"type":"boolean"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"additionalProperties":false}}}},"responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"delete":{"summary":"Delete a rate-cards record","parameters":[{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview; nothing deleted"},"204":{"description":"Deleted; no response body"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/rate-cards/bulk":{"post":{"summary":"Bulk create rate-cards","description":"Validate every item before creating any. Bookkeeping only; does not transfer money, file taxes or sign contracts.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["items"],"properties":{"items":{"type":"array","minItems":1,"maxItems":200,"items":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"defaultRate":{"type":"number","minimum":0},"lines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"rate":{"type":"number","minimum":0}},"required":["label","rate"],"additionalProperties":false}},"effectiveFrom":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"effectiveTo":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"archived":{"type":"boolean"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["clientId","name","defaultRate","effectiveFrom"],"additionalProperties":false}}}},{"type":"array","minItems":1,"maxItems":200,"items":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"id":{"type":"string","minLength":1},"clientId":{"type":"string","minLength":1},"projectId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"defaultRate":{"type":"number","minimum":0},"lines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1},"label":{"type":"string","minLength":1},"rate":{"type":"number","minimum":0}},"required":["label","rate"],"additionalProperties":false}},"effectiveFrom":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"effectiveTo":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"archived":{"type":"boolean"},"createdAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]},"updatedAt":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"string","minLength":1}]}},"required":["clientId","name","defaultRate","effectiveFrom"],"additionalProperties":false}}]}}}},"responses":{"201":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/profile":{"get":{"summary":"Identify the authenticated workspace","responses":{"200":{"description":"{ data: { id, name } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/capabilities":{"get":{"tags":["meta"],"summary":"Discover caller authorization, operations and backend limits","description":"Bearer-authenticated discovery. Reports current verified OAuth scopes or API-token permissions and the caller role; allowedNow is bounded by the backend and role. Session credential controls and provider consent never become bearer grants. Every operation still validates authorization and inputs.","responses":{"200":{"description":"Capability envelope","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AgentCapabilities"}}}}}},"401":{"description":"Invalid credentials"},"403":{"description":"OAuth tally:read required"}}}},"/controls":{"get":{"tags":["meta"],"summary":"Discover a secure control and its interaction requirements","parameters":[{"name":"control","in":"query","required":true,"schema":{"type":"string","enum":["account","api_keys","users","payments","invoice_pdf","offline_data","install_pwa","notifications"]}}],"responses":{"200":{"description":"{ data: { control, url, instructions, requiresUserInteraction: true, interaction, agentCanNavigate: true, requiresHumanConsent, requiredPermission, bearerApiAvailable, agentInstructions } }. Browser navigation may be performed by an authorized agent; external and device consent remain enforced."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Invalid credentials"},"403":{"description":"OAuth tally:read required"}}}},"/data":{"get":{"summary":"Export an atomic cloud workspace backup","description":"Convex only. Returns the bundle and revision; credentials are never exported.","responses":{"200":{"description":"{ data: { bundle, revision, resetRevokesApiKeys } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"post":{"summary":"Replace or reset cloud workspace data","description":"Requires explicit user approval and a saved backup at the current revision. Import replaces rather than merges. Reset revokes sharing links and all personal API keys atomically; import preserves API keys. Convex only; 4 MiB backup / 4,000 document limits. A backend lacking atomic key revocation rejects reset with 503.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["action","expectedRevision","confirmation","bundle"],"properties":{"action":{"const":"import"},"expectedRevision":{"type":"integer","minimum":0},"confirmation":{"const":"REPLACE CLOUD DATA"},"bundle":{"type":"object","description":"A validated Tallyhand bundle from GET /data"}}},{"type":"object","required":["action","expectedRevision","confirmation"],"properties":{"action":{"const":"reset"},"expectedRevision":{"type":"integer","minimum":0},"confirmation":{"const":"RESET CLOUD DATA AND API KEYS"}}}]}}}},"responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"409":{"description":"Workspace changed after backup"},"413":{"description":"Backup exceeds size limit"},"501":{"description":"The configured storage provider does not support this operation"},"503":{"description":"Cloud backend unavailable or atomic reset contract not deployed"}}}},"/dunning/run":{"post":{"summary":"Preview or run overdue reminder and late-fee actions","description":"Use dry_run=true to preview without changes. Actual runs require explicit user approval. OAuth previews must use the query parameter to request only tally:read.","parameters":[{"$ref":"#/components/parameters/DryRun"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"dryRun":{"type":"boolean"},"invoiceIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/share-links":{"get":{"summary":"List owned public share links","responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}},"post":{"summary":"Create a public invoice, timesheet or estimate link","description":"Obtain sharing approval first. Invoice shares reference a persisted invoice; arbitrary invoice snapshots are rejected.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"object","required":["type","target","confirmPublicSharing"],"properties":{"type":{"const":"invoice"},"target":{"type":"object","required":["invoiceId"],"properties":{"invoiceId":{"type":"string","minLength":1}}},"confirmPublicSharing":{"const":true},"expiresInDays":{"type":"integer","minimum":1,"maximum":365,"default":30}}},{"type":"object","required":["type","target","confirmPublicSharing"],"properties":{"type":{"const":"timesheet"},"target":{"type":"object","required":["clientId","weekStartMs"],"properties":{"clientId":{"type":"string","minLength":1},"weekStartMs":{"type":"integer"}}},"confirmPublicSharing":{"const":true},"expiresInDays":{"type":"integer","minimum":1,"maximum":365,"default":30}}},{"type":"object","required":["type","target","confirmPublicSharing"],"properties":{"type":{"const":"estimate"},"target":{"type":"object","required":["snapshot"],"properties":{"snapshot":{"type":"object"}}},"confirmPublicSharing":{"const":true},"expiresInDays":{"type":"integer","minimum":1,"maximum":365,"default":30}}}]}}}},"responses":{"201":{"description":"{ data: { id, url, pdfUrl, expiresAt, type } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/share-links/{id}":{"delete":{"summary":"Revoke an owned share link","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"{ data: { revoked: true, id } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/share-links/{id}/approvals":{"get":{"summary":"List approvals for an owned timesheet link","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Success envelope: { data }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid credentials"},"403":{"description":"Insufficient scope or workspace role"},"501":{"description":"The configured storage provider does not support this operation"}}}},"/onboarding":{"get":{"tags":["meta"],"summary":"Inspect task-specific workspace readiness","description":"Time tracking requires no business settings. Invoicing readiness recommends a seller business.name. Missing configuration includes exact fields and machine-actionable steps with required inputs; readiness does not grant authorization or replace operation validation.","parameters":[{"name":"intent","in":"query","schema":{"type":"string","enum":["time_tracking","invoicing"],"default":"time_tracking"}}],"responses":{"200":{"description":"Readiness envelope","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OnboardingReadiness"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Invalid credentials"},"403":{"description":"OAuth tally:read required"}}},"post":{"tags":["settings"],"summary":"Configure existing workspace settings for onboarding","description":"Apply the existing validated settings patch. Requires tally:write for OAuth; viewer writes remain forbidden. Cookie sessions require x-tallyhand-sync: 1. dryRun=true in the body or dry_run=true in the query validates without mutation or consuming an idempotency key. Nested settings merge with persisted values; no data is fabricated.","parameters":[{"$ref":"#/components/parameters/DryRun"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["settings"],"properties":{"settings":{"$ref":"#/components/schemas/SettingsPatch"},"intent":{"type":"string","enum":["time_tracking","invoicing"]},"dryRun":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Applied: { data: readiness plus settings }; preview: { data: { dryRun: true, valid: true, patch, warnings: [] } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Invalid credentials"},"403":{"description":"Insufficient scope or read-only role"},"503":{"description":"Settings unavailable"}}}},"/changes":{"get":{"tags":["meta"],"summary":"Poll the authenticated workspace change revision","description":"Owner-scoped Convex only. Lightweight revision polling; not a change-event stream.","parameters":[{"name":"since","in":"query","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"{ data: { available: true, revision, changed: boolean|null, pollAfterMs: 2000 } }"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Invalid credentials"},"403":{"description":"Insufficient scope"},"501":{"description":"RECOVERY_UNAVAILABLE: requires Convex storage"}}}},"/requests/{key}":{"get":{"tags":["meta"],"summary":"Inspect an authenticated owner's idempotency receipt","description":"Convex only. Metadata does not disclose stored response bodies. A pending receipt requires reconciliation before retrying a mutation.","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ data: { state: pending|complete, status: number|null, createdAt, requiresReconciliation } }"},"401":{"description":"Invalid credentials"},"403":{"description":"Insufficient scope"},"404":{"description":"REQUEST_NOT_FOUND"},"501":{"description":"RECOVERY_UNAVAILABLE: requires Convex storage"}}}},"/api-tokens":{"get":{"summary":"List the signed-in user's API tokens without secret hashes","description":"Requires a signed-in browser session; existing credential controls apply.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}}},"post":{"summary":"Create a personal API token; raw secret returned once","description":"Session only. Agent may use the authorized secure browser UI. Bearer credentials cannot mint new credentials. Unavailable in single-user mode.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":64}}}}}}}},"/api-tokens/{id}":{"delete":{"summary":"Revoke the signed-in user's API token","description":"Requires a signed-in browser session; existing credential controls apply.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}},"parameters":[{"$ref":"#/components/parameters/Id"}]}},"/oauth-clients":{"get":{"summary":"List OAuth integration clients","description":"Clerk OAuth session only; bearer credentials rejected.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}}},"post":{"summary":"Create an OAuth integration client","description":"Clerk OAuth session only; same-origin writes. Existing provider configuration validation and credential controls apply.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}}}},"/oauth-clients/{id}":{"post":{"summary":"Rotate an OAuth client secret","description":"Clerk OAuth session only; same-origin write; returned secret requires secure handling.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}},"parameters":[{"$ref":"#/components/parameters/Id"}]},"delete":{"summary":"Revoke an OAuth integration client","description":"Clerk OAuth session only; same-origin write.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}},"parameters":[{"$ref":"#/components/parameters/Id"}]}},"/stripe/connect/start":{"get":{"summary":"Start Stripe Connect authorization","description":"Session only; redirects to provider consent. Provider and encryption configuration required.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}}}},"/stripe/connect/callback":{"get":{"summary":"Complete Stripe Connect callback","description":"Session and verified provider state required; provider callback, not a general agent action.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}}}},"/stripe/connect/status":{"get":{"summary":"Read Stripe Connect availability and connection status","description":"Session only; Convex does not support Stripe Connect.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}}}},"/stripe/connect":{"delete":{"summary":"Disconnect Stripe Connect","description":"Session only; requires x-tallyhand-sync: 1.","x-authentication":"session","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}}}},"/stripe/payment-links":{"post":{"summary":"Create a Stripe Checkout link for an existing sent invoice","description":"Shared/personal API token only; OAuth excluded. Requires configured Stripe account and encryption. Creating a payment link does not execute a payment.","x-authentication":"api_token","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}},"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["invoiceId"],"properties":{"invoiceId":{"type":"string","minLength":1},"cancelUrl":{"type":"string","format":"uri"}}}}}}}},"/stripe/webhook":{"post":{"summary":"Receive a signed Stripe provider webhook","x-authentication":"provider_signature","security":[],"description":"Requires verified stripe-signature; not a caller workspace operation.","responses":{"200":{"description":"Webhook processed"},"400":{"description":"Invalid signature or webhook"}}}},"/sync/status":{"get":{"summary":"Probe encrypted sync availability","security":[],"description":"Public availability probe; private session details only for the caller's own session.","responses":{"200":{"description":"{ data: { signedIn, storage, syncSupported, userId?, cloudCount?, vaultError? } }"}}}},"/sync/pull":{"get":{"summary":"Pull encrypted workspace entities","description":"Encrypted-sync backend required. Accepts session or shared/personal API token; OAuth excluded.","x-authentication":"session_or_api_token","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}},"parameters":[{"name":"since","in":"query","schema":{"type":"number"}},{"name":"types","in":"query","schema":{"type":"string"}},{"name":"afterId","in":"query","schema":{"type":"string"}}]}},"/sync/push":{"post":{"summary":"Push validated encrypted workspace entities","description":"Encrypted-sync backend required. Session or shared/personal API token; OAuth excluded. Requires x-tallyhand-sync: 1; viewers cannot write.","x-authentication":"session_or_api_token","security":[{"sessionCookie":[]}],"responses":{"200":{"description":"Success"},"401":{"description":"Session required"},"403":{"description":"Permission or origin denied"},"503":{"description":"Provider unavailable"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["entities"],"properties":{"entities":{"type":"array","maxItems":5000,"items":{"type":"object","description":"Encrypted entity validated by the sync route"}}}}}}}}},"/health":{"get":{"tags":["meta"],"security":[],"summary":"Health check (no auth)","responses":{"200":{"description":"ok","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}}}},"/openapi.json":{"get":{"tags":["meta"],"security":[],"summary":"This OpenAPI document (no auth)","responses":{"200":{"description":"OpenAPI 3.1 document"}}}},"/clients":{"get":{"tags":["clients"],"summary":"List clients","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/Search"},{"name":"includeArchived","in":"query","schema":{"type":"boolean"},"description":"Include archived clients. Alias: include_archived."},{"name":"sort","in":"query","schema":{"type":"string"},"description":"One of: name, -name, createdAt, -createdAt."}],"responses":{"200":{"description":"Client list","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Client"}}}}]}}}}}},"post":{"tags":["clients"],"summary":"Create a client","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Client"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/clients/{id}":{"get":{"tags":["clients"],"summary":"Get a client","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Client","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Client"}}}]}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["clients"],"summary":"Patch a client","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientPatch"}}}},"responses":{"200":{"description":"Updated client"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["clients"],"summary":"Delete a client","description":"Refused with 409 (error.details has projectCount, invoiceCount, expenseCount, scheduleCount, retainerCount) while related records exist. Supports ?dry_run=true.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview"},"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/projects":{"get":{"tags":["projects"],"summary":"List projects","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"clientId","in":"query","schema":{"type":"string"},"description":"Filter by client. Alias: client_id."},{"$ref":"#/components/parameters/Search"},{"name":"includeArchived","in":"query","schema":{"type":"boolean"},"description":"Alias: include_archived."},{"name":"sort","in":"query","schema":{"type":"string"},"description":"One of: name, -name, createdAt, -createdAt."}],"responses":{"200":{"description":"Project list"}}},"post":{"tags":["projects"],"summary":"Create a project","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectInput"}}}},"responses":{"201":{"description":"Created"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{id}":{"get":{"tags":["projects"],"summary":"Get a project","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Project"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["projects"],"summary":"Patch a project","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectPatch"}}}},"responses":{"200":{"description":"Updated project"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["projects"],"summary":"Delete a project","description":"Refused with 409 while tasks, expenses, or recurring schedules reference it. Supports ?dry_run=true.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview"},"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/tasks":{"get":{"tags":["tasks"],"summary":"List time entries","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"projectId","in":"query","schema":{"type":"string"},"description":"Alias: project_id."},{"name":"clientId","in":"query","schema":{"type":"string"},"description":"Alias: client_id."},{"name":"isBilled","in":"query","schema":{"type":"boolean"},"description":"Alias: is_billed."},{"$ref":"#/components/parameters/DateFrom"},{"$ref":"#/components/parameters/DateTo"},{"name":"sort","in":"query","schema":{"type":"string"},"description":"One of: startAt, -startAt, endAt, -endAt, durationMinutes, -durationMinutes, name, -name, createdAt, -createdAt."}],"responses":{"200":{"description":"Task list"}}},"post":{"tags":["tasks"],"summary":"Create a time entry","description":"endAt: 0 (or omitted) means an open/running timer. Otherwise endAt must be >= startAt.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskInput"},"examples":{"closed":{"value":{"projectId":"prj_1","name":"Design review","startAt":1758326400000,"endAt":1758330000000,"durationMinutes":60,"tags":["design"]}},"openTimer":{"value":{"projectId":"prj_1","name":"Deep work","startAt":1758330000000,"endAt":0}}}}}},"responses":{"201":{"description":"Created"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/tasks/{id}":{"get":{"tags":["tasks"],"summary":"Get a time entry","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Task"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["tasks"],"summary":"Patch a time entry","description":"Same endAt rule as create: 0 = open timer, otherwise endAt >= startAt. Accepts Idempotency-Key.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskPatch"}}}},"responses":{"200":{"description":"Updated task"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["tasks"],"summary":"Delete a time entry","description":"Refused with 409 when the task is billed. Supports ?dry_run=true.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview"},"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/tasks/bulk":{"post":{"tags":["tasks"],"summary":"Bulk-create time entries (max 200)","description":"Accepts { items: [...] } or a bare JSON array. Every item is validated before the first write (schema + projectId exists + endAt rule); any failure returns 400 with per-index details and creates nothing. The whole batch shares one Idempotency-Key.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["items"],"properties":{"items":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/TaskInput"}}}},{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/TaskInput"}}]}}}},"responses":{"201":{"description":"All items created"},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/expenses":{"get":{"tags":["expenses"],"summary":"List expenses","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"clientId","in":"query","schema":{"type":"string"},"description":"Alias: client_id."},{"name":"projectId","in":"query","schema":{"type":"string"},"description":"Alias: project_id."},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"isBilled","in":"query","schema":{"type":"boolean"},"description":"Alias: is_billed."},{"$ref":"#/components/parameters/DateFrom"},{"$ref":"#/components/parameters/DateTo"},{"name":"sort","in":"query","schema":{"type":"string"},"description":"One of: date, -date, amount, -amount, category, -category, createdAt, -createdAt."}],"responses":{"200":{"description":"Expense list"}}},"post":{"tags":["expenses"],"summary":"Create an expense","description":"Money is in dollars (amount, rate); only *Cents fields are integer cents.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpenseInput"}}}},"responses":{"201":{"description":"Created"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/expenses/{id}":{"get":{"tags":["expenses"],"summary":"Get an expense","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Expense"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["expenses"],"summary":"Patch an expense","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExpensePatch"}}}},"responses":{"200":{"description":"Updated expense"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["expenses"],"summary":"Delete an expense","description":"Refused with 409 when the expense is billed. Supports ?dry_run=true.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview"},"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/expenses/bulk":{"post":{"tags":["expenses"],"summary":"Bulk-create expenses (max 200)","description":"Same validated-before-write contract as /tasks/bulk. Accepts { items: [...] } or a bare JSON array.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"anyOf":[{"type":"object","required":["items"],"properties":{"items":{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/ExpenseInput"}}}},{"type":"array","maxItems":200,"items":{"$ref":"#/components/schemas/ExpenseInput"}}]}}}},"responses":{"201":{"description":"All items created"},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/invoices":{"get":{"tags":["invoices"],"summary":"List invoices","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"clientId","in":"query","schema":{"type":"string"},"description":"Alias: client_id."},{"name":"status","in":"query","schema":{"type":"string","enum":["draft","sent","paid"]}},{"name":"overdue","in":"query","schema":{"type":"boolean"},"description":"Only sent invoices past their due date."},{"$ref":"#/components/parameters/DateFrom"},{"$ref":"#/components/parameters/DateTo"},{"name":"sort","in":"query","schema":{"type":"string"},"description":"One of: issueDate, -issueDate, dueDate, -dueDate, total, -total, invoiceNumber, -invoiceNumber, createdAt, -createdAt."}],"responses":{"200":{"description":"Invoice list"}}},"post":{"tags":["invoices"],"summary":"Draft an invoice","description":"Always creates a draft. An initial status other than draft/omitted is rejected with 400. Line items may reference tasks/expenses via sourceType + sourceId; those are marked billed only when the invoice is sent.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceInput"},"examples":{"draft":{"value":{"clientId":"cli_1","issueDate":1758326400000,"dueDate":1760918400000,"lineItems":[{"description":"Design work","quantity":10,"rate":150,"sourceType":"task","sourceId":"tsk_1"}]}}}}}},"responses":{"201":{"description":"Draft created"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/invoices/{id}":{"get":{"tags":["invoices"],"summary":"Get an invoice","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Invoice"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["invoices"],"summary":"Patch a draft invoice (never status)","description":"status is not an accepted field — use /send and /paid. Accepts Idempotency-Key.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoicePatch"}}}},"responses":{"200":{"description":"Updated invoice"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["invoices"],"summary":"Delete a draft invoice","description":"Only drafts can be deleted (409 otherwise). Deleting a draft unclaims its billed tasks/expenses. Supports ?dry_run=true.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview"},"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/invoices/{id}/pdf":{"get":{"tags":["invoices"],"summary":"Download the saved invoice PDF without changing its status","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Invoice PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"Invoice not found"}}}},"/invoices/{id}/share":{"post":{"tags":["invoices"],"summary":"Enable or disable the invoice's cloud link","description":"Enabled links expose the saved invoice and PDF to anyone with the URL. Disable revokes previous links. Re-enable issues a fresh link.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Sharing state and shareUrl/pdfUrl"},"404":{"description":"Invoice not found"}}}},"/invoices/{id}/send":{"post":{"tags":["invoices"],"summary":"Send an invoice (draft -> sent)","description":"Marks referenced tasks/expenses billed. Refused with 409 on a paid invoice. Supports ?dry_run=true (previews wouldSetStatus and wouldMarkBilled without mutating).","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Sent"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/invoices/{id}/paid":{"post":{"tags":["invoices"],"summary":"Mark an invoice paid (sent -> paid)","description":"Requires sent status first (409 on draft). Supports ?dry_run=true.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Marked paid"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/recurring-schedules":{"get":{"tags":["recurring"],"summary":"List recurring schedules","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"status","in":"query","schema":{"type":"string","enum":["active","paused","ended"]}},{"name":"clientId","in":"query","schema":{"type":"string"},"description":"Alias: client_id."},{"name":"sort","in":"query","schema":{"type":"string"},"description":"One of: nextRunAt, -nextRunAt, name, -name, createdAt, -createdAt."}],"responses":{"200":{"description":"Schedule list"}}},"post":{"tags":["recurring"],"summary":"Create a recurring schedule","description":"mode=fixed generates a draft invoice each run; mode=unbilled sweeps unbilled tasks/expenses for the client into a draft.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringScheduleInput"}}}},"responses":{"201":{"description":"Created"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/recurring-schedules/{id}":{"get":{"tags":["recurring"],"summary":"Get a recurring schedule","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Schedule"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["recurring"],"summary":"Patch a recurring schedule","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecurringSchedulePatch"}}}},"responses":{"200":{"description":"Updated schedule"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["recurring"],"summary":"Delete a recurring schedule","description":"Refused with 409 while a retainer references it. Supports ?dry_run=true.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview"},"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/recurring-schedules/{id}/run":{"post":{"tags":["recurring"],"summary":"Force-run one schedule","description":"Generates now even if not due. Supports ?dry_run=true (previews wouldCreateInvoice and occurrence counts).","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Run result"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/retainers":{"get":{"tags":["retainers"],"summary":"List retainers","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"status","in":"query","schema":{"type":"string","enum":["active","paused","depleted","ended"]}},{"name":"clientId","in":"query","schema":{"type":"string"},"description":"Alias: client_id."},{"name":"type","in":"query","schema":{"type":"string","enum":["prepaid-hours","monthly-fee"]}},{"name":"sort","in":"query","schema":{"type":"string"},"description":"One of: startDate, -startDate, name, -name, createdAt, -createdAt."}],"responses":{"200":{"description":"Retainer list"}}},"post":{"tags":["retainers"],"summary":"Create a retainer","description":"amountCents is integer cents (unlike the rest of the API, which uses dollars). Optional recurringScheduleId links an auto-renewing retainer to its schedule.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetainerInput"}}}},"responses":{"201":{"description":"Created"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/retainers/{id}":{"get":{"tags":["retainers"],"summary":"Get a retainer","parameters":[{"$ref":"#/components/parameters/Id"}],"responses":{"200":{"description":"Retainer"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["retainers"],"summary":"Patch a retainer","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetainerPatch"}}}},"responses":{"200":{"description":"Updated retainer"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["retainers"],"summary":"Delete a retainer","description":"Supports ?dry_run=true.","parameters":[{"$ref":"#/components/parameters/Id"},{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Dry-run preview"},"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/scheduler/run":{"post":{"tags":["scheduler"],"summary":"Run due recurring schedules","description":"Generates draft invoices for every due schedule and advances their nextRunAt. Safe to call repeatedly — nothing due means nothing generated. Supports ?dry_run=true (previews due schedules without creating or advancing).","parameters":[{"$ref":"#/components/parameters/DryRun"}],"responses":{"200":{"description":"Run results","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"data":{"type":"object","properties":{"generated":{"type":"array","items":{"type":"string"},"description":"Invoice ids created this run."},"results":{"type":"array","items":{"type":"object","properties":{"scheduleId":{"type":"string"},"invoiceId":{"type":["string","null"]},"nextRunAt":{"type":"number"},"occurrences":{"type":"number"}}}},"dryRun":{"type":"boolean"},"due":{"type":"array","description":"Dry-run preview entries.","items":{"type":"object","properties":{"scheduleId":{"type":"string"},"wouldCreateInvoice":{"type":"boolean"},"occurrences":{"type":"number"}}}}}}}}]}}}}}}},"/settings":{"get":{"tags":["settings"],"summary":"Get server settings","responses":{"200":{"description":"Settings"}}},"patch":{"tags":["settings"],"summary":"Update server settings","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/DryRun"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettingsPatch"}}}},"responses":{"200":{"description":"Updated settings"},"400":{"$ref":"#/components/responses/BadRequest"}}}}},"components":{"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"tally_session","description":"Authenticated browser session (builtin cookie or Clerk-managed session); bearer credentials do not substitute."},"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"opaque","description":"Personal API key or resource-bound OAuth access token. Hosted OAuth requires resource <origin>/api/mcp and tally:read/write/manage scopes. Browser-session JWTs are not OAuth access tokens."}},"parameters":{"Id":{"name":"id","in":"path","required":true,"schema":{"type":"string"}},"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor; follow meta.nextCursor until null."},"Search":{"name":"search","in":"query","schema":{"type":"string"},"description":"Case-insensitive substring match on name (and email/notes where present)."},"DateFrom":{"name":"date_from","in":"query","schema":{"type":"string"},"description":"ms epoch or ISO-8601; invalid values -> 400."},"DateTo":{"name":"date_to","in":"query","schema":{"type":"string"},"description":"ms epoch or ISO-8601; invalid values -> 400."},"DryRun":{"name":"dry_run","in":"query","schema":{"type":"boolean"},"description":"Preview only: returns what would happen without mutating. Dry runs never consume idempotency keys."},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Optional client token (uuid recommended). Replaying a key returns the stored response without re-executing."}},"responses":{"BadRequest":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"Lifecycle or referential guard refused the change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing/invalid bearer token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Envelope":{"type":"object","properties":{"meta":{"type":"object","properties":{"nextCursor":{"type":["string","null"]},"limit":{"type":"number"}}}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","api_disabled","bad_request","not_found","conflict","validation"]},"message":{"type":"string"},"details":{"description":"Per-index or per-relation details."}}}}},"Health":{"type":"object","properties":{"data":{"type":"object","properties":{"status":{"type":"string"}}}}},"Client":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"address":{"type":"string"},"defaultRate":{"type":"number","description":"Dollars per hour."},"notes":{"type":"string"},"archived":{"type":"boolean"},"createdAt":{"type":"number"},"updatedAt":{"type":"number"}}},"ClientInput":{"type":"object","required":["name"],"properties":{"id":{"type":"string","description":"Optional custom id."},"name":{"type":"string"},"email":{"type":"string","format":"email"},"address":{"type":"string"},"defaultRate":{"type":"number","minimum":0},"notes":{"type":"string"},"archived":{"type":"boolean"}}},"ClientPatch":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"address":{"type":"string"},"defaultRate":{"type":"number","minimum":0},"notes":{"type":"string"},"archived":{"type":"boolean"}}},"Project":{"type":"object","properties":{"id":{"type":"string"},"clientId":{"type":"string"},"name":{"type":"string"},"rateOverride":{"type":"number","description":"Dollars per hour."},"archived":{"type":"boolean"},"createdAt":{"type":"number"},"updatedAt":{"type":"number"}}},"ProjectInput":{"type":"object","required":["clientId","name"],"properties":{"id":{"type":"string"},"clientId":{"type":"string"},"name":{"type":"string"},"rateOverride":{"type":"number","minimum":0},"archived":{"type":"boolean"}}},"ProjectPatch":{"type":"object","properties":{"clientId":{"type":"string"},"name":{"type":"string"},"rateOverride":{"type":"number","minimum":0},"archived":{"type":"boolean"}}},"Task":{"type":"object","properties":{"id":{"type":"string"},"projectId":{"type":"string"},"name":{"type":"string"},"startAt":{"type":"number","description":"ms epoch."},"endAt":{"type":"number","description":"ms epoch; 0 = open/running timer."},"durationMinutes":{"type":"number"},"notes":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"isBilled":{"type":"boolean"},"invoiceId":{"type":"string"},"createdAt":{"type":"number"},"updatedAt":{"type":"number"}}},"TaskInput":{"type":"object","required":["projectId","name","startAt","endAt"],"properties":{"id":{"type":"string"},"projectId":{"type":"string"},"name":{"type":"string"},"startAt":{"type":"number"},"endAt":{"type":"number","description":"0 = open timer; otherwise >= startAt."},"durationMinutes":{"type":"number","minimum":0},"notes":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"isBilled":{"type":"boolean"}}},"TaskPatch":{"type":"object","properties":{"projectId":{"type":"string"},"name":{"type":"string"},"startAt":{"type":"number"},"endAt":{"type":"number"},"durationMinutes":{"type":"number","minimum":0},"notes":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"isBilled":{"type":"boolean"}}},"Expense":{"type":"object","properties":{"id":{"type":"string"},"clientId":{"type":"string"},"projectId":{"type":"string"},"date":{"type":"number","description":"ms epoch."},"amount":{"type":"number","description":"Dollars."},"category":{"type":"string"},"note":{"type":"string"},"receiptB64":{"type":"string"},"isBilled":{"type":"boolean"},"invoiceId":{"type":"string"},"createdAt":{"type":"number"},"updatedAt":{"type":"number"}}},"ExpenseInput":{"type":"object","required":["date","amount","category"],"properties":{"id":{"type":"string"},"clientId":{"type":"string"},"projectId":{"type":"string"},"date":{"type":"number"},"amount":{"type":"number","minimum":0},"category":{"type":"string"},"note":{"type":"string"},"receiptB64":{"type":"string"},"isBilled":{"type":"boolean"}}},"ExpensePatch":{"type":"object","properties":{"clientId":{"type":"string"},"projectId":{"type":"string"},"date":{"type":"number"},"amount":{"type":"number","minimum":0},"category":{"type":"string"},"note":{"type":"string"},"receiptB64":{"type":"string"},"isBilled":{"type":"boolean"}}},"LineItem":{"type":"object","properties":{"id":{"type":"string"},"description":{"type":"string"},"quantity":{"type":"number"},"rate":{"type":"number","description":"Dollars."},"amount":{"type":"number"},"markupPercent":{"type":"number"},"sourceType":{"type":"string","enum":["task","expense","manual"]},"sourceId":{"type":"string"},"taxRate":{"type":"number","description":"Per-line tax rate, percent."},"taxLabel":{"type":"string"}}},"LineItemInput":{"type":"object","required":["description","quantity","rate"],"properties":{"id":{"type":"string"},"description":{"type":"string"},"quantity":{"type":"number","minimum":0},"rate":{"type":"number","minimum":0},"amount":{"type":"number","minimum":0},"markupPercent":{"type":"number"},"sourceType":{"type":"string","enum":["task","expense","manual"]},"sourceId":{"type":"string"},"taxRate":{"type":"number","minimum":0,"maximum":100},"taxLabel":{"type":"string"}}},"Invoice":{"type":"object","properties":{"cloudLinkEnabled":{"type":"boolean","description":"Cloud link enabled by default on create; false keeps the invoice private."},"shareUrl":{"type":["string","null"],"description":"Canonical public URL on create/get/update responses."},"pdfUrl":{"type":["string","null"],"description":"Direct public PDF URL; null when sharing is disabled or unavailable."},"id":{"type":"string"},"clientId":{"type":"string"},"invoiceNumber":{"type":"string"},"issueDate":{"type":"number"},"dueDate":{"type":"number"},"status":{"type":"string","enum":["draft","sent","paid"]},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItem"}},"subtotal":{"type":"number"},"total":{"type":"number","description":"Dollars."},"notes":{"type":"string"},"publicToken":{"type":"string"},"currency":{"type":"string","description":"ISO 4217 code."},"taxRegion":{"type":"string","enum":["US","EU"]},"sellerTaxId":{"type":"string"},"sellerTaxIdLabel":{"type":"string"},"buyerTaxId":{"type":"string"},"sellerEmailVisible":{"type":"boolean"},"buyerEmailVisible":{"type":"boolean"},"serviceStart":{"type":"number"},"serviceEnd":{"type":"number"},"invoiceType":{"type":"string"},"paymentMethod":{"type":"string"},"paymentUrl":{"type":"string"},"bankAccount":{"type":"string"},"swiftBic":{"type":"string"},"qrEnabled":{"type":"boolean"},"qrPayload":{"type":"string"},"qrDescription":{"type":"string"},"amountInWords":{"type":"boolean"},"template":{"type":"string","enum":["default","stripe"]},"createdAt":{"type":"number"},"updatedAt":{"type":"number"}}},"InvoiceInput":{"type":"object","required":["clientId","issueDate","lineItems"],"properties":{"cloudLinkEnabled":{"type":"boolean","description":"Cloud link enabled by default on create; false keeps the invoice private."},"id":{"type":"string"},"clientId":{"type":"string"},"invoiceNumber":{"type":"string"},"issueDate":{"type":"number"},"dueDate":{"type":"number"},"status":{"type":"string","enum":["draft","sent","paid"],"description":"Only draft (or omitted) is accepted on create."},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItemInput"}},"subtotal":{"type":"number","minimum":0},"total":{"type":"number","minimum":0},"notes":{"type":"string"},"publicToken":{"type":"string"},"currency":{"type":"string","description":"ISO 4217 code. Defaults to the workspace default currency."},"taxRegion":{"type":"string","enum":["US","EU"],"description":"US = sales tax, Letter. EU = VAT, A4, SEPA QR."},"sellerTaxId":{"type":"string"},"sellerTaxIdLabel":{"type":"string"},"buyerTaxId":{"type":"string"},"sellerEmailVisible":{"type":"boolean"},"buyerEmailVisible":{"type":"boolean"},"serviceStart":{"type":"number"},"serviceEnd":{"type":"number"},"invoiceType":{"type":"string"},"paymentMethod":{"type":"string"},"paymentUrl":{"type":"string"},"bankAccount":{"type":"string","description":"IBAN for EU invoices."},"swiftBic":{"type":"string"},"qrEnabled":{"type":"boolean"},"qrPayload":{"type":"string"},"qrDescription":{"type":"string"},"amountInWords":{"type":"boolean"},"template":{"type":"string","enum":["default","stripe"]}}},"InvoicePatch":{"type":"object","description":"status is not accepted here — use /send and /paid.","properties":{"cloudLinkEnabled":{"type":"boolean","description":"Cloud link enabled by default on create; false keeps the invoice private."},"clientId":{"type":"string"},"invoiceNumber":{"type":"string"},"issueDate":{"type":"number"},"dueDate":{"type":"number"},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItemInput"}},"subtotal":{"type":"number","minimum":0},"total":{"type":"number","minimum":0},"notes":{"type":"string"},"currency":{"type":"string"},"taxRegion":{"type":"string","enum":["US","EU"]},"sellerTaxId":{"type":"string"},"sellerTaxIdLabel":{"type":"string"},"buyerTaxId":{"type":"string"},"sellerEmailVisible":{"type":"boolean"},"buyerEmailVisible":{"type":"boolean"},"serviceStart":{"type":"number"},"serviceEnd":{"type":"number"},"invoiceType":{"type":"string"},"paymentMethod":{"type":"string"},"paymentUrl":{"type":"string"},"bankAccount":{"type":"string"},"swiftBic":{"type":"string"},"qrEnabled":{"type":"boolean"},"qrPayload":{"type":"string"},"qrDescription":{"type":"string"},"amountInWords":{"type":"boolean"},"template":{"type":"string","enum":["default","stripe"]}}},"RecurringSchedule":{"type":"object","properties":{"id":{"type":"string"},"clientId":{"type":"string"},"projectId":{"type":"string"},"name":{"type":"string"},"mode":{"type":"string","enum":["fixed","unbilled"]},"frequency":{"type":"string","enum":["weekly","monthly","quarterly","yearly"]},"interval":{"type":"number"},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItem"}},"startDate":{"type":"number"},"endDate":{"type":"number"},"maxOccurrences":{"type":"number"},"occurrences":{"type":"number"},"nextRunAt":{"type":"number"},"lastRunAt":{"type":"number"},"status":{"type":"string","enum":["active","paused","ended"]},"notes":{"type":"string"},"createdAt":{"type":"number"},"updatedAt":{"type":"number"}}},"RecurringScheduleInput":{"type":"object","required":["clientId","name","mode","frequency","interval","lineItems","startDate"],"properties":{"id":{"type":"string"},"clientId":{"type":"string"},"projectId":{"type":"string"},"name":{"type":"string"},"mode":{"type":"string","enum":["fixed","unbilled"]},"frequency":{"type":"string","enum":["weekly","monthly","quarterly","yearly"]},"interval":{"type":"number","minimum":1},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItemInput"}},"startDate":{"type":"number"},"endDate":{"type":"number"},"maxOccurrences":{"type":"number","minimum":1},"status":{"type":"string","enum":["active","paused","ended"]},"notes":{"type":"string"}}},"RecurringSchedulePatch":{"type":"object","properties":{"clientId":{"type":"string"},"projectId":{"type":"string"},"name":{"type":"string"},"mode":{"type":"string","enum":["fixed","unbilled"]},"frequency":{"type":"string","enum":["weekly","monthly","quarterly","yearly"]},"interval":{"type":"number","minimum":1},"lineItems":{"type":"array","items":{"$ref":"#/components/schemas/LineItemInput"}},"startDate":{"type":"number"},"endDate":{"type":"number"},"maxOccurrences":{"type":"number","minimum":1},"status":{"type":"string","enum":["active","paused","ended"]},"notes":{"type":"string"}}},"Retainer":{"type":"object","properties":{"id":{"type":"string"},"clientId":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["prepaid-hours","monthly-fee"]},"totalHours":{"type":"number"},"amountCents":{"type":"number","description":"Integer cents."},"hourlyRate":{"type":"number"},"startDate":{"type":"number"},"endDate":{"type":"number"},"status":{"type":"string","enum":["active","paused","depleted","ended"]},"recurringScheduleId":{"type":"string"},"notes":{"type":"string"},"createdAt":{"type":"number"},"updatedAt":{"type":"number"}}},"RetainerInput":{"type":"object","required":["clientId","name","type","amountCents","startDate"],"properties":{"id":{"type":"string"},"clientId":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["prepaid-hours","monthly-fee"]},"totalHours":{"type":"number","minimum":0},"amountCents":{"type":"number","minimum":0,"description":"Integer cents."},"hourlyRate":{"type":"number","minimum":0},"startDate":{"type":"number"},"endDate":{"type":"number"},"status":{"type":"string","enum":["active","paused","depleted","ended"]},"recurringScheduleId":{"type":"string"},"notes":{"type":"string"}}},"RetainerPatch":{"type":"object","properties":{"clientId":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["prepaid-hours","monthly-fee"]},"totalHours":{"type":"number","minimum":0},"amountCents":{"type":"number","minimum":0},"hourlyRate":{"type":"number","minimum":0},"startDate":{"type":"number"},"endDate":{"type":"number"},"status":{"type":"string","enum":["active","paused","depleted","ended"]},"recurringScheduleId":{"type":"string"},"notes":{"type":"string"}}},"OnboardingReadiness":{"type":"object","required":["intent","ready","readinessByIntent","missingConfiguration","nextSteps","defaults","note"],"properties":{"intent":{"type":"string","enum":["time_tracking","invoicing"]},"ready":{"type":"boolean"},"readinessByIntent":{"type":"object","properties":{"time_tracking":{"type":"boolean"},"invoicing":{"type":"boolean"}}},"missingConfiguration":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"reason":{"type":"string"},"blocking":{"type":"boolean"},"step":{"type":"string"}}}},"nextSteps":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"method":{"type":"string"},"path":{"type":"string"},"bodyTemplate":{"type":"object"},"requiresInput":{"type":"array","items":{"type":"string"}},"requiredScope":{"type":"string"}}}},"defaults":{"type":"object","properties":{"currency":{"type":"string"},"invoicePrefix":{"type":"string"},"paymentTermsDays":{"type":"number"}}},"note":{"type":"string"}}},"AgentCapabilities":{"type":"object","required":["workspaceApi","extensions","paymentExecution","secureControls","surfaces","caller","backend","operations","note"],"properties":{"workspaceApi":{"const":true},"paymentExecution":{"const":false},"extensions":{"type":"object","additionalProperties":{"type":"boolean"}},"secureControls":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/AgentControl"}},"surfaces":{"type":"object","additionalProperties":{"type":"string"}},"caller":{"type":"object","properties":{"authentication":{"type":"string","enum":["oauth","api_token"]},"scopes":{"type":"array","items":{"type":"string"}},"role":{"type":"string"},"canWriteWorkspace":{"type":"boolean"},"canManageWorkspace":{"type":"boolean"}}},"backend":{"type":"object","properties":{"storage":{"type":"string"},"browserLocalDataAccessible":{"const":false},"paymentExecution":{"const":false},"extensions":{"type":"object"}}},"operations":{"type":"array","items":{"type":"object","properties":{"operationId":{"type":"string"},"method":{"type":"string"},"path":{"type":"string"},"summary":{"type":"string"},"requiredScope":{"type":"string"},"authentication":{"type":"string","enum":["workspace","public","session","api_token","session_or_api_token","provider_signature"]},"dryRun":{"type":"boolean"},"dryRunRequiredScope":{"type":["string","null"]},"dryRunAllowedNow":{"type":"boolean"},"idempotency":{"type":"boolean"},"allowedNow":{"type":"boolean"},"supportedByBackend":{"type":"boolean"},"authorizationReason":{"type":"string"}}}},"note":{"type":"string"}}},"AgentControl":{"type":"object","properties":{"path":{"type":"string"},"reason":{"type":"string"},"interaction":{"type":"string","enum":["agent_browser","external_consent","device_permission"]},"permission":{"type":"string"},"requiresHumanConsent":{"type":"boolean"},"bearerApiAvailable":{"type":"boolean"},"agentCanNavigate":{"const":true},"instructions":{"type":"string"}}},"SettingsPatch":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"business":{"type":"object","properties":{"name":{"type":"string"},"ownerName":{"type":"string"},"email":{"description":"One primary business contact address; use billingEmails for additional invoice-display contacts","anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","const":""}]},"billingEmails":{"description":"Additional email addresses displayed on invoices. Does not add message recipients.","maxItems":10,"type":"array","items":{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}},"address":{"type":"string"},"taxId":{"type":"string"},"paymentInstructions":{"type":"string"}},"additionalProperties":false},"invoice":{"type":"object","properties":{"numberPrefix":{"type":"string"},"nextNumber":{"type":"integer","minimum":0,"maximum":9007199254740991},"logoB64":{"type":"string"},"accentColor":{"type":"string"},"footerText":{"type":"string"},"paymentTermsDays":{"type":"integer","minimum":0,"maximum":9007199254740991},"defaultCurrency":{"type":"string","pattern":"^[A-Z]{3}$"},"defaultTaxRegion":{"type":"string","enum":["US","EU"]},"defaultTaxRate":{"type":"number","minimum":0,"maximum":100},"taxIdLabel":{"type":"string"},"defaultPaymentMethod":{"type":"string"},"amountInWordsDefault":{"type":"boolean"}},"additionalProperties":false},"reckoning":{"type":"object","properties":{"enabled":{"type":"boolean"},"dayOfWeek":{"type":"integer","minimum":0,"maximum":6},"hourOfDay":{"type":"integer","minimum":0,"maximum":23},"lastCompletedAtMs":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"expenseCategories":{"type":"array","items":{"type":"string"}},"appearance":{"type":"object","properties":{"theme":{"type":"string","enum":["light","dark","system"]}},"additionalProperties":false},"dunning":{"type":"object","properties":{"enabled":{"type":"boolean"},"reminderDays":{"type":"array","items":{"type":"integer","minimum":0,"maximum":9007199254740991}},"escalatingTone":{"type":"boolean"},"lateFee":{"type":"object","properties":{"enabled":{"type":"boolean"},"type":{"type":"string","enum":["flat","percent"]},"amount":{"type":"number","minimum":0},"graceDays":{"type":"integer","minimum":0,"maximum":9007199254740991},"recurring":{"type":"string","enum":["once","monthly"]},"maxTotal":{"type":"number","minimum":0}},"additionalProperties":false}},"additionalProperties":false},"tax":{"type":"object","properties":{"setAsidePercent":{"type":"number","minimum":0,"maximum":1}},"additionalProperties":false},"analytics":{"type":"object","properties":{"weeklyBillableTargetHours":{"type":"number","minimum":0},"monthlyRevenueTarget":{"type":"number","minimum":0}},"additionalProperties":false},"pluginSettings":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}}}},"additionalProperties":false}}}}