{"openapi":"3.1.0","info":{"title":"PATCH /api/v1/transactions/batch","version":"1.0.0","description":"Edit many transactions, a different change per row"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/transactions/batch":{"patch":{"tags":["transactions"],"summary":"Edit many transactions, a different change per row","description":"Apply a different edit to each of up to 500 transactions in one request: per row ``description`` (alias ``memo``), ``ledger_id`` (category), ``counterparty`` and ``review_status``. Every row is validated first; one invalid row rejects the whole request with 400 and nothing changes. Applying is then isolated per row, so a row that fails at write time is reported without undoing the others (``partial``). ``remember`` saves counterparty renames for the business, with the payment-processor guard. ``dry_run`` validates and reports per row without writing.","operationId":"bulk_edit_transactions","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-business-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Business-Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionBulkEditRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_TransactionBulkEditResponse_"}}}},"400":{"description":"Bad Request - Invalid input","content":{"application/json":{"example":{"error":"validation_error","message":"Validation failed: due_date: Input should be a valid date","errors":{"due_date":"Input should be a valid date"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Missing or expired token, an MCP OAuth token used against the REST API, or no access to this business","content":{"application/json":{"example":{"error":"unauthorized","message":"Authentication required","code":"AUTH_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient permissions","content":{"application/json":{"example":{"error":"forbidden","message":"You don't have permission to access this resource","code":"AUTH_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"validation_error","message":"Validation failed: amount: Input should be a valid integer","errors":{"amount":"Input should be a valid integer"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"}}}}},"x-fastapi-operation-id":"bulk_edit_transactions_api_v1_transactions_batch_patch"}}},"components":{"schemas":{"SuccessEnvelope_TransactionBulkEditResponse_":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"data":{"anyOf":[{"$ref":"#/components/schemas/TransactionBulkEditResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[TransactionBulkEditResponse]"},"TransactionBulkEditRequest":{"properties":{"rows":{"items":{"$ref":"#/components/schemas/TransactionBulkEditRow"},"type":"array","maxItems":500,"minItems":1,"title":"Rows","description":"Rows to edit (1-500), one transaction each."},"remember":{"type":"boolean","title":"Remember","description":"Applies to rows that set a counterparty: save each rename for the business (a saved rename) so its other transactions under the same name show it too. Payment processors that pay many parties (Gusto, ADP, PayPal, Stripe, ...) are still renamed on that row only. Default false: only the listed rows change.","default":false},"dry_run":{"type":"boolean","title":"Dry Run","description":"Validate every row and report what would change without writing anything.","default":false},"plan_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Hash","description":"The plan_hash a dry run of these same rows returned. When present, the edit is refused (VALIDATION_640) if the transactions or the rows no longer match that plan."}},"type":"object","required":["rows"],"title":"TransactionBulkEditRequest","description":"Request schema for ``PATCH /transactions/batch`` (a different edit per row)."},"TransactionBulkEditResponse":{"properties":{"dry_run":{"type":"boolean","title":"Dry Run","default":false},"total_requested":{"type":"integer","title":"Total Requested"},"total_updated":{"type":"integer","title":"Total Updated"},"total_failed":{"type":"integer","title":"Total Failed"},"partial":{"type":"boolean","title":"Partial","description":"True when some rows were applied and some were not.","default":false},"results":{"items":{"$ref":"#/components/schemas/TransactionBulkEditRowResult"},"type":"array","title":"Results"},"rows_renamed":{"type":"integer","title":"Rows Renamed","default":0},"aliases_created":{"type":"integer","title":"Aliases Created","default":0},"other_rows_relabelled":{"type":"integer","title":"Other Rows Relabelled","default":0},"warnings":{"items":{"type":"string"},"type":"array","title":"Warnings"},"plan_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Plan Hash","description":"Digest of the plan this call previewed or ran. Pass it as plan_hash on the apply to have it refused if the books moved in between."}},"type":"object","required":["total_requested","total_updated","total_failed"],"title":"TransactionBulkEditResponse","description":"Result of a per-row bulk edit: counts plus one entry per row."},"TransactionBulkEditRow":{"properties":{"transaction_id":{"type":"string","title":"Transaction Id","description":"Transaction UUID to edit."},"description":{"anyOf":[{"type":"string","maxLength":2000,"minLength":1},{"type":"null"}],"title":"Description","description":"New description / memo. Stamps the row as user-edited so a later bank sync or cleaner pass keeps the text."},"memo":{"anyOf":[{"type":"string","maxLength":2000,"minLength":1},{"type":"null"}],"title":"Memo","description":"Alias of description (one stored field)."},"ledger_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ledger Id","description":"Category ledger UUID to book the transaction to."},"counterparty":{"anyOf":[{"type":"string","maxLength":500,"minLength":1},{"type":"null"}],"title":"Counterparty","description":"Counterparty / merchant name to set on this transaction."},"review_status":{"anyOf":[{"type":"string","enum":["unreviewed","reviewed"]},{"type":"null"}],"title":"Review Status","description":"Set review status: 'unreviewed' or 'reviewed'."}},"type":"object","required":["transaction_id"],"title":"TransactionBulkEditRow","description":"One row of a per-row bulk edit: a transaction and what to change on it.\n\nOnly the fields that are set are changed. ``memo`` is the same stored text\nas ``description`` (a transaction has one description/memo field), so send\none or the other; if both are sent they must agree."},"TransactionBulkEditRowResult":{"properties":{"index":{"type":"integer","title":"Index","description":"Position of the row in the request."},"transaction_id":{"type":"string","title":"Transaction Id"},"success":{"type":"boolean","title":"Success"},"applied":{"items":{"type":"string"},"type":"array","title":"Applied","description":"Fields changed (or, on a dry run, that would change)."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Why the row was not applied."},"changes":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Changes","description":"Dry run only: the values the row would be given."},"before":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Before","description":"Dry run only: what the transaction holds now."}},"type":"object","required":["index","transaction_id","success"],"title":"TransactionBulkEditRowResult","description":"Outcome for one row of a per-row bulk edit."},"ErrorResponse":{"title":"ErrorResponse","type":"object","description":"Envelope returned by every non-2xx response. Branch on `code` (stable) rather than `message` (human-readable, may change).","required":["error","message","code"],"properties":{"error":{"type":"string","description":"Error category: `validation_error`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `rate_limited`, `server_error`, or `service_unavailable`."},"message":{"type":"string","description":"Human-readable explanation, safe to show to end users."},"code":{"type":"string","description":"Stable machine-readable code in `CATEGORY_NNN` form (e.g. `NOT_FOUND_006`, `AUTH_010`, `GEN_002`)."},"request_id":{"type":"string","description":"Correlation id for support requests. Echoes the `x-request-id` request header when one was supplied."},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Field-level validation messages keyed by field name. Present on 400/422 validation failures only."}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Token","description":"API token authentication. Format: `Bearer dz_...`"}}},"security":[{"BearerAuth":[]}]}