{"openapi":"3.1.0","info":{"title":"SaaSFlow API","version":"1.0.0"},"components":{"schemas":{"MetricFormulaNode":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["metric"]},"name":{"type":"string","enum":["mrr","arr","revenue","cogs","cac","pd","ga","newCustomers","grossProfit","grossProfitMargin","operatingProfit","operatingProfitMargin","cacPerNewCustomer","mrrPerCustomer","mrrPerNewCustomer","cacPaybackPeriod","churnRate","churnRate12m","lifetimeInMonths","lifetimeValue","netRevenueRetention","grossRevenueRetention","netRevenueRetentionMonthly","grossRevenueRetentionMonthly","mrrGrowthRate","mrrGrowthRateAnnual","ruleOf40","netBurn","grossBurn","runway","burnMultiple"]},"monthOffset":{"type":"integer"}},"required":["kind","name"]},{"type":"object","properties":{"kind":{"type":"string","enum":["input"]},"name":{"type":"string","enum":["cancelledMrr","mrrAtMonthStart","newCustomerMrr","customerCountAtMonthEnd","cashInflows","cashOutflows","cashBalanceAtMonthEnd","annualCohortStartMrr","annualCohortEndMrr","annualCohortEndMrrCapped","monthlyCohortStartMrr","monthlyCohortEndMrr","monthlyCohortEndMrrCapped"]}},"required":["kind","name"]},{"type":"object","properties":{"kind":{"type":"string","enum":["constant"]},"value":{"type":"number"}},"required":["kind","value"]},{"type":"object","properties":{"kind":{"type":"string","enum":["sum"]},"terms":{"type":"array","items":{"type":"object","properties":{"sign":{"anyOf":[{"type":"number","enum":[1]},{"type":"number","enum":[-1]}]},"node":{"$ref":"#/components/schemas/MetricFormulaNode"}},"required":["sign","node"]}}},"required":["kind","terms"]},{"type":"object","properties":{"kind":{"type":"string","enum":["multiply"]},"operands":{"type":"array","items":{"$ref":"#/components/schemas/MetricFormulaNode"}}},"required":["kind","operands"]},{"type":"object","properties":{"kind":{"type":"string","enum":["divide"]},"numerator":{"$ref":"#/components/schemas/MetricFormulaNode"},"denominator":{"$ref":"#/components/schemas/MetricFormulaNode"}},"required":["kind","numerator","denominator"]},{"type":"object","properties":{"kind":{"type":"string","enum":["power"]},"base":{"$ref":"#/components/schemas/MetricFormulaNode"},"exponent":{"$ref":"#/components/schemas/MetricFormulaNode"}},"required":["kind","base","exponent"]},{"type":"object","properties":{"kind":{"type":"string","enum":["aggregate"]},"aggregation":{"type":"string","enum":["mean","sum"]},"of":{"$ref":"#/components/schemas/MetricFormulaNode"},"window":{"type":"object","properties":{"months":{"type":"integer","minimum":0,"exclusiveMinimum":true},"endingAt":{"type":"string","enum":["previousMonth","lastCompleteMonth"]}},"required":["months","endingAt"]},"skipZeroMonths":{"type":"boolean","enum":[true]}},"required":["kind","aggregation","of","window"]}]},"AnnotatedMetricFormulaNode":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["metric"]},"name":{"type":"string","enum":["mrr","arr","revenue","cogs","cac","pd","ga","newCustomers","grossProfit","grossProfitMargin","operatingProfit","operatingProfitMargin","cacPerNewCustomer","mrrPerCustomer","mrrPerNewCustomer","cacPaybackPeriod","churnRate","churnRate12m","lifetimeInMonths","lifetimeValue","netRevenueRetention","grossRevenueRetention","netRevenueRetentionMonthly","grossRevenueRetentionMonthly","mrrGrowthRate","mrrGrowthRateAnnual","ruleOf40","netBurn","grossBurn","runway","burnMultiple"]},"monthOffset":{"type":"integer"},"month":{"type":"string"},"value":{"type":"number","nullable":true}},"required":["kind","name","month","value"]},{"type":"object","properties":{"kind":{"type":"string","enum":["input"]},"name":{"type":"string","enum":["cancelledMrr","mrrAtMonthStart","newCustomerMrr","customerCountAtMonthEnd","cashInflows","cashOutflows","cashBalanceAtMonthEnd","annualCohortStartMrr","annualCohortEndMrr","annualCohortEndMrrCapped","monthlyCohortStartMrr","monthlyCohortEndMrr","monthlyCohortEndMrrCapped"]},"value":{"type":"number","nullable":true}},"required":["kind","name","value"]},{"type":"object","properties":{"kind":{"type":"string","enum":["constant"]},"value":{"type":"number"}},"required":["kind","value"]},{"type":"object","properties":{"kind":{"type":"string","enum":["sum"]},"terms":{"type":"array","items":{"type":"object","properties":{"sign":{"anyOf":[{"type":"number","enum":[1]},{"type":"number","enum":[-1]}]},"node":{"$ref":"#/components/schemas/AnnotatedMetricFormulaNode"}},"required":["sign","node"]}},"value":{"type":"number","nullable":true}},"required":["kind","terms","value"]},{"type":"object","properties":{"kind":{"type":"string","enum":["multiply"]},"operands":{"type":"array","items":{"$ref":"#/components/schemas/AnnotatedMetricFormulaNode"}},"value":{"type":"number","nullable":true}},"required":["kind","operands","value"]},{"type":"object","properties":{"kind":{"type":"string","enum":["divide"]},"numerator":{"$ref":"#/components/schemas/AnnotatedMetricFormulaNode"},"denominator":{"$ref":"#/components/schemas/AnnotatedMetricFormulaNode"},"value":{"type":"number","nullable":true}},"required":["kind","numerator","denominator","value"]},{"type":"object","properties":{"kind":{"type":"string","enum":["power"]},"base":{"$ref":"#/components/schemas/AnnotatedMetricFormulaNode"},"exponent":{"$ref":"#/components/schemas/AnnotatedMetricFormulaNode"},"value":{"type":"number","nullable":true}},"required":["kind","base","exponent","value"]},{"type":"object","properties":{"kind":{"type":"string","enum":["aggregate"]},"aggregation":{"type":"string","enum":["mean","sum"]},"of":{"$ref":"#/components/schemas/MetricFormulaNode"},"window":{"type":"object","properties":{"months":{"type":"integer","minimum":0,"exclusiveMinimum":true},"endingAt":{"type":"string","enum":["previousMonth","lastCompleteMonth"]}},"required":["months","endingAt"]},"skipZeroMonths":{"type":"boolean","enum":[true]},"months":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string"},"value":{"type":"number","nullable":true}},"required":["month","value"]}},"value":{"type":"number","nullable":true}},"required":["kind","aggregation","of","window","months","value"]}]}},"parameters":{}},"paths":{"/companies":{"get":{"description":"List the companies the authenticated caller can reach, with their `role` in each and the effective `permissions`. Start here when you do not have a `companyId`: every other endpoint is scoped to one company. A `permissions` of null means the role preset applies rather than an explicit allowlist. An API key scoped to a single company sees only that one.","responses":{"200":{"description":"List of companies the user has access to","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"firestoreId":{"type":"string","nullable":true},"name":{"type":"string","description":"The name of the company."},"baseCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The base currency for the company."},"lastOverallUpdatedTime":{"type":"string","format":"date-time","description":"When any data within the company was last updated."},"plShowPlanningPercentageForPastMonths":{"type":"boolean","description":"Whether to show planning percentages for past months in P&L."},"plShowAllCategories":{"type":"boolean","description":"Whether to show all categories in P&L, including empty ones."},"plRecognizeRevenueOnInvoice":{"type":"boolean"},"dataStartDate":{"type":"string","nullable":true,"description":"The company-wide data start date. Imports skip transactions dated before it. Null means no start date is set."},"valuationCategory":{"type":"string","nullable":true,"enum":["traditional","aiEnhanced","aiNative",null],"description":"Self-declared company category used by the valuation estimate. Null means traditional B2B SaaS."},"valuationLens":{"type":"string","nullable":true,"enum":["fundraising","acquisition",null],"description":"Which comparable set the valuation estimate prices against. Null means fundraising."},"systemStatus":{"type":"string","enum":["sandbox","trial","free","paid","cancelled","expired","beta"],"description":"The status of the company."},"systemTrialUntil":{"type":"string","nullable":true,"format":"date-time"},"systemExpiredAt":{"type":"string","nullable":true,"format":"date-time"},"systemDeletionWarning":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647},"systemSandboxExpireTime":{"type":"string","nullable":true,"format":"date-time"},"systemDeletionScheduledTime":{"type":"string","nullable":true,"format":"date-time"},"systemStripe":{"type":"object","nullable":true,"properties":{"customerId":{"type":"string"},"subscription":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["active","incomplete","incomplete_expired","past_due","unpaid","canceled"]},"productName":{"type":"string","enum":["starter","pro"]},"interval":{"type":"string","enum":["monthly","yearly"]},"currentPeriodStart":{"type":"string"},"currentPeriodEnd":{"type":"string"},"price":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"cancelAt":{"type":"string","nullable":true},"limits":{"type":"object","nullable":true,"properties":{"minARR":{"type":"number"},"maxARR":{"type":"number"}},"required":["minARR","maxARR"]}},"required":["id","status","productName","interval","currentPeriodStart","currentPeriodEnd","price","cancelAt","limits"]}},"required":["customerId"]},"systemTierOverageSince":{"type":"string","nullable":true,"format":"date-time"},"computedCommittedMrrEur":{"type":"string","nullable":true},"computedActiveCustomerCount":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647},"computedMetricsTime":{"type":"string","nullable":true,"format":"date-time"},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"role":{"type":"string","enum":["owner","editor","viewer"]},"permissions":{"type":"array","nullable":true,"items":{"type":"string","enum":["subscriptions:read","subscriptions:write","customers:read","customers:write","vendors:read","vendors:write","accounts:read","accounts:write","transactions:read","transactions:write","balances:read","balances:write","financials:read","financials:write","users:manage","api_keys:manage","integrations:manage","company:manage"],"description":"A granular permission in the form `<resource>:<action>`. Stored on companiesUsers and companiesApiKeys. When a grant has `permissions: null` it means unrestricted (legacy behavior derived from role)."}}},"required":["id","firestoreId","name","baseCurrency","lastOverallUpdatedTime","plShowPlanningPercentageForPastMonths","plShowAllCategories","plRecognizeRevenueOnInvoice","dataStartDate","valuationCategory","valuationLens","systemStatus","systemTrialUntil","systemExpiredAt","systemDeletionWarning","systemSandboxExpireTime","systemDeletionScheduledTime","systemTierOverageSince","computedCommittedMrrEur","computedActiveCustomerCount","computedMetricsTime","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","role","permissions"]}}}}}}}},"/companies/{companyId}":{"get":{"description":"Retrieve one company with its settings and its member list. `baseCurrency` is the currency every cross-currency figure is converted into, so read it before interpreting any aggregate amount. Returns 404 if the company does not exist or the caller has no access to it.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"Company details with users","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"firestoreId":{"type":"string","nullable":true},"name":{"type":"string","description":"The name of the company."},"baseCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The base currency for the company."},"lastOverallUpdatedTime":{"type":"string","format":"date-time","description":"When any data within the company was last updated."},"plShowPlanningPercentageForPastMonths":{"type":"boolean","description":"Whether to show planning percentages for past months in P&L."},"plShowAllCategories":{"type":"boolean","description":"Whether to show all categories in P&L, including empty ones."},"plRecognizeRevenueOnInvoice":{"type":"boolean"},"dataStartDate":{"type":"string","nullable":true,"description":"The company-wide data start date. Imports skip transactions dated before it. Null means no start date is set."},"valuationCategory":{"type":"string","nullable":true,"enum":["traditional","aiEnhanced","aiNative",null],"description":"Self-declared company category used by the valuation estimate. Null means traditional B2B SaaS."},"valuationLens":{"type":"string","nullable":true,"enum":["fundraising","acquisition",null],"description":"Which comparable set the valuation estimate prices against. Null means fundraising."},"systemStatus":{"type":"string","enum":["sandbox","trial","free","paid","cancelled","expired","beta"],"description":"The status of the company."},"systemTrialUntil":{"type":"string","nullable":true,"format":"date-time"},"systemExpiredAt":{"type":"string","nullable":true,"format":"date-time"},"systemDeletionWarning":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647},"systemSandboxExpireTime":{"type":"string","nullable":true,"format":"date-time"},"systemDeletionScheduledTime":{"type":"string","nullable":true,"format":"date-time"},"systemStripe":{"type":"object","nullable":true,"properties":{"customerId":{"type":"string"},"subscription":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["active","incomplete","incomplete_expired","past_due","unpaid","canceled"]},"productName":{"type":"string","enum":["starter","pro"]},"interval":{"type":"string","enum":["monthly","yearly"]},"currentPeriodStart":{"type":"string"},"currentPeriodEnd":{"type":"string"},"price":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"cancelAt":{"type":"string","nullable":true},"limits":{"type":"object","nullable":true,"properties":{"minARR":{"type":"number"},"maxARR":{"type":"number"}},"required":["minARR","maxARR"]}},"required":["id","status","productName","interval","currentPeriodStart","currentPeriodEnd","price","cancelAt","limits"]}},"required":["customerId"]},"systemTierOverageSince":{"type":"string","nullable":true,"format":"date-time"},"computedCommittedMrrEur":{"type":"string","nullable":true},"computedActiveCustomerCount":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647},"computedMetricsTime":{"type":"string","nullable":true,"format":"date-time"},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"users":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"firebaseUserId":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"name":{"type":"string","nullable":true},"photoURL":{"type":"string","nullable":true},"role":{"type":"string","enum":["owner","editor","viewer"]},"permissions":{"type":"array","nullable":true,"items":{"type":"string","enum":["subscriptions:read","subscriptions:write","customers:read","customers:write","vendors:read","vendors:write","accounts:read","accounts:write","transactions:read","transactions:write","balances:read","balances:write","financials:read","financials:write","users:manage","api_keys:manage","integrations:manage","company:manage"],"description":"A granular permission in the form `<resource>:<action>`. Stored on companiesUsers and companiesApiKeys. When a grant has `permissions: null` it means unrestricted (legacy behavior derived from role)."}},"lastSeenTime":{"type":"string","nullable":true,"format":"date-time"}},"required":["id","userId","firebaseUserId","email","name","photoURL","role","permissions","lastSeenTime"]}},"integrationTypes":{"type":"array","items":{"type":"string","enum":["stripe","slack","hubspot","datevPayroll","finway","pleo","salesforce"],"description":"The type of integration."},"description":"Distinct integration types currently active for this company. Used by the UI to gate features that depend on a specific integration being installed (e.g. show DATEV personnel-number input only when a datevPayroll integration exists). Avoids loading the full /integrations list — that endpoint is owner-only."}},"required":["id","firestoreId","name","baseCurrency","lastOverallUpdatedTime","plShowPlanningPercentageForPastMonths","plShowAllCategories","plRecognizeRevenueOnInvoice","dataStartDate","valuationCategory","valuationLens","systemStatus","systemTrialUntil","systemExpiredAt","systemDeletionWarning","systemSandboxExpireTime","systemDeletionScheduledTime","systemTierOverageSince","computedCommittedMrrEur","computedActiveCustomerCount","computedMetricsTime","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","users","integrationTypes"]}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update company settings. Only the fields you send change. Changing `baseCurrency` restates every converted figure in the app, including historical ones, so it is not a cosmetic setting. `plShowPlanningPercentageForPastMonths` and `plShowAllCategories` control how the P&L is rendered, and the valuation fields drive the valuation estimate.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"baseCurrency":{"type":"string"},"plShowPlanningPercentageForPastMonths":{"type":"boolean"},"plShowAllCategories":{"type":"boolean"},"plRecognizeRevenueOnInvoice":{"type":"boolean"},"valuationCategory":{"type":"string","nullable":true,"enum":["traditional","aiEnhanced","aiNative",null]},"valuationLens":{"type":"string","nullable":true,"enum":["fundraising","acquisition",null]}}}}}},"responses":{"200":{"description":"Updated company","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"firestoreId":{"type":"string","nullable":true},"name":{"type":"string","description":"The name of the company."},"baseCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The base currency for the company."},"lastOverallUpdatedTime":{"type":"string","format":"date-time","description":"When any data within the company was last updated."},"plShowPlanningPercentageForPastMonths":{"type":"boolean","description":"Whether to show planning percentages for past months in P&L."},"plShowAllCategories":{"type":"boolean","description":"Whether to show all categories in P&L, including empty ones."},"plRecognizeRevenueOnInvoice":{"type":"boolean"},"dataStartDate":{"type":"string","nullable":true,"description":"The company-wide data start date. Imports skip transactions dated before it. Null means no start date is set."},"valuationCategory":{"type":"string","nullable":true,"enum":["traditional","aiEnhanced","aiNative",null],"description":"Self-declared company category used by the valuation estimate. Null means traditional B2B SaaS."},"valuationLens":{"type":"string","nullable":true,"enum":["fundraising","acquisition",null],"description":"Which comparable set the valuation estimate prices against. Null means fundraising."},"systemStatus":{"type":"string","enum":["sandbox","trial","free","paid","cancelled","expired","beta"],"description":"The status of the company."},"systemTrialUntil":{"type":"string","nullable":true,"format":"date-time"},"systemExpiredAt":{"type":"string","nullable":true,"format":"date-time"},"systemDeletionWarning":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647},"systemSandboxExpireTime":{"type":"string","nullable":true,"format":"date-time"},"systemDeletionScheduledTime":{"type":"string","nullable":true,"format":"date-time"},"systemStripe":{"type":"object","nullable":true,"properties":{"customerId":{"type":"string"},"subscription":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["active","incomplete","incomplete_expired","past_due","unpaid","canceled"]},"productName":{"type":"string","enum":["starter","pro"]},"interval":{"type":"string","enum":["monthly","yearly"]},"currentPeriodStart":{"type":"string"},"currentPeriodEnd":{"type":"string"},"price":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"cancelAt":{"type":"string","nullable":true},"limits":{"type":"object","nullable":true,"properties":{"minARR":{"type":"number"},"maxARR":{"type":"number"}},"required":["minARR","maxARR"]}},"required":["id","status","productName","interval","currentPeriodStart","currentPeriodEnd","price","cancelAt","limits"]}},"required":["customerId"]},"systemTierOverageSince":{"type":"string","nullable":true,"format":"date-time"},"computedCommittedMrrEur":{"type":"string","nullable":true},"computedActiveCustomerCount":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647},"computedMetricsTime":{"type":"string","nullable":true,"format":"date-time"},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","firestoreId","name","baseCurrency","lastOverallUpdatedTime","plShowPlanningPercentageForPastMonths","plShowAllCategories","plRecognizeRevenueOnInvoice","dataStartDate","valuationCategory","valuationLens","systemStatus","systemTrialUntil","systemExpiredAt","systemDeletionWarning","systemSandboxExpireTime","systemDeletionScheduledTime","systemTierOverageSince","computedCommittedMrrEur","computedActiveCustomerCount","computedMetricsTime","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Company not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/users":{"get":{"description":"List the people with an actual membership in a company, with their role and when they were last seen. `id` and `userId` both carry the SaaSFlow user id, which is what the role and remove endpoints expect. A null `lastSeenTime` means they have never opened the app. Pending invitations are not memberships and do not appear here, so someone who was invited but never accepted is missing from this list rather than showing up inactive.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of users in the company","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"userId":{"type":"string"},"firebaseUserId":{"type":"string","nullable":true},"email":{"type":"string"},"name":{"type":"string","nullable":true},"photoURL":{"type":"string","nullable":true},"role":{"type":"string","enum":["owner","editor","viewer"]},"lastSeenTime":{"type":"string","nullable":true,"format":"date-time"}},"required":["id","userId","firebaseUserId","email","name","photoURL","role","lastSeenTime"]}}}}}}}},"/companies/{companyId}/users/{userId}/role":{"put":{"description":"Change a member's role, and optionally their explicit permissions. Leave `permissions` out to keep whatever they have, send an array to pin an exact allowlist, or send null to drop back to the role's preset. Nobody can edit their own membership through this endpoint (403), which is what stops an owner locking themselves out.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"userId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"role":{"type":"string","enum":["owner","editor","viewer"],"description":"The role assigned to a user within the system. Owner = full access incl. company settings, billing, and user management; editor = read/write on data; viewer = read-only. The role is also used as the default permission preset and as a non-permission marker (e.g. notifications are sent to all owners). For granular API access control, see PermissionSchema."},"permissions":{"type":"array","nullable":true,"items":{"type":"string","enum":["subscriptions:read","subscriptions:write","customers:read","customers:write","vendors:read","vendors:write","accounts:read","accounts:write","transactions:read","transactions:write","balances:read","balances:write","financials:read","financials:write","users:manage","api_keys:manage","integrations:manage","company:manage"],"description":"A granular permission in the form `<resource>:<action>`. Stored on companiesUsers and companiesApiKeys. When a grant has `permissions: null` it means unrestricted (legacy behavior derived from role)."}}},"required":["role"]}}}},"responses":{"200":{"description":"User role updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"403":{"description":"Cannot edit own membership","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"User not found in company","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/users/{userId}":{"delete":{"description":"Remove someone from a company. Their user account survives, they just lose access to this company, and the data they created stays. Removing yourself is refused with 403.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"userId","in":"path"}],"responses":{"200":{"description":"User removed from company","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"403":{"description":"Cannot remove self","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/account_groups":{"get":{"description":"List a company's account groups. Groups are the sections bank accounts are filed under (for example \"Operating\" or \"Savings\"). Archived groups are included, so filter on `archived` if you only want the active ones. Results come back unsorted; sort by `order` ascending to match the order the app shows.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of account groups","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the account group."},"archived":{"type":"boolean","description":"Flag indicating if the account group is archived."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering account groups in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","archived","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"post":{"description":"Create an account group. `order` is required and sets the position in the list; pass a number higher than every existing group to append at the end. Assign accounts to the new group with `PUT /accounts/{accountId}/change_group`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"archived":{"type":"boolean","default":false},"order":{"type":"number"}},"required":["name","order"]}}}},"responses":{"201":{"description":"Created account group","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the account group."},"archived":{"type":"boolean","description":"Flag indicating if the account group is archived."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering account groups in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","archived","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"/companies/{companyId}/account_groups/reorder":{"put":{"description":"Set the display order of account groups. Pass every group id in the order you want them shown; each group's `order` is set to its position in the array. Groups you leave out of the array keep their current `order` and may end up interleaved, so send the full list.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"accountGroupIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":1000}},"required":["accountGroupIds"]}}}},"responses":{"200":{"description":"Order updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/account_groups/{accountGroupId}":{"get":{"description":"Retrieve a single account group by id. Returns 404 if the group does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountGroupId","in":"path"}],"responses":{"200":{"description":"Account group details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the account group."},"archived":{"type":"boolean","description":"Flag indicating if the account group is archived."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering account groups in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","archived","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Account group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Rename an account group or archive it. Archiving hides the group in the app without touching the accounts inside it.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountGroupId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"archived":{"type":"boolean"},"order":{"type":"number"}}}}}},"responses":{"200":{"description":"Updated account group","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the account group."},"archived":{"type":"boolean","description":"Flag indicating if the account group is archived."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering account groups in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","archived","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Account group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete an account group. Move every account out of the group first (`PUT /accounts/{accountId}/change_group`), otherwise the delete fails on the foreign key from accounts. To keep the grouping but hide it, archive the group with `PUT` instead.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountGroupId","in":"path"}],"responses":{"200":{"description":"Account group deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/accounts":{"get":{"description":"List a company's accounts (bank, cash, credit card, loan, transit, other) with the integration managing each one, where there is one. `includeInCashBalance` decides whether an account counts towards the cash position, so a loan or a holding account can be excluded from it. Archived accounts are included; filter on `archived` for the live ones. Transaction counts and last-transaction dates only appear for callers with `transactions:read`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of accounts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"name":{"type":"string","description":"The name of the account."},"accountNumber":{"type":"string","nullable":true,"description":"The bank account number. Null if not provided."},"includeInCashBalance":{"type":"boolean","description":"Whether this account should be included in cash balance calculations and cash flow reports."},"accountGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the account group this account belongs to. Null if not part of any group."},"archived":{"type":"boolean","description":"Flag indicating if the account is archived and should be hidden from most views."},"expectedAnnualReturn":{"type":"number","nullable":true,"minimum":-8388608,"maximum":8388607,"description":"The expected annual return percentage for this account, if applicable."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering accounts in the UI."},"defaultCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The default currency for transactions recorded under this account."},"defaultCategoryId":{"type":"string","nullable":true,"description":"The default category ID to assign to new transactions for this account."},"importType":{"type":"string","enum":["manual","automated"],"description":"Whether transactions are imported manually or via bank connection."},"transitCurrency":{"type":"string","nullable":true,"enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR",null]},"importSettings":{"type":"object","properties":{"csv":{"nullable":true}}},"bankConnection":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["finApi"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"finApiAccount":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the connected bank account."},"iban":{"type":"string","nullable":true,"description":"The IBAN of the connected bank account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"bic":{"type":"string","nullable":true,"description":"The BIC (Bank Identifier Code) of the institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."}},"required":["id","name","bic","logoStored"],"description":"Details of the banking institution connected via FinAPI."},"createdUserId":{"type":"string","description":"The SaaSFlow user ID of the user who set up this bank connection."}},"required":["id","iban","institution","createdUserId"],"additionalProperties":false,"description":"Details of the FinAPI connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","finApiAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["plaid"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"plaidAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid account ID for the connected bank account."},"mask":{"type":"string","nullable":true,"description":"The last 2-4 characters of the account number. Null if not available."},"type":{"type":"string","enum":["investment","credit","depository","loan","brokerage","other"],"description":"The type of the account."},"subtype":{"type":"string","nullable":true,"description":"The subtype of the account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid institution ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"url":{"type":"string","nullable":true,"description":"The URL of the banking institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."},"routingNumbers":{"type":"array","nullable":true,"items":{"type":"string"},"description":"The routing numbers of the banking institution. Null if not available."},"primaryColor":{"type":"string","nullable":true,"description":"The primary color of the banking institution. Null if not available."}},"required":["id","name","url","logoStored","routingNumbers","primaryColor"],"additionalProperties":false,"description":"Details of the banking institution connected via Plaid."},"createdUserId":{"type":"string","description":"The ID of the user who set up this bank connection."},"itemId":{"type":"string","description":"The Plaid item ID for the connected bank account representing a user to bank relationship."}},"required":["id","mask","type","subtype","institution","createdUserId","itemId"],"additionalProperties":false,"description":"Details of the Plaid connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","plaidAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripe"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe account ID."}},"required":["id"],"additionalProperties":false,"description":"Details of the Stripe connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripeLoan"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeLoanAccount":{"type":"object","properties":{"stripeAccountId":{"type":"string","description":"The Stripe account ID this loan belongs to."},"flexLoanId":{"type":"string","description":"The Stripe Capital loan identifier. Usually the Flex Loan (`flxln_`) id, or a financing-offer (`financingoffer_`) id for loans on the newer Capital object whose payout was imported before any paydown revealed the `flxln_` id."}},"required":["stripeAccountId","flexLoanId"],"additionalProperties":false,"description":"Details of the Stripe Capital Flex Loan account."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeLoanAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["datevPayroll"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["finway"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["pleo"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false}]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"transactionCount":{"type":"number","description":"The number of transactions associated with this account."},"openTransactionCount":{"type":"number","description":"The number of open transactions associated with this account."},"lastTransactionDate":{"type":"string","nullable":true,"format":"date-time","description":"The date of the last transaction associated with this account."},"integration":{"oneOf":[{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["stripe"],"description":"The type of integration."},"stripeMode":{"type":"string","enum":["test","live"],"description":"The mode of the Stripe integration (test or live)."},"lastAccountSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful account synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSubscriptionSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful subscription synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"missingPermissionObjectTypes":{"type":"array","items":{"type":"string"},"description":"Stripe object types (e.g. \"charges\") the connection is currently not permitted to read, typically because the account authorized the app before that permission was added. Reconnecting the integration grants the current permission set."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastAccountSyncTime","lastSubscriptionSyncTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["slack"],"description":"The type of integration."},"slackChannel":{"type":"string","description":"The Slack channel name."},"slackChannelId":{"type":"string","description":"The Slack channel ID."},"slackTeamId":{"type":"string","description":"The Slack team ID."},"slackTeamName":{"type":"string","description":"The Slack team name."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","slackChannel","slackChannelId","slackTeamId","slackTeamName","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["hubspot"],"description":"The type of integration."},"hubspotPortalId":{"type":"string","description":"The HubSpot portal (account) ID."},"lastSubscriptionSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful subscription synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","hubspotPortalId","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["datevPayroll"],"description":"The type of integration."},"lastUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful CSV upload. Null if never uploaded. Format: YYYY-MM-DDTHH:MM:SSZ"},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastUploadTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["finway"],"description":"The type of integration."},"lastWalletUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful wallet statement upload. Null if never."},"lastAntragUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful Antrag ('Alle Formate' zip) upload. Null if never."},"costCategoryMap":{"type":"object","additionalProperties":{"type":"object","properties":{"template":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Categorization template applied to every wallet payment line under this cost center. Null means skipped. `passThroughTax` and `usingDateRule` are placeholders for Finway — real tax / dates come from the Antrag line."},"label":{"type":"string","description":"Human-readable Kostenstelle label as last seen in Finway (e.g. 'Materialkosten/ Produktion'). Refreshed on every Antrag upload."},"mappedTime":{"type":"string","format":"date-time","description":"ISO datetime the mapping was created."},"mappedByUserId":{"type":"string","description":"Firebase UID that created the mapping."}},"required":["template","label","mappedTime","mappedByUserId"]},"default":{},"description":"Map keyed by Finway CostCenter1ID (e.g. '30') to the chosen SaaSFlow category. Always present in Antrag exports, stable across Finway tenants — preferred over Sachkonto string keying."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastWalletUploadTime","lastAntragUploadTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["pleo"],"description":"The type of integration."},"lastExportUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful Pleo export upload. Null if never."},"lastReimbursementsUploadTime":{"type":"string","nullable":true,"format":"date-time","default":null,"description":"ISO datetime of the last successful Pleo reimbursements (Rückerstattungen) upload. Null if never."},"plMappingField":{"type":"object","nullable":true,"properties":{"keyColumn":{"type":"string","description":"CSV column header whose value uniquely identifies the categorization bucket (e.g. 'Chart of Accounts - Code')."},"labelColumn":{"type":"string","nullable":true,"description":"Optional CSV column header used as the human-readable label for picker rows. Null falls back to using the keyColumn value as both key and label."}},"required":["keyColumn","labelColumn"],"description":"CSV column pair used to derive the categorization key + label. Null until the first upload completes the column-picker step."},"mappingValueMap":{"type":"object","additionalProperties":{"type":"object","properties":{"template":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Categorization template applied to every Pleo wallet row whose mapping field equals this entry key. Null means skipped. `passThroughTax` and `usingDateRule` are placeholders — real values come from the CSV row at apply time."},"label":{"type":"string","description":"Human-readable label for the mapping value (typically the matching \"Name\" column, e.g. \"266 Marketing Magazine FR\" when the key is \"266\"). Refreshed on every Pleo upload."},"mappedTime":{"type":"string","format":"date-time","description":"ISO datetime the mapping was created."},"mappedByUserId":{"type":"string","description":"Firebase UID that created the mapping."}},"required":["template","label","mappedTime","mappedByUserId"]},"default":{},"description":"Map keyed by the chosen CSV column's value (e.g. '266') to the user-picked SaaSFlow category template. Re-used on every subsequent upload — only new mapping values are surfaced to the picker."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastExportUploadTime","plMappingField","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["salesforce"],"description":"The type of integration."},"salesforceOrgId":{"type":"string","description":"The Salesforce org ID (15- or 18-char, starts with 00D)."},"salesforceInstanceUrl":{"type":"string","description":"The per-org Salesforce REST API host returned by OAuth (e.g. https://acme.my.salesforce.com). Required for every API call to this org."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","salesforceOrgId","salesforceInstanceUrl","id"],"additionalProperties":false},{"nullable":true}],"description":"The integration managing this account (null for manual accounts or when the bank connection has no integrationId)."}},"required":["id","companyId","type","name","accountNumber","includeInCashBalance","accountGroupId","archived","expectedAnnualReturn","order","defaultCurrency","defaultCategoryId","importType","transitCurrency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","transactionCount","openTransactionCount","lastTransactionDate","integration"]}}}}}}},"post":{"description":"Create an account. Use `importType: \"manual\"` for one you will fill by hand or by CSV; accounts fed by a bank connection are created by the integration itself. `includeInCashBalance` decides whether it counts towards the cash position, which is usually false for loans. Pass `initialBalance` to seed the opening balance so the running balance is right from day one, dated before any transaction you then import. `order` is required and sets its position in the list.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["bank","cash","creditCard","loan","other"]},"name":{"type":"string","minLength":1},"includeInCashBalance":{"type":"boolean"},"defaultCurrency":{"type":"string","minLength":1},"importType":{"type":"string","enum":["manual","automated"]},"order":{"type":"number"},"accountGroupId":{"type":"string","nullable":true},"archived":{"type":"boolean","default":false},"accountNumber":{"type":"string","nullable":true},"expectedAnnualReturn":{"type":"number","nullable":true},"defaultCategoryId":{"type":"string","nullable":true},"initialBalance":{"type":"object","properties":{"value":{"type":"string"},"currency":{"type":"string"},"date":{"type":"string"}},"required":["value","currency","date"]}},"required":["type","name","includeInCashBalance","defaultCurrency","importType","order"]}}}},"responses":{"201":{"description":"Created account","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"name":{"type":"string","description":"The name of the account."},"accountNumber":{"type":"string","nullable":true,"description":"The bank account number. Null if not provided."},"includeInCashBalance":{"type":"boolean","description":"Whether this account should be included in cash balance calculations and cash flow reports."},"accountGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the account group this account belongs to. Null if not part of any group."},"archived":{"type":"boolean","description":"Flag indicating if the account is archived and should be hidden from most views."},"expectedAnnualReturn":{"type":"number","nullable":true,"minimum":-8388608,"maximum":8388607,"description":"The expected annual return percentage for this account, if applicable."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering accounts in the UI."},"defaultCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The default currency for transactions recorded under this account."},"defaultCategoryId":{"type":"string","nullable":true,"description":"The default category ID to assign to new transactions for this account."},"importType":{"type":"string","enum":["manual","automated"],"description":"Whether transactions are imported manually or via bank connection."},"transitCurrency":{"type":"string","nullable":true,"enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR",null]},"importSettings":{"type":"object","properties":{"csv":{"nullable":true}}},"bankConnection":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["finApi"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"finApiAccount":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the connected bank account."},"iban":{"type":"string","nullable":true,"description":"The IBAN of the connected bank account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"bic":{"type":"string","nullable":true,"description":"The BIC (Bank Identifier Code) of the institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."}},"required":["id","name","bic","logoStored"],"description":"Details of the banking institution connected via FinAPI."},"createdUserId":{"type":"string","description":"The SaaSFlow user ID of the user who set up this bank connection."}},"required":["id","iban","institution","createdUserId"],"additionalProperties":false,"description":"Details of the FinAPI connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","finApiAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["plaid"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"plaidAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid account ID for the connected bank account."},"mask":{"type":"string","nullable":true,"description":"The last 2-4 characters of the account number. Null if not available."},"type":{"type":"string","enum":["investment","credit","depository","loan","brokerage","other"],"description":"The type of the account."},"subtype":{"type":"string","nullable":true,"description":"The subtype of the account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid institution ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"url":{"type":"string","nullable":true,"description":"The URL of the banking institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."},"routingNumbers":{"type":"array","nullable":true,"items":{"type":"string"},"description":"The routing numbers of the banking institution. Null if not available."},"primaryColor":{"type":"string","nullable":true,"description":"The primary color of the banking institution. Null if not available."}},"required":["id","name","url","logoStored","routingNumbers","primaryColor"],"additionalProperties":false,"description":"Details of the banking institution connected via Plaid."},"createdUserId":{"type":"string","description":"The ID of the user who set up this bank connection."},"itemId":{"type":"string","description":"The Plaid item ID for the connected bank account representing a user to bank relationship."}},"required":["id","mask","type","subtype","institution","createdUserId","itemId"],"additionalProperties":false,"description":"Details of the Plaid connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","plaidAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripe"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe account ID."}},"required":["id"],"additionalProperties":false,"description":"Details of the Stripe connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripeLoan"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeLoanAccount":{"type":"object","properties":{"stripeAccountId":{"type":"string","description":"The Stripe account ID this loan belongs to."},"flexLoanId":{"type":"string","description":"The Stripe Capital loan identifier. Usually the Flex Loan (`flxln_`) id, or a financing-offer (`financingoffer_`) id for loans on the newer Capital object whose payout was imported before any paydown revealed the `flxln_` id."}},"required":["stripeAccountId","flexLoanId"],"additionalProperties":false,"description":"Details of the Stripe Capital Flex Loan account."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeLoanAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["datevPayroll"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["finway"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["pleo"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false}]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","name","accountNumber","includeInCashBalance","accountGroupId","archived","expectedAnnualReturn","order","defaultCurrency","defaultCategoryId","importType","transitCurrency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"/companies/{companyId}/accounts/balances":{"get":{"description":"Get the balance of each account at two points in time and the change between them. Keyed by account id, `value` is the balance on `endDate` and `valuePreviousPeriod` the balance on `startDate`. Amounts are in each account's own currency, named in `defaultCurrency`, so do not sum across accounts without converting first. Narrow to specific accounts with a comma-separated `accountIds`. For a converted series over time rather than two snapshots, use `daily_balance`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Start date (YYYY-MM-DD). Balance at this date is the \"previous period\" value."},"required":true,"description":"Start date (YYYY-MM-DD). Balance at this date is the \"previous period\" value.","name":"startDate","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"End date (YYYY-MM-DD). Balance at this date is the current value."},"required":true,"description":"End date (YYYY-MM-DD). Balance at this date is the current value.","name":"endDate","in":"query"},{"schema":{"type":"string","description":"Comma-separated account IDs to filter by."},"required":false,"description":"Comma-separated account IDs to filter by.","name":"accountIds","in":"query"}],"responses":{"200":{"description":"Balance per account at the given dates.","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"object","properties":{"value":{"type":"number","description":"Balance at the end of the period."},"valuePreviousPeriod":{"type":"number","description":"Balance at the start of the period."},"valueChange":{"type":"number","description":"Change in balance over the period."},"valuePreviousPeriodWithTransfersAndBalancesAdded":{"type":"number","description":"Balance at the start of the period (same as valuePreviousPeriod for this endpoint)."},"defaultCurrency":{"type":"string","description":"The default currency of the account."}},"required":["value","valuePreviousPeriod","valueChange","valuePreviousPeriodWithTransfersAndBalancesAdded","defaultCurrency"]}}}}}}}},"/companies/{companyId}/accounts/daily_balance":{"get":{"description":"Get the day-by-day cumulative balance across accounts, returned as `[date, balance]` tuples. Unlike the per-account balances endpoint, these are converted into the company's base currency and summed, so this is the series to plot as a cash curve. Restrict it to specific accounts with a comma-separated `accountIds`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Start date (YYYY-MM-DD)."},"required":true,"description":"Start date (YYYY-MM-DD).","name":"startDate","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"End date (YYYY-MM-DD)."},"required":true,"description":"End date (YYYY-MM-DD).","name":"endDate","in":"query"},{"schema":{"type":"string","description":"Comma-separated account IDs to filter by."},"required":false,"description":"Comma-separated account IDs to filter by.","name":"accountIds","in":"query"}],"responses":{"200":{"description":"Daily cumulative balance in base currency.","content":{"application/json":{"schema":{"type":"array","items":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"}]},"minItems":2,"maxItems":2},"description":"Array of [date, cumulativeBalance] tuples in base currency."}}}}}}},"/companies/{companyId}/accounts/remove_history_preview":{"post":{"description":"Preview what cutting off history before a date would do, without changing anything. Returns per-account transaction counts and date ranges, plus `suggestedDate`: the earliest date from which every account has data, which is the point where the books stop having gaps. Loan accounts are ignored when computing it, since their data only spans payout to final repayment and a late loan is not a coverage gap. Only accounts with transactions are listed, and transit accounts are left out. Always run this before `remove_history`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"beforeDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}}}}}},"responses":{"200":{"description":"Per-account history stats for the remove-history flow. Only accounts that have transactions appear; transit accounts are excluded. suggestedDate is the first date on which every listed account has data, ignoring loan accounts (their data only spans payout to final repayment, so a late loan is not a coverage gap).","content":{"application/json":{"schema":{"type":"object","properties":{"accounts":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"accountType":{"type":"string"},"earliestDate":{"type":"string","nullable":true},"transactionCount":{"type":"integer"},"openTransactionCount":{"type":"integer"},"removeCount":{"type":"integer"},"openRemoveCount":{"type":"integer"},"remainingCount":{"type":"integer"},"remainingOpenCount":{"type":"integer"},"newStartingBalance":{"type":"string"},"hasInitialBalance":{"type":"boolean"}},"required":["accountId","accountType","earliestDate","transactionCount","openTransactionCount","removeCount","openRemoveCount","remainingCount","remainingOpenCount","newStartingBalance","hasInitialBalance"]}},"suggestedDate":{"type":"string","nullable":true},"dataStartDate":{"type":"string","nullable":true}},"required":["accounts","suggestedDate","dataStartDate"]}}}}}}},"/companies/{companyId}/accounts/remove_history":{"post":{"description":"Permanently delete transactions before `beforeDate` on the listed accounts and set the company data start date, so reporting begins from a clean, fully covered period. Each collapsed account gets an initial-balance seed so its running balance still ends up right; supply `initialBalances` for accounts with no existing seed, such as CSV wallets whose uploads started late, otherwise the seed is derived from the recorded sum. Pass an empty `accountIds` to only move the data start date. Accounts left with no transactions are archived and returned in `archivedAccountIds`. Transit accounts are refused with 422. This cannot be undone, so preview first.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"beforeDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"accountIds":{"type":"array","items":{"type":"string"}},"initialBalances":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"value":{"type":"string","pattern":"^-?\\d+(\\.\\d+)?$"}},"required":["accountId","value"]}}},"required":["beforeDate","accountIds"]}}}},"responses":{"200":{"description":"History removed for the selected accounts and the company data start date set. An empty accountIds list only sets the date.","content":{"application/json":{"schema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string"},"removedCount":{"type":"integer"},"newBalance":{"type":"string"}},"required":["accountId","removedCount","newBalance"]}},"archivedAccountIds":{"type":"array","items":{"type":"string"}},"dataStartDate":{"type":"string"}},"required":["results","archivedAccountIds","dataStartDate"]}}}},"404":{"description":"One of the accounts was not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"A transit account cannot have its history removed directly","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/accounts/reorder":{"put":{"description":"Reorder accounts inside one group, and move them into it in the same call. Every id in `accountIds` is assigned to `accountGroupId` (null for the ungrouped list) and given an `order` matching its position, so send the full contents of the target group rather than just the account that moved. Fails with 404 if the group does not exist.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"accountGroupId":{"type":"string","nullable":true},"accountIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":1000}},"required":["accountGroupId","accountIds"]}}}},"responses":{"200":{"description":"Group membership and order updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"404":{"description":"Account group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/accounts/{accountId}":{"get":{"description":"Retrieve a single account with the integration managing it, if any. Returns 404 if the account does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"responses":{"200":{"description":"Account details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"name":{"type":"string","description":"The name of the account."},"accountNumber":{"type":"string","nullable":true,"description":"The bank account number. Null if not provided."},"includeInCashBalance":{"type":"boolean","description":"Whether this account should be included in cash balance calculations and cash flow reports."},"accountGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the account group this account belongs to. Null if not part of any group."},"archived":{"type":"boolean","description":"Flag indicating if the account is archived and should be hidden from most views."},"expectedAnnualReturn":{"type":"number","nullable":true,"minimum":-8388608,"maximum":8388607,"description":"The expected annual return percentage for this account, if applicable."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering accounts in the UI."},"defaultCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The default currency for transactions recorded under this account."},"defaultCategoryId":{"type":"string","nullable":true,"description":"The default category ID to assign to new transactions for this account."},"importType":{"type":"string","enum":["manual","automated"],"description":"Whether transactions are imported manually or via bank connection."},"transitCurrency":{"type":"string","nullable":true,"enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR",null]},"importSettings":{"type":"object","properties":{"csv":{"nullable":true}}},"bankConnection":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["finApi"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"finApiAccount":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the connected bank account."},"iban":{"type":"string","nullable":true,"description":"The IBAN of the connected bank account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"bic":{"type":"string","nullable":true,"description":"The BIC (Bank Identifier Code) of the institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."}},"required":["id","name","bic","logoStored"],"description":"Details of the banking institution connected via FinAPI."},"createdUserId":{"type":"string","description":"The SaaSFlow user ID of the user who set up this bank connection."}},"required":["id","iban","institution","createdUserId"],"additionalProperties":false,"description":"Details of the FinAPI connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","finApiAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["plaid"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"plaidAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid account ID for the connected bank account."},"mask":{"type":"string","nullable":true,"description":"The last 2-4 characters of the account number. Null if not available."},"type":{"type":"string","enum":["investment","credit","depository","loan","brokerage","other"],"description":"The type of the account."},"subtype":{"type":"string","nullable":true,"description":"The subtype of the account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid institution ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"url":{"type":"string","nullable":true,"description":"The URL of the banking institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."},"routingNumbers":{"type":"array","nullable":true,"items":{"type":"string"},"description":"The routing numbers of the banking institution. Null if not available."},"primaryColor":{"type":"string","nullable":true,"description":"The primary color of the banking institution. Null if not available."}},"required":["id","name","url","logoStored","routingNumbers","primaryColor"],"additionalProperties":false,"description":"Details of the banking institution connected via Plaid."},"createdUserId":{"type":"string","description":"The ID of the user who set up this bank connection."},"itemId":{"type":"string","description":"The Plaid item ID for the connected bank account representing a user to bank relationship."}},"required":["id","mask","type","subtype","institution","createdUserId","itemId"],"additionalProperties":false,"description":"Details of the Plaid connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","plaidAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripe"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe account ID."}},"required":["id"],"additionalProperties":false,"description":"Details of the Stripe connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripeLoan"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeLoanAccount":{"type":"object","properties":{"stripeAccountId":{"type":"string","description":"The Stripe account ID this loan belongs to."},"flexLoanId":{"type":"string","description":"The Stripe Capital loan identifier. Usually the Flex Loan (`flxln_`) id, or a financing-offer (`financingoffer_`) id for loans on the newer Capital object whose payout was imported before any paydown revealed the `flxln_` id."}},"required":["stripeAccountId","flexLoanId"],"additionalProperties":false,"description":"Details of the Stripe Capital Flex Loan account."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeLoanAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["datevPayroll"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["finway"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["pleo"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false}]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"transactionCount":{"type":"number","description":"The number of transactions associated with this account."},"openTransactionCount":{"type":"number","description":"The number of open transactions associated with this account."},"lastTransactionDate":{"type":"string","nullable":true,"format":"date-time","description":"The date of the last transaction associated with this account."},"integration":{"oneOf":[{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["stripe"],"description":"The type of integration."},"stripeMode":{"type":"string","enum":["test","live"],"description":"The mode of the Stripe integration (test or live)."},"lastAccountSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful account synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSubscriptionSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful subscription synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"missingPermissionObjectTypes":{"type":"array","items":{"type":"string"},"description":"Stripe object types (e.g. \"charges\") the connection is currently not permitted to read, typically because the account authorized the app before that permission was added. Reconnecting the integration grants the current permission set."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastAccountSyncTime","lastSubscriptionSyncTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["slack"],"description":"The type of integration."},"slackChannel":{"type":"string","description":"The Slack channel name."},"slackChannelId":{"type":"string","description":"The Slack channel ID."},"slackTeamId":{"type":"string","description":"The Slack team ID."},"slackTeamName":{"type":"string","description":"The Slack team name."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","slackChannel","slackChannelId","slackTeamId","slackTeamName","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["hubspot"],"description":"The type of integration."},"hubspotPortalId":{"type":"string","description":"The HubSpot portal (account) ID."},"lastSubscriptionSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful subscription synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","hubspotPortalId","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["datevPayroll"],"description":"The type of integration."},"lastUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful CSV upload. Null if never uploaded. Format: YYYY-MM-DDTHH:MM:SSZ"},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastUploadTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["finway"],"description":"The type of integration."},"lastWalletUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful wallet statement upload. Null if never."},"lastAntragUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful Antrag ('Alle Formate' zip) upload. Null if never."},"costCategoryMap":{"type":"object","additionalProperties":{"type":"object","properties":{"template":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Categorization template applied to every wallet payment line under this cost center. Null means skipped. `passThroughTax` and `usingDateRule` are placeholders for Finway — real tax / dates come from the Antrag line."},"label":{"type":"string","description":"Human-readable Kostenstelle label as last seen in Finway (e.g. 'Materialkosten/ Produktion'). Refreshed on every Antrag upload."},"mappedTime":{"type":"string","format":"date-time","description":"ISO datetime the mapping was created."},"mappedByUserId":{"type":"string","description":"Firebase UID that created the mapping."}},"required":["template","label","mappedTime","mappedByUserId"]},"default":{},"description":"Map keyed by Finway CostCenter1ID (e.g. '30') to the chosen SaaSFlow category. Always present in Antrag exports, stable across Finway tenants — preferred over Sachkonto string keying."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastWalletUploadTime","lastAntragUploadTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["pleo"],"description":"The type of integration."},"lastExportUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful Pleo export upload. Null if never."},"lastReimbursementsUploadTime":{"type":"string","nullable":true,"format":"date-time","default":null,"description":"ISO datetime of the last successful Pleo reimbursements (Rückerstattungen) upload. Null if never."},"plMappingField":{"type":"object","nullable":true,"properties":{"keyColumn":{"type":"string","description":"CSV column header whose value uniquely identifies the categorization bucket (e.g. 'Chart of Accounts - Code')."},"labelColumn":{"type":"string","nullable":true,"description":"Optional CSV column header used as the human-readable label for picker rows. Null falls back to using the keyColumn value as both key and label."}},"required":["keyColumn","labelColumn"],"description":"CSV column pair used to derive the categorization key + label. Null until the first upload completes the column-picker step."},"mappingValueMap":{"type":"object","additionalProperties":{"type":"object","properties":{"template":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Categorization template applied to every Pleo wallet row whose mapping field equals this entry key. Null means skipped. `passThroughTax` and `usingDateRule` are placeholders — real values come from the CSV row at apply time."},"label":{"type":"string","description":"Human-readable label for the mapping value (typically the matching \"Name\" column, e.g. \"266 Marketing Magazine FR\" when the key is \"266\"). Refreshed on every Pleo upload."},"mappedTime":{"type":"string","format":"date-time","description":"ISO datetime the mapping was created."},"mappedByUserId":{"type":"string","description":"Firebase UID that created the mapping."}},"required":["template","label","mappedTime","mappedByUserId"]},"default":{},"description":"Map keyed by the chosen CSV column's value (e.g. '266') to the user-picked SaaSFlow category template. Re-used on every subsequent upload — only new mapping values are surfaced to the picker."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastExportUploadTime","plMappingField","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["salesforce"],"description":"The type of integration."},"salesforceOrgId":{"type":"string","description":"The Salesforce org ID (15- or 18-char, starts with 00D)."},"salesforceInstanceUrl":{"type":"string","description":"The per-org Salesforce REST API host returned by OAuth (e.g. https://acme.my.salesforce.com). Required for every API call to this org."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","salesforceOrgId","salesforceInstanceUrl","id"],"additionalProperties":false},{"nullable":true}],"description":"The integration managing this account (null for manual accounts or when the bank connection has no integrationId)."}},"required":["id","companyId","type","name","accountNumber","includeInCashBalance","accountGroupId","archived","expectedAnnualReturn","order","defaultCurrency","defaultCategoryId","importType","transitCurrency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","transactionCount","openTransactionCount","lastTransactionDate","integration"]}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update an account. Only the fields you send change. Two rules are enforced here: transit accounts may not change their type or currency (422), and a company can hold only one active transit account per currency, so creating a second is refused with 409 until the existing one is archived. Changing `defaultCurrency` on an account that already has transactions does not restate them.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["bank","cash","creditCard","loan","other"]},"name":{"type":"string","minLength":1},"includeInCashBalance":{"type":"boolean"},"defaultCurrency":{"type":"string","minLength":1},"importType":{"type":"string","enum":["manual","automated"]},"order":{"type":"number"},"accountGroupId":{"type":"string","nullable":true},"archived":{"type":"boolean"},"accountNumber":{"type":"string","nullable":true},"expectedAnnualReturn":{"type":"number","nullable":true},"defaultCategoryId":{"type":"string","nullable":true},"importSettings":{"nullable":true}}}}}},"responses":{"200":{"description":"Updated account","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"name":{"type":"string","description":"The name of the account."},"accountNumber":{"type":"string","nullable":true,"description":"The bank account number. Null if not provided."},"includeInCashBalance":{"type":"boolean","description":"Whether this account should be included in cash balance calculations and cash flow reports."},"accountGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the account group this account belongs to. Null if not part of any group."},"archived":{"type":"boolean","description":"Flag indicating if the account is archived and should be hidden from most views."},"expectedAnnualReturn":{"type":"number","nullable":true,"minimum":-8388608,"maximum":8388607,"description":"The expected annual return percentage for this account, if applicable."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering accounts in the UI."},"defaultCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The default currency for transactions recorded under this account."},"defaultCategoryId":{"type":"string","nullable":true,"description":"The default category ID to assign to new transactions for this account."},"importType":{"type":"string","enum":["manual","automated"],"description":"Whether transactions are imported manually or via bank connection."},"transitCurrency":{"type":"string","nullable":true,"enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR",null]},"importSettings":{"type":"object","properties":{"csv":{"nullable":true}}},"bankConnection":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["finApi"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"finApiAccount":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the connected bank account."},"iban":{"type":"string","nullable":true,"description":"The IBAN of the connected bank account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"bic":{"type":"string","nullable":true,"description":"The BIC (Bank Identifier Code) of the institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."}},"required":["id","name","bic","logoStored"],"description":"Details of the banking institution connected via FinAPI."},"createdUserId":{"type":"string","description":"The SaaSFlow user ID of the user who set up this bank connection."}},"required":["id","iban","institution","createdUserId"],"additionalProperties":false,"description":"Details of the FinAPI connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","finApiAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["plaid"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"plaidAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid account ID for the connected bank account."},"mask":{"type":"string","nullable":true,"description":"The last 2-4 characters of the account number. Null if not available."},"type":{"type":"string","enum":["investment","credit","depository","loan","brokerage","other"],"description":"The type of the account."},"subtype":{"type":"string","nullable":true,"description":"The subtype of the account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid institution ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"url":{"type":"string","nullable":true,"description":"The URL of the banking institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."},"routingNumbers":{"type":"array","nullable":true,"items":{"type":"string"},"description":"The routing numbers of the banking institution. Null if not available."},"primaryColor":{"type":"string","nullable":true,"description":"The primary color of the banking institution. Null if not available."}},"required":["id","name","url","logoStored","routingNumbers","primaryColor"],"additionalProperties":false,"description":"Details of the banking institution connected via Plaid."},"createdUserId":{"type":"string","description":"The ID of the user who set up this bank connection."},"itemId":{"type":"string","description":"The Plaid item ID for the connected bank account representing a user to bank relationship."}},"required":["id","mask","type","subtype","institution","createdUserId","itemId"],"additionalProperties":false,"description":"Details of the Plaid connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","plaidAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripe"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe account ID."}},"required":["id"],"additionalProperties":false,"description":"Details of the Stripe connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripeLoan"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeLoanAccount":{"type":"object","properties":{"stripeAccountId":{"type":"string","description":"The Stripe account ID this loan belongs to."},"flexLoanId":{"type":"string","description":"The Stripe Capital loan identifier. Usually the Flex Loan (`flxln_`) id, or a financing-offer (`financingoffer_`) id for loans on the newer Capital object whose payout was imported before any paydown revealed the `flxln_` id."}},"required":["stripeAccountId","flexLoanId"],"additionalProperties":false,"description":"Details of the Stripe Capital Flex Loan account."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeLoanAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["datevPayroll"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["finway"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["pleo"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false}]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","name","accountNumber","includeInCashBalance","accountGroupId","archived","expectedAnnualReturn","order","defaultCurrency","defaultCategoryId","importType","transitCurrency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"An active transit account for this currency already exists — archive it first","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"Transit accounts cannot change their type or currency","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete an account and cascade-delete every transaction on it, along with their categorizations. That rewrites cash balances and the P&L for every period the account covered, so archive it instead when the intent is only to get it off the screen.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"responses":{"200":{"description":"Account deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/accounts/{accountId}/archive":{"put":{"description":"Archive an account. It drops out of the app's account lists and pickers while its transactions and history stay intact, which is the right move for a closed bank account. Reversible with `/unarchive`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"responses":{"200":{"description":"Account archived","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"name":{"type":"string","description":"The name of the account."},"accountNumber":{"type":"string","nullable":true,"description":"The bank account number. Null if not provided."},"includeInCashBalance":{"type":"boolean","description":"Whether this account should be included in cash balance calculations and cash flow reports."},"accountGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the account group this account belongs to. Null if not part of any group."},"archived":{"type":"boolean","description":"Flag indicating if the account is archived and should be hidden from most views."},"expectedAnnualReturn":{"type":"number","nullable":true,"minimum":-8388608,"maximum":8388607,"description":"The expected annual return percentage for this account, if applicable."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering accounts in the UI."},"defaultCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The default currency for transactions recorded under this account."},"defaultCategoryId":{"type":"string","nullable":true,"description":"The default category ID to assign to new transactions for this account."},"importType":{"type":"string","enum":["manual","automated"],"description":"Whether transactions are imported manually or via bank connection."},"transitCurrency":{"type":"string","nullable":true,"enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR",null]},"importSettings":{"type":"object","properties":{"csv":{"nullable":true}}},"bankConnection":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["finApi"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"finApiAccount":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the connected bank account."},"iban":{"type":"string","nullable":true,"description":"The IBAN of the connected bank account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"bic":{"type":"string","nullable":true,"description":"The BIC (Bank Identifier Code) of the institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."}},"required":["id","name","bic","logoStored"],"description":"Details of the banking institution connected via FinAPI."},"createdUserId":{"type":"string","description":"The SaaSFlow user ID of the user who set up this bank connection."}},"required":["id","iban","institution","createdUserId"],"additionalProperties":false,"description":"Details of the FinAPI connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","finApiAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["plaid"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"plaidAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid account ID for the connected bank account."},"mask":{"type":"string","nullable":true,"description":"The last 2-4 characters of the account number. Null if not available."},"type":{"type":"string","enum":["investment","credit","depository","loan","brokerage","other"],"description":"The type of the account."},"subtype":{"type":"string","nullable":true,"description":"The subtype of the account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid institution ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"url":{"type":"string","nullable":true,"description":"The URL of the banking institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."},"routingNumbers":{"type":"array","nullable":true,"items":{"type":"string"},"description":"The routing numbers of the banking institution. Null if not available."},"primaryColor":{"type":"string","nullable":true,"description":"The primary color of the banking institution. Null if not available."}},"required":["id","name","url","logoStored","routingNumbers","primaryColor"],"additionalProperties":false,"description":"Details of the banking institution connected via Plaid."},"createdUserId":{"type":"string","description":"The ID of the user who set up this bank connection."},"itemId":{"type":"string","description":"The Plaid item ID for the connected bank account representing a user to bank relationship."}},"required":["id","mask","type","subtype","institution","createdUserId","itemId"],"additionalProperties":false,"description":"Details of the Plaid connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","plaidAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripe"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe account ID."}},"required":["id"],"additionalProperties":false,"description":"Details of the Stripe connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripeLoan"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeLoanAccount":{"type":"object","properties":{"stripeAccountId":{"type":"string","description":"The Stripe account ID this loan belongs to."},"flexLoanId":{"type":"string","description":"The Stripe Capital loan identifier. Usually the Flex Loan (`flxln_`) id, or a financing-offer (`financingoffer_`) id for loans on the newer Capital object whose payout was imported before any paydown revealed the `flxln_` id."}},"required":["stripeAccountId","flexLoanId"],"additionalProperties":false,"description":"Details of the Stripe Capital Flex Loan account."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeLoanAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["datevPayroll"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["finway"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["pleo"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false}]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","name","accountNumber","includeInCashBalance","accountGroupId","archived","expectedAnnualReturn","order","defaultCurrency","defaultCategoryId","importType","transitCurrency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/accounts/{accountId}/unarchive":{"put":{"description":"Bring an archived account back into the active lists. Nothing about its transactions changes; only its visibility does.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"responses":{"200":{"description":"Account unarchived","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"name":{"type":"string","description":"The name of the account."},"accountNumber":{"type":"string","nullable":true,"description":"The bank account number. Null if not provided."},"includeInCashBalance":{"type":"boolean","description":"Whether this account should be included in cash balance calculations and cash flow reports."},"accountGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the account group this account belongs to. Null if not part of any group."},"archived":{"type":"boolean","description":"Flag indicating if the account is archived and should be hidden from most views."},"expectedAnnualReturn":{"type":"number","nullable":true,"minimum":-8388608,"maximum":8388607,"description":"The expected annual return percentage for this account, if applicable."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering accounts in the UI."},"defaultCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The default currency for transactions recorded under this account."},"defaultCategoryId":{"type":"string","nullable":true,"description":"The default category ID to assign to new transactions for this account."},"importType":{"type":"string","enum":["manual","automated"],"description":"Whether transactions are imported manually or via bank connection."},"transitCurrency":{"type":"string","nullable":true,"enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR",null]},"importSettings":{"type":"object","properties":{"csv":{"nullable":true}}},"bankConnection":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["finApi"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"finApiAccount":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the connected bank account."},"iban":{"type":"string","nullable":true,"description":"The IBAN of the connected bank account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"bic":{"type":"string","nullable":true,"description":"The BIC (Bank Identifier Code) of the institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."}},"required":["id","name","bic","logoStored"],"description":"Details of the banking institution connected via FinAPI."},"createdUserId":{"type":"string","description":"The SaaSFlow user ID of the user who set up this bank connection."}},"required":["id","iban","institution","createdUserId"],"additionalProperties":false,"description":"Details of the FinAPI connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","finApiAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["plaid"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"plaidAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid account ID for the connected bank account."},"mask":{"type":"string","nullable":true,"description":"The last 2-4 characters of the account number. Null if not available."},"type":{"type":"string","enum":["investment","credit","depository","loan","brokerage","other"],"description":"The type of the account."},"subtype":{"type":"string","nullable":true,"description":"The subtype of the account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid institution ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"url":{"type":"string","nullable":true,"description":"The URL of the banking institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."},"routingNumbers":{"type":"array","nullable":true,"items":{"type":"string"},"description":"The routing numbers of the banking institution. Null if not available."},"primaryColor":{"type":"string","nullable":true,"description":"The primary color of the banking institution. Null if not available."}},"required":["id","name","url","logoStored","routingNumbers","primaryColor"],"additionalProperties":false,"description":"Details of the banking institution connected via Plaid."},"createdUserId":{"type":"string","description":"The ID of the user who set up this bank connection."},"itemId":{"type":"string","description":"The Plaid item ID for the connected bank account representing a user to bank relationship."}},"required":["id","mask","type","subtype","institution","createdUserId","itemId"],"additionalProperties":false,"description":"Details of the Plaid connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","plaidAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripe"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe account ID."}},"required":["id"],"additionalProperties":false,"description":"Details of the Stripe connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripeLoan"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeLoanAccount":{"type":"object","properties":{"stripeAccountId":{"type":"string","description":"The Stripe account ID this loan belongs to."},"flexLoanId":{"type":"string","description":"The Stripe Capital loan identifier. Usually the Flex Loan (`flxln_`) id, or a financing-offer (`financingoffer_`) id for loans on the newer Capital object whose payout was imported before any paydown revealed the `flxln_` id."}},"required":["stripeAccountId","flexLoanId"],"additionalProperties":false,"description":"Details of the Stripe Capital Flex Loan account."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeLoanAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["datevPayroll"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["finway"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["pleo"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false}]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","name","accountNumber","includeInCashBalance","accountGroupId","archived","expectedAnnualReturn","order","defaultCurrency","defaultCategoryId","importType","transitCurrency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/accounts/{accountId}/change_group":{"put":{"description":"Move one account into a different account group, or send `accountGroupId: null` to take it out of every group. Grouping is presentational and does not affect balances. Use `/accounts/reorder` when you also need to set the position inside the target group. Fails with 404 if the account or the group does not exist.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"accountGroupId":{"type":"string","nullable":true}},"required":["accountGroupId"]}}}},"responses":{"200":{"description":"Account group changed","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"name":{"type":"string","description":"The name of the account."},"accountNumber":{"type":"string","nullable":true,"description":"The bank account number. Null if not provided."},"includeInCashBalance":{"type":"boolean","description":"Whether this account should be included in cash balance calculations and cash flow reports."},"accountGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the account group this account belongs to. Null if not part of any group."},"archived":{"type":"boolean","description":"Flag indicating if the account is archived and should be hidden from most views."},"expectedAnnualReturn":{"type":"number","nullable":true,"minimum":-8388608,"maximum":8388607,"description":"The expected annual return percentage for this account, if applicable."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering accounts in the UI."},"defaultCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The default currency for transactions recorded under this account."},"defaultCategoryId":{"type":"string","nullable":true,"description":"The default category ID to assign to new transactions for this account."},"importType":{"type":"string","enum":["manual","automated"],"description":"Whether transactions are imported manually or via bank connection."},"transitCurrency":{"type":"string","nullable":true,"enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR",null]},"importSettings":{"type":"object","properties":{"csv":{"nullable":true}}},"bankConnection":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["finApi"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"finApiAccount":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the connected bank account."},"iban":{"type":"string","nullable":true,"description":"The IBAN of the connected bank account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"bic":{"type":"string","nullable":true,"description":"The BIC (Bank Identifier Code) of the institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."}},"required":["id","name","bic","logoStored"],"description":"Details of the banking institution connected via FinAPI."},"createdUserId":{"type":"string","description":"The SaaSFlow user ID of the user who set up this bank connection."}},"required":["id","iban","institution","createdUserId"],"additionalProperties":false,"description":"Details of the FinAPI connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","finApiAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["plaid"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"plaidAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid account ID for the connected bank account."},"mask":{"type":"string","nullable":true,"description":"The last 2-4 characters of the account number. Null if not available."},"type":{"type":"string","enum":["investment","credit","depository","loan","brokerage","other"],"description":"The type of the account."},"subtype":{"type":"string","nullable":true,"description":"The subtype of the account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid institution ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"url":{"type":"string","nullable":true,"description":"The URL of the banking institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."},"routingNumbers":{"type":"array","nullable":true,"items":{"type":"string"},"description":"The routing numbers of the banking institution. Null if not available."},"primaryColor":{"type":"string","nullable":true,"description":"The primary color of the banking institution. Null if not available."}},"required":["id","name","url","logoStored","routingNumbers","primaryColor"],"additionalProperties":false,"description":"Details of the banking institution connected via Plaid."},"createdUserId":{"type":"string","description":"The ID of the user who set up this bank connection."},"itemId":{"type":"string","description":"The Plaid item ID for the connected bank account representing a user to bank relationship."}},"required":["id","mask","type","subtype","institution","createdUserId","itemId"],"additionalProperties":false,"description":"Details of the Plaid connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","plaidAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripe"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe account ID."}},"required":["id"],"additionalProperties":false,"description":"Details of the Stripe connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripeLoan"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeLoanAccount":{"type":"object","properties":{"stripeAccountId":{"type":"string","description":"The Stripe account ID this loan belongs to."},"flexLoanId":{"type":"string","description":"The Stripe Capital loan identifier. Usually the Flex Loan (`flxln_`) id, or a financing-offer (`financingoffer_`) id for loans on the newer Capital object whose payout was imported before any paydown revealed the `flxln_` id."}},"required":["stripeAccountId","flexLoanId"],"additionalProperties":false,"description":"Details of the Stripe Capital Flex Loan account."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeLoanAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["datevPayroll"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["finway"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["pleo"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false}]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","name","accountNumber","includeInCashBalance","accountGroupId","archived","expectedAnnualReturn","order","defaultCurrency","defaultCategoryId","importType","transitCurrency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Account or account group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/accounts/{accountId}/switch_to_manual":{"put":{"description":"Detach an account from the integration feeding it and make it manually managed. Existing transactions stay; new ones stop arriving automatically and the account becomes editable by hand. Use this when a bank connection is being retired but its history should survive. Reconnecting later means setting the integration up again.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"responses":{"200":{"description":"Account switched to manual","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"name":{"type":"string","description":"The name of the account."},"accountNumber":{"type":"string","nullable":true,"description":"The bank account number. Null if not provided."},"includeInCashBalance":{"type":"boolean","description":"Whether this account should be included in cash balance calculations and cash flow reports."},"accountGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the account group this account belongs to. Null if not part of any group."},"archived":{"type":"boolean","description":"Flag indicating if the account is archived and should be hidden from most views."},"expectedAnnualReturn":{"type":"number","nullable":true,"minimum":-8388608,"maximum":8388607,"description":"The expected annual return percentage for this account, if applicable."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering accounts in the UI."},"defaultCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The default currency for transactions recorded under this account."},"defaultCategoryId":{"type":"string","nullable":true,"description":"The default category ID to assign to new transactions for this account."},"importType":{"type":"string","enum":["manual","automated"],"description":"Whether transactions are imported manually or via bank connection."},"transitCurrency":{"type":"string","nullable":true,"enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR",null]},"importSettings":{"type":"object","properties":{"csv":{"nullable":true}}},"bankConnection":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["finApi"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"finApiAccount":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the connected bank account."},"iban":{"type":"string","nullable":true,"description":"The IBAN of the connected bank account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"number","description":"The FinAPI internal ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"bic":{"type":"string","nullable":true,"description":"The BIC (Bank Identifier Code) of the institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."}},"required":["id","name","bic","logoStored"],"description":"Details of the banking institution connected via FinAPI."},"createdUserId":{"type":"string","description":"The SaaSFlow user ID of the user who set up this bank connection."}},"required":["id","iban","institution","createdUserId"],"additionalProperties":false,"description":"Details of the FinAPI connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","finApiAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["plaid"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"plaidAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid account ID for the connected bank account."},"mask":{"type":"string","nullable":true,"description":"The last 2-4 characters of the account number. Null if not available."},"type":{"type":"string","enum":["investment","credit","depository","loan","brokerage","other"],"description":"The type of the account."},"subtype":{"type":"string","nullable":true,"description":"The subtype of the account. Null if not available."},"institution":{"type":"object","properties":{"id":{"type":"string","description":"The Plaid institution ID for the banking institution."},"name":{"type":"string","description":"The name of the banking institution."},"url":{"type":"string","nullable":true,"description":"The URL of the banking institution. Null if not available."},"logoStored":{"type":"boolean","description":"Flag indicating if a logo is available for this bank."},"routingNumbers":{"type":"array","nullable":true,"items":{"type":"string"},"description":"The routing numbers of the banking institution. Null if not available."},"primaryColor":{"type":"string","nullable":true,"description":"The primary color of the banking institution. Null if not available."}},"required":["id","name","url","logoStored","routingNumbers","primaryColor"],"additionalProperties":false,"description":"Details of the banking institution connected via Plaid."},"createdUserId":{"type":"string","description":"The ID of the user who set up this bank connection."},"itemId":{"type":"string","description":"The Plaid item ID for the connected bank account representing a user to bank relationship."}},"required":["id","mask","type","subtype","institution","createdUserId","itemId"],"additionalProperties":false,"description":"Details of the Plaid connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","plaidAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripe"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeAccount":{"type":"object","properties":{"id":{"type":"string","description":"The Stripe account ID."}},"required":["id"],"additionalProperties":false,"description":"Details of the Stripe connected account, if applicable. Null otherwise."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["stripeLoan"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"},"stripeLoanAccount":{"type":"object","properties":{"stripeAccountId":{"type":"string","description":"The Stripe account ID this loan belongs to."},"flexLoanId":{"type":"string","description":"The Stripe Capital loan identifier. Usually the Flex Loan (`flxln_`) id, or a financing-offer (`financingoffer_`) id for loans on the newer Capital object whose payout was imported before any paydown revealed the `flxln_` id."}},"required":["stripeAccountId","flexLoanId"],"additionalProperties":false,"description":"Details of the Stripe Capital Flex Loan account."}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime","stripeLoanAccount"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["datevPayroll"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["finway"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["pleo"]},"countryCode":{"type":"string","nullable":true,"description":"The country of the bank this connection belongs to. Null if not available."},"status":{"type":"string","enum":["active","balanceMismatch","expired","error"],"description":"The current status of the bank connection."},"integrationId":{"type":"string","nullable":true,"description":"The ID of the integration that manages this bank account. Null if not managed by an integration."},"expireTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime when the bank connection is expected to expire. Null if not applicable or no expiry. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful synchronization with the bank. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"balanceMismatchReportedBalances":{"type":"array","items":{"type":"number"},"description":"The expected balances reported by the bank when a balance mismatch occurred. Can contain multiple values, e.g. balance with and without pending transactions. Only present when status is 'balanceMismatch'."},"balanceMismatchCalculatedBalance":{"type":"number","description":"The balance derived from summing all transactions when a balance mismatch occurred. Only present when status is 'balanceMismatch'."},"balanceMismatchSince":{"type":"string","format":"date-time","description":"The ISO datetime since when automated imports have consecutively failed the balance consistency check. Used to confirm a mismatch before it is surfaced, so it can be present while status is still 'active'. Cleared by the next successful import. Format: YYYY-MM-DDTHH:MM:SSZ"}},"required":["type","countryCode","status","integrationId","expireTime","lastSyncTime"],"additionalProperties":false}]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","name","accountNumber","includeInCashBalance","accountGroupId","archived","expectedAnnualReturn","order","defaultCurrency","defaultCategoryId","importType","transitCurrency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/accounts/{accountId}/remove_history":{"post":{"description":"Permanently delete this one account's transactions before `beforeDate` and replace them with a single balance transaction, so the running balance stays correct while the detail goes away. Useful for trimming a long import back to the period the books actually start from. For the company-wide version that also sets the data start date, use `POST /accounts/remove_history`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"beforeDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},"required":["beforeDate"]}}}},"responses":{"200":{"description":"History removed and balance transaction created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/accounts/{accountId}/initial_balance":{"get":{"description":"Get the account's initial-balance seed, the synthetic transaction that carries whatever the account held before its first imported transaction. Returns `{ transaction: null }` when no seed exists, which is the usual reason a running balance looks right in shape but wrong by a constant amount.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"responses":{"200":{"description":"Initial-balance transaction of the account","content":{"application/json":{"schema":{"type":"object","properties":{"transaction":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid","description":"The ID of the account associated with this transaction."},"date":{"type":"string","description":"The date of the transaction (YYYY-MM-DD)."},"fromTo":{"type":"string","nullable":true,"description":"A description of the sender or recipient of the transaction."},"description":{"type":"string","nullable":true,"description":"A user- or bank-provided description or memo for the transaction."},"amountValue":{"type":"string","description":"The numerical value of the transaction amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the transaction amount."},"categorizationsAutoAssigned":{"type":"boolean","nullable":true,"description":"Flag indicating if the categorizations were automatically assigned."},"excludeFromAutoCategorization":{"type":"boolean","description":"Flag to exclude this transaction from automatic categorization."},"notes":{"type":"string","nullable":true,"description":"Additional notes or comments for the transaction."},"source":{"type":"string","nullable":true,"description":"The source of the transaction data (e.g., bank import, manual entry)."},"assignedUserId":{"type":"string","nullable":true,"description":"The ID of the user to whom this transaction is assigned for review."},"finApiTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from FinAPI, if applicable."},"plaidTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from Plaid, if applicable."},"stripeBalanceTransactionId":{"type":"string","nullable":true,"description":"The balance transaction ID from Stripe, if applicable."},"stripeInvoiceId":{"type":"string","nullable":true,"description":"The invoice ID from Stripe, if applicable."},"stripeCreditNoteId":{"type":"string","nullable":true,"description":"The credit note ID from Stripe, if applicable."},"stripePaymentIntentId":{"type":"string","nullable":true,"description":"The Stripe PaymentIntent ID from the charge source."},"stripeBillingEmail":{"type":"string","nullable":true,"description":"The customer billing email from the Stripe charge source."},"hubspotPortalId":{"type":"string","nullable":true,"description":"The HubSpot portal ID from Stripe balance transaction metadata."},"hubspotInvoiceId":{"type":"string","nullable":true,"description":"The HubSpot invoice ID from Stripe balance transaction metadata."},"hubspotCategorizationResolved":{"type":"boolean","nullable":true,"description":"Whether HubSpot categorization resolution has been attempted."},"finwayExpenseNumber":{"type":"string","nullable":true},"finwayInvoiceNumber":{"type":"string","nullable":true},"pleoExpenseId":{"type":"string","nullable":true},"createdVendorId":{"type":"string","nullable":true,"format":"uuid"},"integrationId":{"type":"string","nullable":true,"format":"uuid"},"pending":{"type":"boolean","description":"Flag indicating if the transaction is pending and not yet cleared."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"categorizations":{"type":"object","additionalProperties":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The ID of the category to categorize the transaction."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["custom"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["type","startDate","endDate"],"additionalProperties":false}],"description":"The date rule to apply to the transaction."},"computedStartDate":{"type":"string","format":"date","description":"The start date to apply to the transaction."},"computedEndDate":{"type":"string","format":"date","description":"The end date to apply to the transaction."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The amount of the revenue."},"cogs":{"type":"number","description":"The amount of the cost of goods sold."},"cac":{"type":"number","description":"The amount of the customer acquisition cost."},"pd":{"type":"number","description":"The amount of the product development."},"ga":{"type":"number","description":"The amount of the general and administrative."},"no":{"type":"number","description":"The amount of the non-operating expenses."},"neutral":{"type":"number","description":"The amount assigned to neutral categories."}},"additionalProperties":false,"description":"The split of the transaction amount into different Profit & Loss (P&L) types within the same category."},"passThroughTax":{"type":"number","description":"The amount of the pass-through tax (e.g. VAT) that is not important for a profit & loss calculation."},"transferCounterPosition":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction that holds the categorization which is the counter object for a transfer between accounts."},"categorizationId":{"type":"string","description":"The ID of the categorization that is the counter object for a transfer between accounts."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The counter object in a transfer between accounts. Only for categorizations of type \"transfer\"."},"internalTransitLink":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction on the same transit account that holds the linked categorization."},"categorizationId":{"type":"string","description":"The ID of the linked categorization on the same transit account."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The sibling categorization on the same transit account that closes an in-flight transfer. Only for transfer categorizations on transit accounts."},"sourceNote":{"type":"string","nullable":true,"description":"Read-only context from the source system, e.g. the invoice line-item description behind this categorization. Set by integrations; not user-editable."},"externalInvoiceUrn":{"type":"string","nullable":true,"description":"The external invoice this categorization was derived from, when the transaction was linked to one, as `{system}:invoice:{accountId}:{invoiceId}`. Set per categorization rather than per transaction because a single payment can settle several invoices."}},"required":["counterparty","categoryId","usingDateRule","computedStartDate","computedEndDate","plSplit","passThroughTax"],"additionalProperties":false},"description":"A record mapping categorization IDs to categorization details."}},"required":["id","companyId","accountId","date","fromTo","description","amountValue","amountCurrency","categorizationsAutoAssigned","excludeFromAutoCategorization","notes","source","assignedUserId","finApiTransactionId","plaidTransactionId","stripeBalanceTransactionId","stripeInvoiceId","stripeCreditNoteId","stripePaymentIntentId","stripeBillingEmail","hubspotPortalId","hubspotInvoiceId","hubspotCategorizationResolved","finwayExpenseNumber","finwayInvoiceNumber","pleoExpenseId","createdVendorId","integrationId","pending","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","categorizations"],"description":"The account's initial-balance seed transaction, or null if none exists."}},"required":["transaction"]}}}}}},"put":{"description":"Set or correct the account's opening balance, creating the seed transaction if there is none. `value` is the balance on `date` in the account's default currency, not a delta. The date has to fall strictly before every other transaction on the account: otherwise you get 422 with `initial_balance_not_first` and the `earliestDate` to go before. Accounts managed by an integration that does not allow hand-set balances are refused with `integration_managed`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"accountId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","pattern":"^-?\\d+(\\.\\d+)?$","description":"Balance of the account on the given date, in the account's default currency."},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Date of the seed transaction, typically the day before the first imported transaction."}},"required":["value","date"]}}}},"responses":{"200":{"description":"Created or updated initial-balance transaction","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid","description":"The ID of the account associated with this transaction."},"date":{"type":"string","description":"The date of the transaction (YYYY-MM-DD)."},"fromTo":{"type":"string","nullable":true,"description":"A description of the sender or recipient of the transaction."},"description":{"type":"string","nullable":true,"description":"A user- or bank-provided description or memo for the transaction."},"amountValue":{"type":"string","description":"The numerical value of the transaction amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the transaction amount."},"categorizationsAutoAssigned":{"type":"boolean","nullable":true,"description":"Flag indicating if the categorizations were automatically assigned."},"excludeFromAutoCategorization":{"type":"boolean","description":"Flag to exclude this transaction from automatic categorization."},"notes":{"type":"string","nullable":true,"description":"Additional notes or comments for the transaction."},"source":{"type":"string","nullable":true,"description":"The source of the transaction data (e.g., bank import, manual entry)."},"assignedUserId":{"type":"string","nullable":true,"description":"The ID of the user to whom this transaction is assigned for review."},"finApiTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from FinAPI, if applicable."},"plaidTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from Plaid, if applicable."},"stripeBalanceTransactionId":{"type":"string","nullable":true,"description":"The balance transaction ID from Stripe, if applicable."},"stripeInvoiceId":{"type":"string","nullable":true,"description":"The invoice ID from Stripe, if applicable."},"stripeCreditNoteId":{"type":"string","nullable":true,"description":"The credit note ID from Stripe, if applicable."},"stripePaymentIntentId":{"type":"string","nullable":true,"description":"The Stripe PaymentIntent ID from the charge source."},"stripeBillingEmail":{"type":"string","nullable":true,"description":"The customer billing email from the Stripe charge source."},"hubspotPortalId":{"type":"string","nullable":true,"description":"The HubSpot portal ID from Stripe balance transaction metadata."},"hubspotInvoiceId":{"type":"string","nullable":true,"description":"The HubSpot invoice ID from Stripe balance transaction metadata."},"hubspotCategorizationResolved":{"type":"boolean","nullable":true,"description":"Whether HubSpot categorization resolution has been attempted."},"finwayExpenseNumber":{"type":"string","nullable":true},"finwayInvoiceNumber":{"type":"string","nullable":true},"pleoExpenseId":{"type":"string","nullable":true},"createdVendorId":{"type":"string","nullable":true,"format":"uuid"},"integrationId":{"type":"string","nullable":true,"format":"uuid"},"pending":{"type":"boolean","description":"Flag indicating if the transaction is pending and not yet cleared."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"categorizations":{"type":"object","additionalProperties":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The ID of the category to categorize the transaction."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["custom"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["type","startDate","endDate"],"additionalProperties":false}],"description":"The date rule to apply to the transaction."},"computedStartDate":{"type":"string","format":"date","description":"The start date to apply to the transaction."},"computedEndDate":{"type":"string","format":"date","description":"The end date to apply to the transaction."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The amount of the revenue."},"cogs":{"type":"number","description":"The amount of the cost of goods sold."},"cac":{"type":"number","description":"The amount of the customer acquisition cost."},"pd":{"type":"number","description":"The amount of the product development."},"ga":{"type":"number","description":"The amount of the general and administrative."},"no":{"type":"number","description":"The amount of the non-operating expenses."},"neutral":{"type":"number","description":"The amount assigned to neutral categories."}},"additionalProperties":false,"description":"The split of the transaction amount into different Profit & Loss (P&L) types within the same category."},"passThroughTax":{"type":"number","description":"The amount of the pass-through tax (e.g. VAT) that is not important for a profit & loss calculation."},"transferCounterPosition":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction that holds the categorization which is the counter object for a transfer between accounts."},"categorizationId":{"type":"string","description":"The ID of the categorization that is the counter object for a transfer between accounts."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The counter object in a transfer between accounts. Only for categorizations of type \"transfer\"."},"internalTransitLink":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction on the same transit account that holds the linked categorization."},"categorizationId":{"type":"string","description":"The ID of the linked categorization on the same transit account."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The sibling categorization on the same transit account that closes an in-flight transfer. Only for transfer categorizations on transit accounts."},"sourceNote":{"type":"string","nullable":true,"description":"Read-only context from the source system, e.g. the invoice line-item description behind this categorization. Set by integrations; not user-editable."},"externalInvoiceUrn":{"type":"string","nullable":true,"description":"The external invoice this categorization was derived from, when the transaction was linked to one, as `{system}:invoice:{accountId}:{invoiceId}`. Set per categorization rather than per transaction because a single payment can settle several invoices."}},"required":["counterparty","categoryId","usingDateRule","computedStartDate","computedEndDate","plSplit","passThroughTax"],"additionalProperties":false},"description":"A record mapping categorization IDs to categorization details."}},"required":["id","companyId","accountId","date","fromTo","description","amountValue","amountCurrency","categorizationsAutoAssigned","excludeFromAutoCategorization","notes","source","assignedUserId","finApiTransactionId","plaidTransactionId","stripeBalanceTransactionId","stripeInvoiceId","stripeCreditNoteId","stripePaymentIntentId","stripeBillingEmail","hubspotPortalId","hubspotInvoiceId","hubspotCategorizationResolved","finwayExpenseNumber","finwayInvoiceNumber","pleoExpenseId","createdVendorId","integrationId","pending","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","categorizations"]}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"Account is managed by an integration without manual edits, or the date is not before the earliest transaction","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"error":{"type":"string","enum":["integration_managed"]}},"required":["error"]},{"type":"object","properties":{"error":{"type":"string","enum":["initial_balance_not_first"]},"earliestDate":{"type":"string","description":"Date of the earliest other transaction on the account. The initial balance must be dated strictly before it."}},"required":["error","earliestDate"]}]}}}}}}},"/companies/{companyId}/categories":{"get":{"description":"List a company's transaction categories. Each category declares the P&L lines it can post to through `plTypes` (revenue, cogs, cac, pd, ga, no, neutral), which is what lets one category split across the P&L. `used` counts everything currently assigned to the category, both transactions and planned transactions, so a zero there means the category can be deleted outright; `usedTransactions` and `usedPlannedTransactions` break that total down. Categories flagged `system` are created and maintained by SaaSFlow and cannot be deleted at all.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of categories","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the category."},"emoji":{"type":"string","nullable":true,"description":"An emoji representing the category."},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"description":"The P&L types that the category can be used for."},"system":{"type":"boolean","description":"Flag indicating if this is a system-defined category."},"systemKey":{"type":"string","nullable":true,"description":"Semantic key for system categories (e.g., revRecurring, paymentFees)."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering categories in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"used":{"type":"number","description":"Total number of things currently assigned to this category: transactions plus planned transactions. Zero means the category can be deleted outright."},"usedTransactions":{"type":"number","description":"How many transactions are categorized under this category."},"usedPlannedTransactions":{"type":"number","description":"How many planned transactions use this category."}},"required":["id","companyId","name","emoji","plTypes","system","systemKey","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","used","usedTransactions","usedPlannedTransactions"]}}}}}}},"post":{"description":"Create a category. `plTypes` decides where transactions in it land in the P&L, and `order` sets its position in the list. Leave `system` at its default of false; system categories are SaaSFlow-managed and behave differently on delete.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"emoji":{"type":"string","nullable":true},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]}},"system":{"type":"boolean","default":false},"order":{"type":"number"}},"required":["name","plTypes","order"]}}}},"responses":{"201":{"description":"Created category","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the category."},"emoji":{"type":"string","nullable":true,"description":"An emoji representing the category."},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"description":"The P&L types that the category can be used for."},"system":{"type":"boolean","description":"Flag indicating if this is a system-defined category."},"systemKey":{"type":"string","nullable":true,"description":"Semantic key for system categories (e.g., revRecurring, paymentFees)."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering categories in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","emoji","plTypes","system","systemKey","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"/companies/{companyId}/categories/{categoryId}":{"get":{"description":"Retrieve a single category by id. Returns 404 if the category does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"categoryId","in":"path"}],"responses":{"200":{"description":"Category details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the category."},"emoji":{"type":"string","nullable":true,"description":"An emoji representing the category."},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"description":"The P&L types that the category can be used for."},"system":{"type":"boolean","description":"Flag indicating if this is a system-defined category."},"systemKey":{"type":"string","nullable":true,"description":"Semantic key for system categories (e.g., revRecurring, paymentFees)."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering categories in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","emoji","plTypes","system","systemKey","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Category not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a category's name, emoji, `plTypes`, or order. Only the fields you send change. Narrowing `plTypes` does not rewrite transactions already categorized under the removed P&L type, so check reporting after a change like that.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"categoryId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"emoji":{"type":"string","nullable":true},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]}},"order":{"type":"number"}}}}}},"responses":{"200":{"description":"Updated category","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the category."},"emoji":{"type":"string","nullable":true,"description":"An emoji representing the category."},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"description":"The P&L types that the category can be used for."},"system":{"type":"boolean","description":"Flag indicating if this is a system-defined category."},"systemKey":{"type":"string","nullable":true,"description":"Semantic key for system categories (e.g., revRecurring, paymentFees)."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering categories in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","emoji","plTypes","system","systemKey","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Category not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a category. Refused with 409 while any transaction is still categorized under it or any planned transaction still uses it, and with 400 for system categories, which SaaSFlow manages. To retire a category that is in use, merge it into another one instead.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"categoryId","in":"path"}],"responses":{"200":{"description":"Category deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"400":{"description":"Cannot delete system category","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Category is in use by transactions","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/categories/batch_delete":{"post":{"description":"Delete several categories at once. All or nothing: if any id in the list is a system category, or still has transactions or planned transactions attached, nothing is deleted and the call fails with 400 or 409.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"categoryIds":{"type":"array","items":{"type":"string","minLength":1}}},"required":["categoryIds"]}}}},"responses":{"200":{"description":"Categories deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"A selected category is in use by transactions or planned transactions","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/categories/merge":{"post":{"description":"Merge several categories into one, in a single transaction. Creates the category described by `newCategory`, then repoints everything that referenced the old ids: transaction categorizations, the default categorization on customers and vendors, and the default category on accounts. The old categories are deleted afterwards. This is how to retire duplicates without stranding history, since a plain delete is refused while transactions still use the category. One thing to know before running it: plan items on the old categories are deleted rather than repointed, so a merge drops that part of the financial plan. Returns the new category.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"categoryIds":{"type":"array","items":{"type":"string","minLength":1}},"newCategory":{"type":"object","properties":{"name":{"type":"string","minLength":1},"emoji":{"type":"string","nullable":true},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]}},"order":{"type":"number"}},"required":["name","plTypes","order"]}},"required":["categoryIds","newCategory"]}}}},"responses":{"200":{"description":"The merged category","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the category."},"emoji":{"type":"string","nullable":true,"description":"An emoji representing the category."},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"description":"The P&L types that the category can be used for."},"system":{"type":"boolean","description":"Flag indicating if this is a system-defined category."},"systemKey":{"type":"string","nullable":true,"description":"Semantic key for system categories (e.g., revRecurring, paymentFees)."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering categories in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","emoji","plTypes","system","systemKey","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"/companies/{companyId}/categories/reorder":{"put":{"description":"Set the display order of categories. Each id's `order` becomes its position in the array you send, so send the full list rather than the few you moved.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"categoryIds":{"type":"array","items":{"type":"string","minLength":1}}},"required":["categoryIds"]}}}},"responses":{"200":{"description":"Order updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/customers":{"get":{"description":"List customers, sorted by name, each enriched with its transaction count, subscription count, and current committed MRR. The response shape depends on `limit`: send it and you get `{ items, totalCount }`, omit it and you get a bare array of every customer. `q` matches on name, and `onlyActive=true` keeps just the customers whose current cMRR is above zero. The enrichment fields are gated separately from customer master data, so a caller without `transactions:read` or `subscriptions:read` gets those keys omitted rather than nulled.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"q","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500},"required":false,"name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string","enum":["0","1","true","false"]},"required":false,"name":"onlyActive","in":"query"}],"responses":{"200":{"description":"List of customers","content":{"application/json":{"schema":{"nullable":true}}}}}},"post":{"description":"Create a customer. `domains` are the email or web domains used to match incoming payments to this customer automatically. `defaultCategorization` is the template applied to transactions attributed to them, so setting it once saves categorizing every future payment by hand. `countryCode` is ISO 3166-1 alpha-2.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string"},"usingDateRule":{"nullable":true},"plSplit":{"type":"object","additionalProperties":{"type":"number"}},"passThroughTax":{"type":"number"}},"required":["categoryId"]},"domains":{"type":"array","items":{"type":"string"}},"logo":{"type":"boolean"},"countryCode":{"type":"string","nullable":true}},"required":["name","logo"]}}}},"responses":{"201":{"description":"Created customer","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the customer."},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"The default categorization to apply to transactions from this customer."},"domains":{"type":"array","items":{"type":"string"},"description":"An array of website domains associated with this customer."},"logo":{"type":"boolean","description":"Flag indicating if a logo is available for this customer."},"autoCreatedSource":{"type":"string","nullable":true,"enum":["searchApiGoogleWebSearch","stripe","hubspot","datevPayroll","finway","pleo",null],"description":"The source from which this customer was automatically created."},"countryCode":{"type":"string","nullable":true,"description":"ISO 3166-1 alpha-2 country code (e.g., US, DE, GB)."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this customer (e.g., Stripe, HubSpot)."}},"required":["id","companyId","name","defaultCategorization","domains","logo","autoCreatedSource","countryCode","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}}}}},"/companies/{companyId}/customers/{customerId}":{"get":{"description":"Retrieve a single customer by id. Returns 404 if the customer does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"customerId","in":"path"}],"responses":{"200":{"description":"Customer details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the customer."},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"The default categorization to apply to transactions from this customer."},"domains":{"type":"array","items":{"type":"string"},"description":"An array of website domains associated with this customer."},"logo":{"type":"boolean","description":"Flag indicating if a logo is available for this customer."},"autoCreatedSource":{"type":"string","nullable":true,"enum":["searchApiGoogleWebSearch","stripe","hubspot","datevPayroll","finway","pleo",null],"description":"The source from which this customer was automatically created."},"countryCode":{"type":"string","nullable":true,"description":"ISO 3166-1 alpha-2 country code (e.g., US, DE, GB)."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this customer (e.g., Stripe, HubSpot)."}},"required":["id","companyId","name","defaultCategorization","domains","logo","autoCreatedSource","countryCode","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a customer. Only the fields you send change. Changing `defaultCategorization` affects future categorizations only; transactions already categorized keep what they have.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"customerId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string"},"usingDateRule":{"nullable":true},"plSplit":{"type":"object","additionalProperties":{"type":"number"}},"passThroughTax":{"type":"number"}},"required":["categoryId"]},"domains":{"type":"array","items":{"type":"string"}},"logo":{"type":"boolean"},"countryCode":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Updated customer","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the customer."},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"The default categorization to apply to transactions from this customer."},"domains":{"type":"array","items":{"type":"string"},"description":"An array of website domains associated with this customer."},"logo":{"type":"boolean","description":"Flag indicating if a logo is available for this customer."},"autoCreatedSource":{"type":"string","nullable":true,"enum":["searchApiGoogleWebSearch","stripe","hubspot","datevPayroll","finway","pleo",null],"description":"The source from which this customer was automatically created."},"countryCode":{"type":"string","nullable":true,"description":"ISO 3166-1 alpha-2 country code (e.g., US, DE, GB)."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this customer (e.g., Stripe, HubSpot)."}},"required":["id","companyId","name","defaultCategorization","domains","logo","autoCreatedSource","countryCode","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"404":{"description":"Customer not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a customer. This reaches further than it looks: their subscriptions are cascade-deleted, which changes MRR history, and any planned transaction pointing at them is unassigned. Transactions survive but lose their counterparty, and each one is flagged to stay out of auto-categorization so the deleted customer is not immediately re-matched. For a duplicate record, merge instead of deleting.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"customerId","in":"path"}],"responses":{"200":{"description":"Customer deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/customers/batch_delete":{"post":{"description":"Delete many customers in one call. `selection` works two ways: `mode: \"ids\"` with an explicit list, or `mode: \"filter\"` with the same filter the list endpoint takes (`q`, `onlyActive`) plus `excludedIds` for the rows the user unticked. Filter mode is what \"select all matching\" in the UI sends, so nothing has to round-trip every id. Selections resolving to more than 10,000 customers are refused with 413 and a body giving the count and the cap. The response reports how many rows were actually affected.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"selection":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["ids"]},"ids":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":10000,"description":"Explicit list of entity IDs to act on."}},"required":["mode","ids"]},{"type":"object","properties":{"mode":{"type":"string","enum":["filter"]},"filter":{"type":"object","properties":{"q":{"type":"string"},"onlyActive":{"type":"boolean"}},"description":"Filter spec matching the list endpoint; resolves to all matching rows."},"excludedIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":10000,"default":[],"description":"IDs to exclude from the resolved filter result."}},"required":["mode","filter"]}]}},"required":["selection"]}}}},"responses":{"200":{"description":"Customers deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"affected":{"type":"integer","minimum":0}},"required":["success","affected"]}}}},"413":{"description":"Selection exceeds bulk cap","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["bulk_too_many"]},"count":{"type":"integer","minimum":0},"max":{"type":"integer","minimum":0}},"required":["error","count","max"]}}}}}}},"/companies/{companyId}/customers/merge":{"post":{"description":"Merge duplicate customers into one, in a single transaction. Creates the customer described by `newCustomer`, repoints the transaction categorizations, subscriptions, and external ids of every id in `customerIds` onto it, then deletes the originals. Use this rather than delete when the same account was imported twice: it keeps revenue history and MRR continuous, where a delete would cascade the subscriptions away. Returns the merged customer with its `logoUrl` and combined `externalIds`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"customerIds":{"type":"array","items":{"type":"string","minLength":1}},"newCustomer":{"type":"object","properties":{"name":{"type":"string","minLength":1},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string"},"usingDateRule":{"nullable":true},"plSplit":{"type":"object","additionalProperties":{"type":"number"}},"passThroughTax":{"type":"number"}},"required":["categoryId"]},"domains":{"type":"array","items":{"type":"string"}},"logo":{"type":"boolean"},"countryCode":{"type":"string","nullable":true}},"required":["name","logo"]}},"required":["customerIds","newCustomer"]}}}},"responses":{"200":{"description":"The merged customer","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the customer."},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"The default categorization to apply to transactions from this customer."},"domains":{"type":"array","items":{"type":"string"},"description":"An array of website domains associated with this customer."},"logo":{"type":"boolean","description":"Flag indicating if a logo is available for this customer."},"autoCreatedSource":{"type":"string","nullable":true,"enum":["searchApiGoogleWebSearch","stripe","hubspot","datevPayroll","finway","pleo",null],"description":"The source from which this customer was automatically created."},"countryCode":{"type":"string","nullable":true,"description":"ISO 3166-1 alpha-2 country code (e.g., US, DE, GB)."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this customer (e.g., Stripe, HubSpot)."}},"required":["id","companyId","name","defaultCategorization","domains","logo","autoCreatedSource","countryCode","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}}}}},"/companies/{companyId}/customers/{customerId}/external-ids":{"get":{"description":"List the external ids linked to a customer. These are the identifiers this customer carries in connected systems (a Stripe customer id, a HubSpot company id, and so on), and they are what lets an importer recognise the same customer across syncs.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"customerId","in":"path"}],"responses":{"200":{"description":"External IDs for the customer","content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}}}},"put":{"description":"Replace a customer's external ids with the list you send. This is a full replacement, not a merge, so include the ids you want to keep. An id already claimed by a different customer in the same company is refused with 409, which keeps imports from splitting one customer across two records.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"customerId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}},"responses":{"200":{"description":"Updated external IDs","content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}},"409":{"description":"One or more externalIds already in use by another customer","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/vendors":{"get":{"description":"List vendors, each enriched with the number of transactions booked against it. The response shape depends on `limit`: send it and you get `{ items, totalCount }`, omit it and you get a bare array of every vendor. `q` matches on name. `nrTransactions` is omitted entirely for callers without `transactions:read`, so absence of the key means no permission rather than zero transactions.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"q","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500},"required":false,"name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"List of vendors","content":{"application/json":{"schema":{"nullable":true}}}}}},"post":{"description":"Create a vendor. `defaultCategorization` is the template applied to every future transaction matched to this vendor: category, date rule, P&L split, and pass-through tax. Getting it right once is what keeps recurring spend categorizing itself. `domains` feed the matching. A default categorization booking a transfer must name an account that can actually receive one, otherwise the request fails with 400.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string"},"usingDateRule":{"nullable":true},"plSplit":{"type":"object","additionalProperties":{"type":"number"}},"passThroughTax":{"type":"number"},"transferTargetAccountId":{"type":"string","nullable":true}},"required":["categoryId"]},"domains":{"type":"array","items":{"type":"string"}},"logo":{"type":"boolean","default":false}},"required":["name"]}}}},"responses":{"201":{"description":"Created vendor","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the vendor."},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"The default categorization to apply to transactions from this vendor."},"domains":{"type":"array","items":{"type":"string"},"description":"An array of website domains associated with this vendor."},"logo":{"type":"boolean","description":"Flag indicating if a logo is available for this vendor."},"autoCreatedSource":{"type":"string","nullable":true,"enum":["searchApiGoogleWebSearch","stripe","hubspot","datevPayroll","finway","pleo",null],"description":"The source from which this vendor was automatically created."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this vendor (e.g., DATEV Payroll)."}},"required":["id","companyId","name","defaultCategorization","domains","logo","autoCreatedSource","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"400":{"description":"Invalid transfer target account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/vendors/{vendorId}":{"get":{"description":"Retrieve a single vendor by id, including its default categorization. Returns 404 if the vendor does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"vendorId","in":"path"}],"responses":{"200":{"description":"Vendor details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the vendor."},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"The default categorization to apply to transactions from this vendor."},"domains":{"type":"array","items":{"type":"string"},"description":"An array of website domains associated with this vendor."},"logo":{"type":"boolean","description":"Flag indicating if a logo is available for this vendor."},"autoCreatedSource":{"type":"string","nullable":true,"enum":["searchApiGoogleWebSearch","stripe","hubspot","datevPayroll","finway","pleo",null],"description":"The source from which this vendor was automatically created."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this vendor (e.g., DATEV Payroll)."}},"required":["id","companyId","name","defaultCategorization","domains","logo","autoCreatedSource","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"404":{"description":"Vendor not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a vendor. Only the fields you send change. A new `defaultCategorization` applies to future matches only; to push it onto transactions already booked against this vendor, call `apply-default-categorization` afterwards.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"vendorId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string"},"usingDateRule":{"nullable":true},"plSplit":{"type":"object","additionalProperties":{"type":"number"}},"passThroughTax":{"type":"number"},"transferTargetAccountId":{"type":"string","nullable":true}},"required":["categoryId"]},"domains":{"type":"array","items":{"type":"string"}},"logo":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated vendor","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the vendor."},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"The default categorization to apply to transactions from this vendor."},"domains":{"type":"array","items":{"type":"string"},"description":"An array of website domains associated with this vendor."},"logo":{"type":"boolean","description":"Flag indicating if a logo is available for this vendor."},"autoCreatedSource":{"type":"string","nullable":true,"enum":["searchApiGoogleWebSearch","stripe","hubspot","datevPayroll","finway","pleo",null],"description":"The source from which this vendor was automatically created."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this vendor (e.g., DATEV Payroll)."}},"required":["id","companyId","name","defaultCategorization","domains","logo","autoCreatedSource","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"400":{"description":"Invalid transfer target account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Vendor not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a vendor. Transactions booked against it survive but lose their counterparty, and each one is flagged to stay out of auto-categorization so the deleted vendor is not immediately re-matched. For a duplicate record, merge instead.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"vendorId","in":"path"}],"responses":{"200":{"description":"Vendor deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/vendors/{vendorId}/apply-default-categorization/preview":{"get":{"description":"Count what re-applying this vendor's default categorization would touch, without changing anything. The three buckets map onto the `applyTo` choices on the write endpoint: `empty` is transactions with no categorization yet, `autoAssigned` is ones SaaSFlow categorized itself, and `userEdited` is ones a person set by hand. Show these numbers before overwriting, since `applyTo: \"all\"` is what discards manual work.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"vendorId","in":"path"}],"responses":{"200":{"description":"Counts of transactions affected by re-applying default categorization","content":{"application/json":{"schema":{"type":"object","properties":{"empty":{"type":"integer","minimum":0},"autoAssigned":{"type":"integer","minimum":0},"userEdited":{"type":"integer","minimum":0}},"required":["empty","autoAssigned","userEdited"]}}}}}}},"/companies/{companyId}/vendors/{vendorId}/apply-default-categorization":{"post":{"description":"Re-apply a vendor's default categorization to transactions already booked against it. `applyTo` decides how far it reaches: `empty` only fills in transactions with no categorization, `autoAssigned` also overwrites what SaaSFlow categorized itself, and `all` overwrites hand-edited categorizations too. Run the `/preview` endpoint first to see the counts. If the template books a transfer, the missing counter transactions are created and counted in the response. Fails with 400 when the vendor has no default categorization to apply.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"vendorId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"applyTo":{"type":"string","enum":["empty","autoAssigned","all"]}},"required":["applyTo"]}}}},"responses":{"200":{"description":"Re-application complete","content":{"application/json":{"schema":{"type":"object","properties":{"insertedSlices":{"type":"integer","minimum":0},"updatedSlices":{"type":"integer","minimum":0},"counterTransactionsCreated":{"type":"integer","minimum":0}},"required":["insertedSlices","updatedSlices","counterTransactionsCreated"]}}}},"400":{"description":"Vendor has no default categorization","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Vendor not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/vendors/batch_delete":{"post":{"description":"Delete many vendors in one call. `selection` works two ways: `mode: \"ids\"` with an explicit list, or `mode: \"filter\"` with the same `q` the list endpoint takes plus `excludedIds` for the rows the user unticked. Filter mode is what \"select all matching\" sends, so nothing has to round-trip every id. Selections resolving to more than 10,000 vendors are refused with 413 and a body giving the count and the cap. The response reports how many rows were affected.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"selection":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["ids"]},"ids":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":10000,"description":"Explicit list of entity IDs to act on."}},"required":["mode","ids"]},{"type":"object","properties":{"mode":{"type":"string","enum":["filter"]},"filter":{"type":"object","properties":{"q":{"type":"string"}},"description":"Filter spec matching the list endpoint; resolves to all matching rows."},"excludedIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":10000,"default":[],"description":"IDs to exclude from the resolved filter result."}},"required":["mode","filter"]}]}},"required":["selection"]}}}},"responses":{"200":{"description":"Vendors deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"affected":{"type":"integer","minimum":0}},"required":["success","affected"]}}}},"413":{"description":"Selection exceeds bulk cap","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["bulk_too_many"]},"count":{"type":"integer","minimum":0},"max":{"type":"integer","minimum":0}},"required":["error","count","max"]}}}}}}},"/companies/{companyId}/vendors/merge":{"post":{"description":"Merge duplicate vendors into one. Creates the vendor described in the body, repoints the transactions and external ids of every id in `vendorIds` onto it, then deletes the originals. Use this when the same supplier came in under several spellings, since it keeps spend history in one place instead of scattering it. Returns the merged vendor.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"vendorIds":{"type":"array","items":{"type":"string","minLength":1}},"newVendor":{"type":"object","properties":{"name":{"type":"string","minLength":1},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string"},"usingDateRule":{"nullable":true},"plSplit":{"type":"object","additionalProperties":{"type":"number"}},"passThroughTax":{"type":"number"},"transferTargetAccountId":{"type":"string","nullable":true}},"required":["categoryId"]},"domains":{"type":"array","items":{"type":"string"}},"logo":{"type":"boolean","default":false}},"required":["name"]}},"required":["vendorIds","newVendor"]}}}},"responses":{"200":{"description":"The merged vendor","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the vendor."},"defaultCategorization":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"The default categorization to apply to transactions from this vendor."},"domains":{"type":"array","items":{"type":"string"},"description":"An array of website domains associated with this vendor."},"logo":{"type":"boolean","description":"Flag indicating if a logo is available for this vendor."},"autoCreatedSource":{"type":"string","nullable":true,"enum":["searchApiGoogleWebSearch","stripe","hubspot","datevPayroll","finway","pleo",null],"description":"The source from which this vendor was automatically created."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this vendor (e.g., DATEV Payroll)."}},"required":["id","companyId","name","defaultCategorization","domains","logo","autoCreatedSource","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"400":{"description":"Invalid transfer target account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/vendors/{vendorId}/external-ids":{"get":{"description":"List the external ids linked to a vendor. These are the identifiers the vendor carries in connected systems, and they are what lets an importer recognise the same supplier across syncs.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"vendorId","in":"path"}],"responses":{"200":{"description":"External IDs for the vendor","content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}}}},"put":{"description":"Replace a vendor's external ids with the list you send. This is a full replacement, not a merge, so include the ids you want to keep. An id already claimed by a different vendor in the same company is refused with 409.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"vendorId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}},"responses":{"200":{"description":"Updated external IDs","content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}},"409":{"description":"One or more externalIds already in use by another vendor","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/counterparties/search":{"get":{"description":"Search customers and vendors together by name or domain, returning a light record (id, name, type, domains, logo, default categorization) rather than the full entity. Built for pickers and autocomplete. `q` matches names and domains case-insensitively and may be omitted to get the first `limit` counterparties alphabetically. Narrow the result with `types=customer` or `types=vendor`; the caller still only sees types their permissions allow, so a grant with `customers:read` alone never returns vendors.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"q","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":5000,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string"},"required":false,"name":"types","in":"query"}],"responses":{"200":{"description":"Lightweight counterparty search","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/transactions":{"get":{"description":"List transactions, paginated, with their categorizations attached. This is the simple listing: filter by account, or by `status: \"uncategorized\"` for rows with no categorization at all. For anything richer (date ranges, amounts, counterparties, categories, free text, sorting) use `POST /transactions/search`, whose `open` status is also broader than `uncategorized` here. Do not aggregate by paging through this endpoint; the `data` slices compute totals server-side.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"integer","minimum":1,"maximum":5000,"default":100,"description":"Number of transactions to return (1-5000, default 100)."},"required":false,"description":"Number of transactions to return (1-5000, default 100).","name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0,"description":"Number of transactions to skip (default 0)."},"required":false,"description":"Number of transactions to skip (default 0).","name":"offset","in":"query"},{"schema":{"type":"string","description":"Filter by account ID."},"required":false,"description":"Filter by account ID.","name":"accountId","in":"query"},{"schema":{"type":"string","enum":["uncategorized"],"description":"\"uncategorized\" returns only transactions with NO categorizations yet (strictly open: excludes partially categorized and incomplete-transfer rows, which the POST /search \"open\" status includes). Deterministic, server-side scope for bulk categorization tooling."},"required":false,"description":"\"uncategorized\" returns only transactions with NO categorizations yet (strictly open: excludes partially categorized and incomplete-transfer rows, which the POST /search \"open\" status includes). Deterministic, server-side scope for bulk categorization tooling.","name":"status","in":"query"},{"schema":{"type":"string","enum":["true","false"],"description":"When true, omits transactions on accounts whose categorizations are owned by their managing integration (Stripe, Finway, Pleo, DATEV Payroll): those rows are categorized by the integration, not by this tooling. Composes with any status. Defaults to false."},"required":false,"description":"When true, omits transactions on accounts whose categorizations are owned by their managing integration (Stripe, Finway, Pleo, DATEV Payroll): those rows are categorized by the integration, not by this tooling. Composes with any status. Defaults to false.","name":"excludeIntegrationOwnedAccounts","in":"query"}],"responses":{"200":{"description":"Paginated list of transactions","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid","description":"The ID of the account associated with this transaction."},"date":{"type":"string","description":"The date of the transaction (YYYY-MM-DD)."},"fromTo":{"type":"string","nullable":true,"description":"A description of the sender or recipient of the transaction."},"description":{"type":"string","nullable":true,"description":"A user- or bank-provided description or memo for the transaction."},"amountValue":{"type":"string","description":"The numerical value of the transaction amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the transaction amount."},"categorizationsAutoAssigned":{"type":"boolean","nullable":true,"description":"Flag indicating if the categorizations were automatically assigned."},"excludeFromAutoCategorization":{"type":"boolean","description":"Flag to exclude this transaction from automatic categorization."},"notes":{"type":"string","nullable":true,"description":"Additional notes or comments for the transaction."},"source":{"type":"string","nullable":true,"description":"The source of the transaction data (e.g., bank import, manual entry)."},"assignedUserId":{"type":"string","nullable":true,"description":"The ID of the user to whom this transaction is assigned for review."},"finApiTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from FinAPI, if applicable."},"plaidTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from Plaid, if applicable."},"stripeBalanceTransactionId":{"type":"string","nullable":true,"description":"The balance transaction ID from Stripe, if applicable."},"stripeInvoiceId":{"type":"string","nullable":true,"description":"The invoice ID from Stripe, if applicable."},"stripeCreditNoteId":{"type":"string","nullable":true,"description":"The credit note ID from Stripe, if applicable."},"stripePaymentIntentId":{"type":"string","nullable":true,"description":"The Stripe PaymentIntent ID from the charge source."},"stripeBillingEmail":{"type":"string","nullable":true,"description":"The customer billing email from the Stripe charge source."},"hubspotPortalId":{"type":"string","nullable":true,"description":"The HubSpot portal ID from Stripe balance transaction metadata."},"hubspotInvoiceId":{"type":"string","nullable":true,"description":"The HubSpot invoice ID from Stripe balance transaction metadata."},"hubspotCategorizationResolved":{"type":"boolean","nullable":true,"description":"Whether HubSpot categorization resolution has been attempted."},"finwayExpenseNumber":{"type":"string","nullable":true},"finwayInvoiceNumber":{"type":"string","nullable":true},"pleoExpenseId":{"type":"string","nullable":true},"createdVendorId":{"type":"string","nullable":true,"format":"uuid"},"integrationId":{"type":"string","nullable":true,"format":"uuid"},"pending":{"type":"boolean","description":"Flag indicating if the transaction is pending and not yet cleared."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"categorizations":{"type":"object","additionalProperties":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The ID of the category to categorize the transaction."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["custom"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["type","startDate","endDate"],"additionalProperties":false}],"description":"The date rule to apply to the transaction."},"computedStartDate":{"type":"string","format":"date","description":"The start date to apply to the transaction."},"computedEndDate":{"type":"string","format":"date","description":"The end date to apply to the transaction."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The amount of the revenue."},"cogs":{"type":"number","description":"The amount of the cost of goods sold."},"cac":{"type":"number","description":"The amount of the customer acquisition cost."},"pd":{"type":"number","description":"The amount of the product development."},"ga":{"type":"number","description":"The amount of the general and administrative."},"no":{"type":"number","description":"The amount of the non-operating expenses."},"neutral":{"type":"number","description":"The amount assigned to neutral categories."}},"additionalProperties":false,"description":"The split of the transaction amount into different Profit & Loss (P&L) types within the same category."},"passThroughTax":{"type":"number","description":"The amount of the pass-through tax (e.g. VAT) that is not important for a profit & loss calculation."},"transferCounterPosition":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction that holds the categorization which is the counter object for a transfer between accounts."},"categorizationId":{"type":"string","description":"The ID of the categorization that is the counter object for a transfer between accounts."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The counter object in a transfer between accounts. Only for categorizations of type \"transfer\"."},"internalTransitLink":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction on the same transit account that holds the linked categorization."},"categorizationId":{"type":"string","description":"The ID of the linked categorization on the same transit account."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The sibling categorization on the same transit account that closes an in-flight transfer. Only for transfer categorizations on transit accounts."},"sourceNote":{"type":"string","nullable":true,"description":"Read-only context from the source system, e.g. the invoice line-item description behind this categorization. Set by integrations; not user-editable."},"externalInvoiceUrn":{"type":"string","nullable":true,"description":"The external invoice this categorization was derived from, when the transaction was linked to one, as `{system}:invoice:{accountId}:{invoiceId}`. Set per categorization rather than per transaction because a single payment can settle several invoices."}},"required":["counterparty","categoryId","usingDateRule","computedStartDate","computedEndDate","plSplit","passThroughTax"],"additionalProperties":false},"description":"A record mapping categorization IDs to categorization details."},"inTransit":{"type":"boolean","description":"True when this transaction is part of an open in-transit transfer (booked onto a transit account but not yet matched to the arriving side). Drives the \"needs attention\" indicator alongside the open status."}},"required":["id","companyId","accountId","date","fromTo","description","amountValue","amountCurrency","categorizationsAutoAssigned","excludeFromAutoCategorization","notes","source","assignedUserId","finApiTransactionId","plaidTransactionId","stripeBalanceTransactionId","stripeInvoiceId","stripeCreditNoteId","stripePaymentIntentId","stripeBillingEmail","hubspotPortalId","hubspotInvoiceId","hubspotCategorizationResolved","finwayExpenseNumber","finwayInvoiceNumber","pleoExpenseId","createdVendorId","integrationId","pending","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","categorizations"]}},"totalCount":{"type":"number","description":"Total number of transactions matching the filters."}},"required":["items","totalCount"]}}}}}},"post":{"description":"Create a single transaction on an account. `amountValue` is a signed decimal string: negative for money leaving the account, positive for money arriving. `categorizations` can be supplied inline to book it in one call, and their amounts must sum to the transaction amount or the row counts as open. Accounts owned by an integration that does not allow manual edits reject this with 422, and an account that already has an opening balance rejects a second initial-balance row with 409. For more than a handful of rows, use `/transactions/import`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"accountId":{"type":"string","minLength":1},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"fromTo":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"amountValue":{"type":"string"},"amountCurrency":{"type":"string"},"categorizations":{"type":"object","nullable":true,"additionalProperties":{"type":"object","properties":{"plSplit":{"type":"object","additionalProperties":{"type":"number"}},"passThroughTax":{"type":"number"}},"additionalProperties":{"nullable":true}}},"notes":{"type":"string","nullable":true},"source":{"type":"string","nullable":true},"pending":{"type":"boolean","default":false},"excludeFromAutoCategorization":{"type":"boolean","default":false}},"required":["accountId","date","amountValue","amountCurrency"]}}}},"responses":{"201":{"description":"Created transaction","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid","description":"The ID of the account associated with this transaction."},"date":{"type":"string","description":"The date of the transaction (YYYY-MM-DD)."},"fromTo":{"type":"string","nullable":true,"description":"A description of the sender or recipient of the transaction."},"description":{"type":"string","nullable":true,"description":"A user- or bank-provided description or memo for the transaction."},"amountValue":{"type":"string","description":"The numerical value of the transaction amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the transaction amount."},"categorizationsAutoAssigned":{"type":"boolean","nullable":true,"description":"Flag indicating if the categorizations were automatically assigned."},"excludeFromAutoCategorization":{"type":"boolean","description":"Flag to exclude this transaction from automatic categorization."},"notes":{"type":"string","nullable":true,"description":"Additional notes or comments for the transaction."},"source":{"type":"string","nullable":true,"description":"The source of the transaction data (e.g., bank import, manual entry)."},"assignedUserId":{"type":"string","nullable":true,"description":"The ID of the user to whom this transaction is assigned for review."},"finApiTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from FinAPI, if applicable."},"plaidTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from Plaid, if applicable."},"stripeBalanceTransactionId":{"type":"string","nullable":true,"description":"The balance transaction ID from Stripe, if applicable."},"stripeInvoiceId":{"type":"string","nullable":true,"description":"The invoice ID from Stripe, if applicable."},"stripeCreditNoteId":{"type":"string","nullable":true,"description":"The credit note ID from Stripe, if applicable."},"stripePaymentIntentId":{"type":"string","nullable":true,"description":"The Stripe PaymentIntent ID from the charge source."},"stripeBillingEmail":{"type":"string","nullable":true,"description":"The customer billing email from the Stripe charge source."},"hubspotPortalId":{"type":"string","nullable":true,"description":"The HubSpot portal ID from Stripe balance transaction metadata."},"hubspotInvoiceId":{"type":"string","nullable":true,"description":"The HubSpot invoice ID from Stripe balance transaction metadata."},"hubspotCategorizationResolved":{"type":"boolean","nullable":true,"description":"Whether HubSpot categorization resolution has been attempted."},"finwayExpenseNumber":{"type":"string","nullable":true},"finwayInvoiceNumber":{"type":"string","nullable":true},"pleoExpenseId":{"type":"string","nullable":true},"createdVendorId":{"type":"string","nullable":true,"format":"uuid"},"integrationId":{"type":"string","nullable":true,"format":"uuid"},"pending":{"type":"boolean","description":"Flag indicating if the transaction is pending and not yet cleared."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"categorizations":{"type":"object","additionalProperties":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The ID of the category to categorize the transaction."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["custom"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["type","startDate","endDate"],"additionalProperties":false}],"description":"The date rule to apply to the transaction."},"computedStartDate":{"type":"string","format":"date","description":"The start date to apply to the transaction."},"computedEndDate":{"type":"string","format":"date","description":"The end date to apply to the transaction."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The amount of the revenue."},"cogs":{"type":"number","description":"The amount of the cost of goods sold."},"cac":{"type":"number","description":"The amount of the customer acquisition cost."},"pd":{"type":"number","description":"The amount of the product development."},"ga":{"type":"number","description":"The amount of the general and administrative."},"no":{"type":"number","description":"The amount of the non-operating expenses."},"neutral":{"type":"number","description":"The amount assigned to neutral categories."}},"additionalProperties":false,"description":"The split of the transaction amount into different Profit & Loss (P&L) types within the same category."},"passThroughTax":{"type":"number","description":"The amount of the pass-through tax (e.g. VAT) that is not important for a profit & loss calculation."},"transferCounterPosition":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction that holds the categorization which is the counter object for a transfer between accounts."},"categorizationId":{"type":"string","description":"The ID of the categorization that is the counter object for a transfer between accounts."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The counter object in a transfer between accounts. Only for categorizations of type \"transfer\"."},"internalTransitLink":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction on the same transit account that holds the linked categorization."},"categorizationId":{"type":"string","description":"The ID of the linked categorization on the same transit account."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The sibling categorization on the same transit account that closes an in-flight transfer. Only for transfer categorizations on transit accounts."},"sourceNote":{"type":"string","nullable":true,"description":"Read-only context from the source system, e.g. the invoice line-item description behind this categorization. Set by integrations; not user-editable."},"externalInvoiceUrn":{"type":"string","nullable":true,"description":"The external invoice this categorization was derived from, when the transaction was linked to one, as `{system}:invoice:{accountId}:{invoiceId}`. Set per categorization rather than per transaction because a single payment can settle several invoices."}},"required":["counterparty","categoryId","usingDateRule","computedStartDate","computedEndDate","plSplit","passThroughTax"],"additionalProperties":false},"description":"A record mapping categorization IDs to categorization details."}},"required":["id","companyId","accountId","date","fromTo","description","amountValue","amountCurrency","categorizationsAutoAssigned","excludeFromAutoCategorization","notes","source","assignedUserId","finApiTransactionId","plaidTransactionId","stripeBalanceTransactionId","stripeInvoiceId","stripeCreditNoteId","stripePaymentIntentId","stripeBillingEmail","hubspotPortalId","hubspotInvoiceId","hubspotCategorizationResolved","finwayExpenseNumber","finwayInvoiceNumber","pleoExpenseId","createdVendorId","integrationId","pending","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","categorizations"]}}}},"409":{"description":"The account already has an initial-balance transaction","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["initial_balance_exists"]}},"required":["error"]}}}},"422":{"description":"Account is managed by an integration without manual edits","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["integration_managed"]}},"required":["error"]}}}}}}},"/companies/{companyId}/transactions/search":{"post":{"description":"Search transactions with sorting and a composable filter set. It is a POST because the filter array is too structured for a query string, not because it changes anything. Each entry in `filters` is active simply by being present, and they combine with AND. This is the endpoint behind the transactions table in the app, so use it whenever the plain list is not enough. `balanceByDate` gives the running end-of-day balance and is only filled in when the page covers a single account and `sortBy` is `date`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":500,"default":100,"description":"Number of transactions to return (1-500, default 100)."},"offset":{"type":"integer","minimum":0,"default":0,"description":"Number of transactions to skip (default 0)."},"sortBy":{"type":"string","enum":["date","amountValue","fromTo"],"default":"date","description":"Column to sort by."},"sortDirection":{"type":"string","enum":["asc","desc"],"default":"desc","description":"Sort direction."},"filters":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["status"]},"status":{"type":"string","enum":["all","open","incompleteTransfer","inTransitStale"],"description":"\"all\" = no filter, \"open\" = uncategorized, partially categorized, or an incomplete transfer (no counter yet, or still in transit / not matched), \"incompleteTransfer\" = unfinished transfers: missing a counter (directly linked or not yet booked through transit) OR still in transit (booked onto a transit account but not yet matched to the arriving side); the real leg only, not its transit mirror, \"inTransitStale\" = the in-transit subset whose money left more than 14 days ago."}},"required":["type","status"]},{"type":"object","properties":{"type":{"type":"string","enum":["account"]},"accounts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Account IDs to include."}},"required":["type","accounts"]},{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["text"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["fromTo"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["description"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["amount"]},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no lower bound."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no upper bound."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["assignedUser"]},"userIds":{"type":"array","items":{"type":"string"},"minItems":1,"description":"User IDs to filter by. Omit to match any user."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no assigned user."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["quickSearch"]},"text":{"type":"string","description":"Searches fromTo, description, notes, and account name."},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["plType"]},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"minItems":1,"description":"P&L types to filter for."}},"required":["type","plTypes"]},{"type":"object","properties":{"type":{"type":"string","enum":["category"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to include. Omit to include all categories; pass an empty array to include none (use together with showUnassigned to match only uncategorized transactions)."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no categorizations."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["excludeCategory"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to exclude. An empty array excludes nothing."}},"required":["type","categories"]},{"type":"object","properties":{"type":{"type":"string","enum":["counterparty"]},"counterparties":{"type":"array","items":{"type":"string"},"description":"Counterparty IDs to include. Pass an empty array to include none (use together with showUnassigned to match only categorizations that have no counterparty)."},"showUnassigned":{"type":"boolean","default":false,"description":"Include categorizations with no counterparty."}},"required":["type","counterparties"]},{"type":"object","properties":{"type":{"type":"string","enum":["usingDate"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["cashFlowDirection"]},"direction":{"type":"string","enum":["inflow","outflow"],"description":"\"inflow\" = positive amounts, \"outflow\" = negative amounts."}},"required":["type","direction"]},{"type":"object","properties":{"type":{"type":"string","enum":["transactionIds"]},"ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100,"description":"Array of transaction IDs to match (max 100)."}},"required":["type","ids"]}]},"default":[],"description":"Array of filter objects. Each filter is active by its presence."}}}}}},"responses":{"200":{"description":"Paginated search results","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid","description":"The ID of the account associated with this transaction."},"date":{"type":"string","description":"The date of the transaction (YYYY-MM-DD)."},"fromTo":{"type":"string","nullable":true,"description":"A description of the sender or recipient of the transaction."},"description":{"type":"string","nullable":true,"description":"A user- or bank-provided description or memo for the transaction."},"amountValue":{"type":"string","description":"The numerical value of the transaction amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the transaction amount."},"categorizationsAutoAssigned":{"type":"boolean","nullable":true,"description":"Flag indicating if the categorizations were automatically assigned."},"excludeFromAutoCategorization":{"type":"boolean","description":"Flag to exclude this transaction from automatic categorization."},"notes":{"type":"string","nullable":true,"description":"Additional notes or comments for the transaction."},"source":{"type":"string","nullable":true,"description":"The source of the transaction data (e.g., bank import, manual entry)."},"assignedUserId":{"type":"string","nullable":true,"description":"The ID of the user to whom this transaction is assigned for review."},"finApiTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from FinAPI, if applicable."},"plaidTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from Plaid, if applicable."},"stripeBalanceTransactionId":{"type":"string","nullable":true,"description":"The balance transaction ID from Stripe, if applicable."},"stripeInvoiceId":{"type":"string","nullable":true,"description":"The invoice ID from Stripe, if applicable."},"stripeCreditNoteId":{"type":"string","nullable":true,"description":"The credit note ID from Stripe, if applicable."},"stripePaymentIntentId":{"type":"string","nullable":true,"description":"The Stripe PaymentIntent ID from the charge source."},"stripeBillingEmail":{"type":"string","nullable":true,"description":"The customer billing email from the Stripe charge source."},"hubspotPortalId":{"type":"string","nullable":true,"description":"The HubSpot portal ID from Stripe balance transaction metadata."},"hubspotInvoiceId":{"type":"string","nullable":true,"description":"The HubSpot invoice ID from Stripe balance transaction metadata."},"hubspotCategorizationResolved":{"type":"boolean","nullable":true,"description":"Whether HubSpot categorization resolution has been attempted."},"finwayExpenseNumber":{"type":"string","nullable":true},"finwayInvoiceNumber":{"type":"string","nullable":true},"pleoExpenseId":{"type":"string","nullable":true},"createdVendorId":{"type":"string","nullable":true,"format":"uuid"},"integrationId":{"type":"string","nullable":true,"format":"uuid"},"pending":{"type":"boolean","description":"Flag indicating if the transaction is pending and not yet cleared."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"categorizations":{"type":"object","additionalProperties":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The ID of the category to categorize the transaction."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["custom"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["type","startDate","endDate"],"additionalProperties":false}],"description":"The date rule to apply to the transaction."},"computedStartDate":{"type":"string","format":"date","description":"The start date to apply to the transaction."},"computedEndDate":{"type":"string","format":"date","description":"The end date to apply to the transaction."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The amount of the revenue."},"cogs":{"type":"number","description":"The amount of the cost of goods sold."},"cac":{"type":"number","description":"The amount of the customer acquisition cost."},"pd":{"type":"number","description":"The amount of the product development."},"ga":{"type":"number","description":"The amount of the general and administrative."},"no":{"type":"number","description":"The amount of the non-operating expenses."},"neutral":{"type":"number","description":"The amount assigned to neutral categories."}},"additionalProperties":false,"description":"The split of the transaction amount into different Profit & Loss (P&L) types within the same category."},"passThroughTax":{"type":"number","description":"The amount of the pass-through tax (e.g. VAT) that is not important for a profit & loss calculation."},"transferCounterPosition":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction that holds the categorization which is the counter object for a transfer between accounts."},"categorizationId":{"type":"string","description":"The ID of the categorization that is the counter object for a transfer between accounts."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The counter object in a transfer between accounts. Only for categorizations of type \"transfer\"."},"internalTransitLink":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction on the same transit account that holds the linked categorization."},"categorizationId":{"type":"string","description":"The ID of the linked categorization on the same transit account."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The sibling categorization on the same transit account that closes an in-flight transfer. Only for transfer categorizations on transit accounts."},"sourceNote":{"type":"string","nullable":true,"description":"Read-only context from the source system, e.g. the invoice line-item description behind this categorization. Set by integrations; not user-editable."},"externalInvoiceUrn":{"type":"string","nullable":true,"description":"The external invoice this categorization was derived from, when the transaction was linked to one, as `{system}:invoice:{accountId}:{invoiceId}`. Set per categorization rather than per transaction because a single payment can settle several invoices."}},"required":["counterparty","categoryId","usingDateRule","computedStartDate","computedEndDate","plSplit","passThroughTax"],"additionalProperties":false},"description":"A record mapping categorization IDs to categorization details."},"inTransit":{"type":"boolean","description":"True when this transaction is part of an open in-transit transfer (booked onto a transit account but not yet matched to the arriving side). Drives the \"needs attention\" indicator alongside the open status."},"partialAmount":{"type":"number","description":"When categorization-level filters are active, the sum of amounts from matching categorizations only. Omitted when it equals the full transaction amount."}},"required":["id","companyId","accountId","date","fromTo","description","amountValue","amountCurrency","categorizationsAutoAssigned","excludeFromAutoCategorization","notes","source","assignedUserId","finApiTransactionId","plaidTransactionId","stripeBalanceTransactionId","stripeInvoiceId","stripeCreditNoteId","stripePaymentIntentId","stripeBillingEmail","hubspotPortalId","hubspotInvoiceId","hubspotCategorizationResolved","finwayExpenseNumber","finwayInvoiceNumber","pleoExpenseId","createdVendorId","integrationId","pending","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","categorizations"]}},"totalCount":{"type":"number","description":"Total number of transactions matching all filters."},"balanceByDate":{"type":"object","additionalProperties":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"description":"End-of-day account balance per visible date, in the company`s base currency. Populated only when the page rows all belong to a single account and sortBy=date."}},"required":["items","totalCount"]}}}}}}},"/companies/{companyId}/transactions/selection_summary":{"post":{"description":"Count and sum a transaction selection without fetching the rows, which is what makes \"1,284 selected, €412,900\" possible over a filter that matches more than one page. Sums are per currency and never converted, so a multi-currency selection returns several entries. `partialSums` appears only when category-level filters mean some transactions match on part of their amount, for instance one leg of a split categorization; without it, every transaction counted in full.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"selection":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["ids"]},"ids":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":10000,"description":"Explicit list of entity IDs to act on."}},"required":["mode","ids"]},{"type":"object","properties":{"mode":{"type":"string","enum":["filter"]},"filter":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["status"]},"status":{"type":"string","enum":["all","open","incompleteTransfer","inTransitStale"],"description":"\"all\" = no filter, \"open\" = uncategorized, partially categorized, or an incomplete transfer (no counter yet, or still in transit / not matched), \"incompleteTransfer\" = unfinished transfers: missing a counter (directly linked or not yet booked through transit) OR still in transit (booked onto a transit account but not yet matched to the arriving side); the real leg only, not its transit mirror, \"inTransitStale\" = the in-transit subset whose money left more than 14 days ago."}},"required":["type","status"]},{"type":"object","properties":{"type":{"type":"string","enum":["account"]},"accounts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Account IDs to include."}},"required":["type","accounts"]},{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["text"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["fromTo"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["description"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["amount"]},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no lower bound."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no upper bound."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["assignedUser"]},"userIds":{"type":"array","items":{"type":"string"},"minItems":1,"description":"User IDs to filter by. Omit to match any user."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no assigned user."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["quickSearch"]},"text":{"type":"string","description":"Searches fromTo, description, notes, and account name."},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["plType"]},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"minItems":1,"description":"P&L types to filter for."}},"required":["type","plTypes"]},{"type":"object","properties":{"type":{"type":"string","enum":["category"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to include. Omit to include all categories; pass an empty array to include none (use together with showUnassigned to match only uncategorized transactions)."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no categorizations."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["excludeCategory"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to exclude. An empty array excludes nothing."}},"required":["type","categories"]},{"type":"object","properties":{"type":{"type":"string","enum":["counterparty"]},"counterparties":{"type":"array","items":{"type":"string"},"description":"Counterparty IDs to include. Pass an empty array to include none (use together with showUnassigned to match only categorizations that have no counterparty)."},"showUnassigned":{"type":"boolean","default":false,"description":"Include categorizations with no counterparty."}},"required":["type","counterparties"]},{"type":"object","properties":{"type":{"type":"string","enum":["usingDate"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["cashFlowDirection"]},"direction":{"type":"string","enum":["inflow","outflow"],"description":"\"inflow\" = positive amounts, \"outflow\" = negative amounts."}},"required":["type","direction"]},{"type":"object","properties":{"type":{"type":"string","enum":["transactionIds"]},"ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100,"description":"Array of transaction IDs to match (max 100)."}},"required":["type","ids"]}]},"description":"Filter spec matching the list endpoint; resolves to all matching rows."},"excludedIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":10000,"default":[],"description":"IDs to exclude from the resolved filter result."}},"required":["mode","filter"]}]}},"required":["selection"]}}}},"responses":{"200":{"description":"Per-currency amount sums over the selection","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer","description":"Number of transactions in the selection."},"sums":{"type":"array","items":{"type":"object","properties":{"currency":{"type":"string","description":"ISO 4217 currency code, e.g. \"EUR\"."},"value":{"type":"number","description":"Sum of transaction amounts in this currency."}},"required":["currency","value"]},"description":"Per-currency sums of the selected transactions, sorted by currency code."},"partialSums":{"type":"array","items":{"type":"object","properties":{"currency":{"type":"string","description":"ISO 4217 currency code, e.g. \"EUR\"."},"value":{"type":"number","description":"Sum of transaction amounts in this currency."}},"required":["currency","value"]},"description":"Per-currency sums counting only the amount attributed to the matching categorizations, for selections with category-level filters where some transactions match only partially (e.g. a split categorization). Omitted when every transaction is attributed in full. Transactions without a partial attribution contribute their full amount."}},"required":["count","sums"]}}}}}}},"/companies/{companyId}/transactions/stripe_invoice_link_availability":{"get":{"description":"Whether the company has any Stripe invoice that was paid outside Stripe and is not yet linked to a transaction. Drives whether the \"Link Stripe invoice\" action is shown: a company whose customers always pay through Stripe has nothing to link.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"Availability of the invoice-link action","content":{"application/json":{"schema":{"type":"object","properties":{"available":{"type":"boolean"}},"required":["available"]}}}}}}},"/companies/{companyId}/transactions/stripe_invoice_candidates":{"get":{"description":"List Stripe invoices this payment might have settled, strongest evidence first. Only invoices that were paid outside Stripe and are not yet linked to a transaction are offered, because anything Stripe collected itself already has its revenue booked. `evidence` says why a row is being offered: `reference` means the payment text quotes that invoice number, `amountAndDate` means only the figures line up.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"transactionId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"search","in":"query"}],"responses":{"200":{"description":"Candidate invoices","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"stripeInvoiceId":{"type":"string"},"stripeAccountId":{"type":"string"},"number":{"type":"string","nullable":true},"customerName":{"type":"string","nullable":true},"amount":{"type":"number"},"currency":{"type":"string"},"invoiceDate":{"type":"string","nullable":true},"paidOn":{"type":"string","nullable":true},"servicePeriod":{"type":"object","nullable":true,"properties":{"start":{"type":"string"},"end":{"type":"string"}},"required":["start","end"]},"hostedInvoiceUrl":{"type":"string","nullable":true},"evidence":{"type":"string","enum":["reference","amountAndDate","none"]}},"required":["stripeInvoiceId","stripeAccountId","number","customerName","amount","currency","invoiceDate","paidOn","servicePeriod","hostedInvoiceUrl","evidence"]}}}}}}}},"/companies/{companyId}/transactions/link_stripe_invoices":{"post":{"description":"Book one or more Stripe invoices onto the transaction that paid them, taking the service period, tax split and customer from each invoice instead of the payment date. Pass several ids when one transfer settled several invoices. Re-linking an invoice replaces only the categorizations it produced, so anything categorized by hand is kept. If the payer settled for a different amount than was invoiced, the difference is left uncategorized and the transaction stays open, because only the customer knows what the difference was.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"transactionId":{"type":"string","minLength":1},"stripeAccountId":{"type":"string","minLength":1},"stripeInvoiceIds":{"type":"array","items":{"type":"string","minLength":1},"minItems":1}},"required":["transactionId","stripeAccountId","stripeInvoiceIds"]}}}},"responses":{"200":{"description":"Invoices linked","content":{"application/json":{"schema":{"type":"object","properties":{"createdByInvoice":{"type":"object","additionalProperties":{"type":"number"}},"uncovered":{"type":"number"},"covered":{"type":"boolean"},"skipped":{"type":"array","items":{"type":"object","properties":{"stripeInvoiceId":{"type":"string"},"reason":{"type":"string"}},"required":["stripeInvoiceId","reason"]}}},"required":["createdByInvoice","uncovered","covered","skipped"]}}}},"404":{"description":"Transaction or Stripe integration not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/transactions/{transactionId}/transfer_chain":{"get":{"description":"Resolve the full path of a linked transfer, ordered from source to destination. Money moving between two accounts is two transactions, and when the legs cannot be matched directly SaaSFlow routes them through a transit account, which puts extra hops in between. This returns every node on that path so the whole movement can be shown as one thing. `nodes` is empty when the categorization is not a linked transfer.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"transactionId","in":"path"},{"schema":{"type":"string","minLength":1,"description":"The transfer categorization whose chain to resolve."},"required":true,"description":"The transfer categorization whose chain to resolve.","name":"categorizationId","in":"query"}],"responses":{"200":{"description":"Ordered transfer chain (real legs plus any transit hops)","content":{"application/json":{"schema":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"transactionId":{"type":"string"},"categorizationId":{"type":"string"},"accountId":{"type":"string"},"accountName":{"type":"string"},"isTransit":{"type":"boolean","description":"True when the node is a transit-account mirror."},"amountValue":{"type":"string","description":"This leg's own total, which is not the same as its transaction's amount when the transaction is split. A payout of 2033.45 that carries a 30.05 fee moves 2003.40, and that is what this node reports."},"amountCurrency":{"type":"string"},"date":{"type":"string"},"isCurrent":{"type":"boolean","description":"True for the categorization the chain was requested from."}},"required":["transactionId","categorizationId","accountId","accountName","isTransit","amountValue","amountCurrency","date","isCurrent"]},"description":"The transfer chain ordered \"from → to\". Empty if the categorization is not a linked transfer."}},"required":["nodes"]}}}}}}},"/companies/{companyId}/transactions/{transactionId}":{"get":{"description":"Retrieve one transaction with everything hanging off it: its account, categorizations, comments, and attached files. This is the detail view, so it returns considerably more than the same row does in a list. Returns 404 if the transaction does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"transactionId","in":"path"}],"responses":{"200":{"description":"Transaction details with account, comments, and files","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid","description":"The ID of the account associated with this transaction."},"date":{"type":"string","description":"The date of the transaction (YYYY-MM-DD)."},"fromTo":{"type":"string","nullable":true,"description":"A description of the sender or recipient of the transaction."},"description":{"type":"string","nullable":true,"description":"A user- or bank-provided description or memo for the transaction."},"amountValue":{"type":"string","description":"The numerical value of the transaction amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the transaction amount."},"categorizationsAutoAssigned":{"type":"boolean","nullable":true,"description":"Flag indicating if the categorizations were automatically assigned."},"excludeFromAutoCategorization":{"type":"boolean","description":"Flag to exclude this transaction from automatic categorization."},"notes":{"type":"string","nullable":true,"description":"Additional notes or comments for the transaction."},"source":{"type":"string","nullable":true,"description":"The source of the transaction data (e.g., bank import, manual entry)."},"assignedUserId":{"type":"string","nullable":true,"description":"The ID of the user to whom this transaction is assigned for review."},"finApiTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from FinAPI, if applicable."},"plaidTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from Plaid, if applicable."},"stripeBalanceTransactionId":{"type":"string","nullable":true,"description":"The balance transaction ID from Stripe, if applicable."},"stripeInvoiceId":{"type":"string","nullable":true,"description":"The invoice ID from Stripe, if applicable."},"stripeCreditNoteId":{"type":"string","nullable":true,"description":"The credit note ID from Stripe, if applicable."},"stripePaymentIntentId":{"type":"string","nullable":true,"description":"The Stripe PaymentIntent ID from the charge source."},"stripeBillingEmail":{"type":"string","nullable":true,"description":"The customer billing email from the Stripe charge source."},"hubspotPortalId":{"type":"string","nullable":true,"description":"The HubSpot portal ID from Stripe balance transaction metadata."},"hubspotInvoiceId":{"type":"string","nullable":true,"description":"The HubSpot invoice ID from Stripe balance transaction metadata."},"hubspotCategorizationResolved":{"type":"boolean","nullable":true,"description":"Whether HubSpot categorization resolution has been attempted."},"finwayExpenseNumber":{"type":"string","nullable":true},"finwayInvoiceNumber":{"type":"string","nullable":true},"pleoExpenseId":{"type":"string","nullable":true},"createdVendorId":{"type":"string","nullable":true,"format":"uuid"},"integrationId":{"type":"string","nullable":true,"format":"uuid"},"pending":{"type":"boolean","description":"Flag indicating if the transaction is pending and not yet cleared."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"categorizations":{"type":"object","additionalProperties":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The ID of the category to categorize the transaction."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["custom"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["type","startDate","endDate"],"additionalProperties":false}],"description":"The date rule to apply to the transaction."},"computedStartDate":{"type":"string","format":"date","description":"The start date to apply to the transaction."},"computedEndDate":{"type":"string","format":"date","description":"The end date to apply to the transaction."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The amount of the revenue."},"cogs":{"type":"number","description":"The amount of the cost of goods sold."},"cac":{"type":"number","description":"The amount of the customer acquisition cost."},"pd":{"type":"number","description":"The amount of the product development."},"ga":{"type":"number","description":"The amount of the general and administrative."},"no":{"type":"number","description":"The amount of the non-operating expenses."},"neutral":{"type":"number","description":"The amount assigned to neutral categories."}},"additionalProperties":false,"description":"The split of the transaction amount into different Profit & Loss (P&L) types within the same category."},"passThroughTax":{"type":"number","description":"The amount of the pass-through tax (e.g. VAT) that is not important for a profit & loss calculation."},"transferCounterPosition":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction that holds the categorization which is the counter object for a transfer between accounts."},"categorizationId":{"type":"string","description":"The ID of the categorization that is the counter object for a transfer between accounts."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The counter object in a transfer between accounts. Only for categorizations of type \"transfer\"."},"internalTransitLink":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction on the same transit account that holds the linked categorization."},"categorizationId":{"type":"string","description":"The ID of the linked categorization on the same transit account."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The sibling categorization on the same transit account that closes an in-flight transfer. Only for transfer categorizations on transit accounts."},"sourceNote":{"type":"string","nullable":true,"description":"Read-only context from the source system, e.g. the invoice line-item description behind this categorization. Set by integrations; not user-editable."},"externalInvoiceUrn":{"type":"string","nullable":true,"description":"The external invoice this categorization was derived from, when the transaction was linked to one, as `{system}:invoice:{accountId}:{invoiceId}`. Set per categorization rather than per transaction because a single payment can settle several invoices."}},"required":["counterparty","categoryId","usingDateRule","computedStartDate","computedEndDate","plSplit","passThroughTax"],"additionalProperties":false},"description":"A record mapping categorization IDs to categorization details."},"account":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["bank","cash","creditCard","loan","other","transit"],"description":"The type of account: bank, cash, creditCard, loan, other, or transit (system-managed, holds the in-flight bookings of transfers between accounts)."},"importType":{"type":"string","enum":["manual","automated"]},"defaultCurrency":{"type":"string"},"bankConnection":{"type":"object","properties":{"type":{"type":"string","nullable":true},"integrationId":{"type":"string","nullable":true}},"required":["type","integrationId"]},"accountGroup":{"type":"object","nullable":true,"properties":{"name":{"type":"string"}},"required":["name"]}},"required":["id","name","type","importType","defaultCurrency","bankConnection","accountGroup"]},"comments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionId":{"type":"string","format":"uuid"},"text":{"type":"string","description":"The content of the comment."},"taggedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs mentioned in the comment."},"likedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs that liked the comment."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionId","text","taggedUserIds","likedUserIds","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}},"files":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionIds":{"type":"array","items":{"type":"string"},"description":"The IDs of transactions this file is attached to."},"filename":{"type":"string","description":"The original filename."},"fileExtension":{"type":"string","description":"The file extension (e.g., pdf, png)."},"size":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"The file size in bytes."},"storagePath":{"type":"string","description":"The storage path in cloud storage."},"parseStatus":{"type":"string","enum":["open","running","failed","succeeded"],"description":"The status of automated document parsing."},"parseData":{"type":"object","nullable":true,"properties":{"amount":{"type":"object","nullable":true,"properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"date":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"enum":["invoice","creditNote","contract","receipt","other",null]}},"required":["amount","date","type"],"description":"Parsed data extracted from the document."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionIds","filename","fileExtension","size","storagePath","parseStatus","parseData","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}},"required":["id","companyId","accountId","date","fromTo","description","amountValue","amountCurrency","categorizationsAutoAssigned","excludeFromAutoCategorization","notes","source","assignedUserId","finApiTransactionId","plaidTransactionId","stripeBalanceTransactionId","stripeInvoiceId","stripeCreditNoteId","stripePaymentIntentId","stripeBillingEmail","hubspotPortalId","hubspotInvoiceId","hubspotCategorizationResolved","finwayExpenseNumber","finwayInvoiceNumber","pleoExpenseId","createdVendorId","integrationId","pending","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","categorizations","account","comments","files"]}}}},"404":{"description":"Transaction not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a transaction, including replacing its categorizations. Only the fields you send change, but `categorizations` is replaced wholesale rather than merged, so send the complete set including the ones you are keeping. Accounts owned by an integration that does not allow manual edits reject this with 422.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"transactionId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"accountId":{"type":"string","minLength":1},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"fromTo":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"amountValue":{"type":"string"},"amountCurrency":{"type":"string"},"categorizations":{"type":"object","nullable":true,"additionalProperties":{"type":"object","properties":{"plSplit":{"type":"object","additionalProperties":{"type":"number"}},"passThroughTax":{"type":"number"}},"additionalProperties":{"nullable":true}}},"notes":{"type":"string","nullable":true},"source":{"type":"string","nullable":true},"pending":{"type":"boolean"},"assignedUserId":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Updated transaction","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid","description":"The ID of the account associated with this transaction."},"date":{"type":"string","description":"The date of the transaction (YYYY-MM-DD)."},"fromTo":{"type":"string","nullable":true,"description":"A description of the sender or recipient of the transaction."},"description":{"type":"string","nullable":true,"description":"A user- or bank-provided description or memo for the transaction."},"amountValue":{"type":"string","description":"The numerical value of the transaction amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the transaction amount."},"categorizationsAutoAssigned":{"type":"boolean","nullable":true,"description":"Flag indicating if the categorizations were automatically assigned."},"excludeFromAutoCategorization":{"type":"boolean","description":"Flag to exclude this transaction from automatic categorization."},"notes":{"type":"string","nullable":true,"description":"Additional notes or comments for the transaction."},"source":{"type":"string","nullable":true,"description":"The source of the transaction data (e.g., bank import, manual entry)."},"assignedUserId":{"type":"string","nullable":true,"description":"The ID of the user to whom this transaction is assigned for review."},"finApiTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from FinAPI, if applicable."},"plaidTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from Plaid, if applicable."},"stripeBalanceTransactionId":{"type":"string","nullable":true,"description":"The balance transaction ID from Stripe, if applicable."},"stripeInvoiceId":{"type":"string","nullable":true,"description":"The invoice ID from Stripe, if applicable."},"stripeCreditNoteId":{"type":"string","nullable":true,"description":"The credit note ID from Stripe, if applicable."},"stripePaymentIntentId":{"type":"string","nullable":true,"description":"The Stripe PaymentIntent ID from the charge source."},"stripeBillingEmail":{"type":"string","nullable":true,"description":"The customer billing email from the Stripe charge source."},"hubspotPortalId":{"type":"string","nullable":true,"description":"The HubSpot portal ID from Stripe balance transaction metadata."},"hubspotInvoiceId":{"type":"string","nullable":true,"description":"The HubSpot invoice ID from Stripe balance transaction metadata."},"hubspotCategorizationResolved":{"type":"boolean","nullable":true,"description":"Whether HubSpot categorization resolution has been attempted."},"finwayExpenseNumber":{"type":"string","nullable":true},"finwayInvoiceNumber":{"type":"string","nullable":true},"pleoExpenseId":{"type":"string","nullable":true},"createdVendorId":{"type":"string","nullable":true,"format":"uuid"},"integrationId":{"type":"string","nullable":true,"format":"uuid"},"pending":{"type":"boolean","description":"Flag indicating if the transaction is pending and not yet cleared."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"categorizations":{"type":"object","additionalProperties":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The ID of the category to categorize the transaction."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["custom"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["type","startDate","endDate"],"additionalProperties":false}],"description":"The date rule to apply to the transaction."},"computedStartDate":{"type":"string","format":"date","description":"The start date to apply to the transaction."},"computedEndDate":{"type":"string","format":"date","description":"The end date to apply to the transaction."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The amount of the revenue."},"cogs":{"type":"number","description":"The amount of the cost of goods sold."},"cac":{"type":"number","description":"The amount of the customer acquisition cost."},"pd":{"type":"number","description":"The amount of the product development."},"ga":{"type":"number","description":"The amount of the general and administrative."},"no":{"type":"number","description":"The amount of the non-operating expenses."},"neutral":{"type":"number","description":"The amount assigned to neutral categories."}},"additionalProperties":false,"description":"The split of the transaction amount into different Profit & Loss (P&L) types within the same category."},"passThroughTax":{"type":"number","description":"The amount of the pass-through tax (e.g. VAT) that is not important for a profit & loss calculation."},"transferCounterPosition":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction that holds the categorization which is the counter object for a transfer between accounts."},"categorizationId":{"type":"string","description":"The ID of the categorization that is the counter object for a transfer between accounts."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The counter object in a transfer between accounts. Only for categorizations of type \"transfer\"."},"internalTransitLink":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction on the same transit account that holds the linked categorization."},"categorizationId":{"type":"string","description":"The ID of the linked categorization on the same transit account."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The sibling categorization on the same transit account that closes an in-flight transfer. Only for transfer categorizations on transit accounts."},"sourceNote":{"type":"string","nullable":true,"description":"Read-only context from the source system, e.g. the invoice line-item description behind this categorization. Set by integrations; not user-editable."},"externalInvoiceUrn":{"type":"string","nullable":true,"description":"The external invoice this categorization was derived from, when the transaction was linked to one, as `{system}:invoice:{accountId}:{invoiceId}`. Set per categorization rather than per transaction because a single payment can settle several invoices."}},"required":["counterparty","categoryId","usingDateRule","computedStartDate","computedEndDate","plSplit","passThroughTax"],"additionalProperties":false},"description":"A record mapping categorization IDs to categorization details."}},"required":["id","companyId","accountId","date","fromTo","description","amountValue","amountCurrency","categorizationsAutoAssigned","excludeFromAutoCategorization","notes","source","assignedUserId","finApiTransactionId","plaidTransactionId","stripeBalanceTransactionId","stripeInvoiceId","stripeCreditNoteId","stripePaymentIntentId","stripeBillingEmail","hubspotPortalId","hubspotInvoiceId","hubspotCategorizationResolved","finwayExpenseNumber","finwayInvoiceNumber","pleoExpenseId","createdVendorId","integrationId","pending","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","categorizations"]}}}},"404":{"description":"Transaction not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"Account is managed by an integration without manual edits","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["integration_managed"]}},"required":["error"]}}}}}},"delete":{"description":"Delete a transaction and its categorizations, which changes the account balance from that date onward and the P&L for its period. Transactions on integration-managed accounts cannot be deleted here (422); they would reappear on the next sync anyway.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"transactionId","in":"path"}],"responses":{"200":{"description":"Transaction deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"422":{"description":"Account is managed by an integration without manual edits","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["integration_managed"]}},"required":["error"]}}}}}}},"/companies/{companyId}/transactions/batch_delete":{"post":{"description":"Delete many transactions at once. `selection` takes either `mode: \"ids\"` with an explicit list, or `mode: \"filter\"` with the same filters `POST /transactions/search` accepts plus `excludedIds` for rows the user unticked, which is how \"select all matching\" works without sending every id. Selections over 10,000 rows are refused with 413. A selection containing integration-managed transactions is refused with 422 rather than partially applied.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"selection":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["ids"]},"ids":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":10000,"description":"Explicit list of entity IDs to act on."}},"required":["mode","ids"]},{"type":"object","properties":{"mode":{"type":"string","enum":["filter"]},"filter":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["status"]},"status":{"type":"string","enum":["all","open","incompleteTransfer","inTransitStale"],"description":"\"all\" = no filter, \"open\" = uncategorized, partially categorized, or an incomplete transfer (no counter yet, or still in transit / not matched), \"incompleteTransfer\" = unfinished transfers: missing a counter (directly linked or not yet booked through transit) OR still in transit (booked onto a transit account but not yet matched to the arriving side); the real leg only, not its transit mirror, \"inTransitStale\" = the in-transit subset whose money left more than 14 days ago."}},"required":["type","status"]},{"type":"object","properties":{"type":{"type":"string","enum":["account"]},"accounts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Account IDs to include."}},"required":["type","accounts"]},{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["text"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["fromTo"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["description"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["amount"]},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no lower bound."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no upper bound."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["assignedUser"]},"userIds":{"type":"array","items":{"type":"string"},"minItems":1,"description":"User IDs to filter by. Omit to match any user."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no assigned user."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["quickSearch"]},"text":{"type":"string","description":"Searches fromTo, description, notes, and account name."},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["plType"]},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"minItems":1,"description":"P&L types to filter for."}},"required":["type","plTypes"]},{"type":"object","properties":{"type":{"type":"string","enum":["category"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to include. Omit to include all categories; pass an empty array to include none (use together with showUnassigned to match only uncategorized transactions)."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no categorizations."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["excludeCategory"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to exclude. An empty array excludes nothing."}},"required":["type","categories"]},{"type":"object","properties":{"type":{"type":"string","enum":["counterparty"]},"counterparties":{"type":"array","items":{"type":"string"},"description":"Counterparty IDs to include. Pass an empty array to include none (use together with showUnassigned to match only categorizations that have no counterparty)."},"showUnassigned":{"type":"boolean","default":false,"description":"Include categorizations with no counterparty."}},"required":["type","counterparties"]},{"type":"object","properties":{"type":{"type":"string","enum":["usingDate"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["cashFlowDirection"]},"direction":{"type":"string","enum":["inflow","outflow"],"description":"\"inflow\" = positive amounts, \"outflow\" = negative amounts."}},"required":["type","direction"]},{"type":"object","properties":{"type":{"type":"string","enum":["transactionIds"]},"ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100,"description":"Array of transaction IDs to match (max 100)."}},"required":["type","ids"]}]},"description":"Filter spec matching the list endpoint; resolves to all matching rows."},"excludedIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":10000,"default":[],"description":"IDs to exclude from the resolved filter result."}},"required":["mode","filter"]}]}},"required":["selection"]}}}},"responses":{"200":{"description":"Transactions deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"affected":{"type":"integer","minimum":0}},"required":["success","affected"]}}}},"413":{"description":"Selection exceeds bulk cap","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["bulk_too_many"]},"count":{"type":"integer","minimum":0},"max":{"type":"integer","minimum":0}},"required":["error","count","max"]}}}},"422":{"description":"Selection contains integration-managed transactions","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["integration_managed"]},"count":{"type":"integer","minimum":0}},"required":["error","count"]}}}}}}},"/companies/{companyId}/transactions/uncategorize":{"post":{"description":"Strip the categorizations from a selection of transactions, leaving the transactions themselves untouched. Use it to undo a bad bulk categorization or a wrong vendor default before re-running it. The rows go back to open and disappear from the P&L until they are categorized again. Same `selection` shape and 10,000-row cap as the other bulk endpoints.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"selection":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["ids"]},"ids":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":10000,"description":"Explicit list of entity IDs to act on."}},"required":["mode","ids"]},{"type":"object","properties":{"mode":{"type":"string","enum":["filter"]},"filter":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["status"]},"status":{"type":"string","enum":["all","open","incompleteTransfer","inTransitStale"],"description":"\"all\" = no filter, \"open\" = uncategorized, partially categorized, or an incomplete transfer (no counter yet, or still in transit / not matched), \"incompleteTransfer\" = unfinished transfers: missing a counter (directly linked or not yet booked through transit) OR still in transit (booked onto a transit account but not yet matched to the arriving side); the real leg only, not its transit mirror, \"inTransitStale\" = the in-transit subset whose money left more than 14 days ago."}},"required":["type","status"]},{"type":"object","properties":{"type":{"type":"string","enum":["account"]},"accounts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Account IDs to include."}},"required":["type","accounts"]},{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["text"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["fromTo"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["description"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["amount"]},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no lower bound."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no upper bound."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["assignedUser"]},"userIds":{"type":"array","items":{"type":"string"},"minItems":1,"description":"User IDs to filter by. Omit to match any user."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no assigned user."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["quickSearch"]},"text":{"type":"string","description":"Searches fromTo, description, notes, and account name."},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["plType"]},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"minItems":1,"description":"P&L types to filter for."}},"required":["type","plTypes"]},{"type":"object","properties":{"type":{"type":"string","enum":["category"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to include. Omit to include all categories; pass an empty array to include none (use together with showUnassigned to match only uncategorized transactions)."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no categorizations."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["excludeCategory"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to exclude. An empty array excludes nothing."}},"required":["type","categories"]},{"type":"object","properties":{"type":{"type":"string","enum":["counterparty"]},"counterparties":{"type":"array","items":{"type":"string"},"description":"Counterparty IDs to include. Pass an empty array to include none (use together with showUnassigned to match only categorizations that have no counterparty)."},"showUnassigned":{"type":"boolean","default":false,"description":"Include categorizations with no counterparty."}},"required":["type","counterparties"]},{"type":"object","properties":{"type":{"type":"string","enum":["usingDate"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["cashFlowDirection"]},"direction":{"type":"string","enum":["inflow","outflow"],"description":"\"inflow\" = positive amounts, \"outflow\" = negative amounts."}},"required":["type","direction"]},{"type":"object","properties":{"type":{"type":"string","enum":["transactionIds"]},"ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100,"description":"Array of transaction IDs to match (max 100)."}},"required":["type","ids"]}]},"description":"Filter spec matching the list endpoint; resolves to all matching rows."},"excludedIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":10000,"default":[],"description":"IDs to exclude from the resolved filter result."}},"required":["mode","filter"]}]}},"required":["selection"]}}}},"responses":{"200":{"description":"Transactions uncategorized","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"affected":{"type":"integer","minimum":0}},"required":["success","affected"]}}}},"413":{"description":"Selection exceeds bulk cap","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["bulk_too_many"]},"count":{"type":"integer","minimum":0},"max":{"type":"integer","minimum":0}},"required":["error","count","max"]}}}}}}},"/companies/{companyId}/transactions/batch_edit":{"post":{"description":"Apply one patch to a whole selection of transactions: reassign them, set notes, or push a categorization template across all of them. Fields left out of `patch` are untouched, and null clears a field. The categorization template deliberately has no transfer target, because counter transactions are booked from the vendor's stored default rather than from anything passed here. Same `selection` shape and 10,000-row cap as the other bulk endpoints.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"selection":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["ids"]},"ids":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":10000,"description":"Explicit list of entity IDs to act on."}},"required":["mode","ids"]},{"type":"object","properties":{"mode":{"type":"string","enum":["filter"]},"filter":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["status"]},"status":{"type":"string","enum":["all","open","incompleteTransfer","inTransitStale"],"description":"\"all\" = no filter, \"open\" = uncategorized, partially categorized, or an incomplete transfer (no counter yet, or still in transit / not matched), \"incompleteTransfer\" = unfinished transfers: missing a counter (directly linked or not yet booked through transit) OR still in transit (booked onto a transit account but not yet matched to the arriving side); the real leg only, not its transit mirror, \"inTransitStale\" = the in-transit subset whose money left more than 14 days ago."}},"required":["type","status"]},{"type":"object","properties":{"type":{"type":"string","enum":["account"]},"accounts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Account IDs to include."}},"required":["type","accounts"]},{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["text"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["fromTo"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["description"]},"texts":{"type":"array","items":{"type":"string"},"minItems":1,"description":"Search terms (OR logic)."}},"required":["type","texts"]},{"type":"object","properties":{"type":{"type":"string","enum":["amount"]},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no lower bound."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Omit for no upper bound."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["assignedUser"]},"userIds":{"type":"array","items":{"type":"string"},"minItems":1,"description":"User IDs to filter by. Omit to match any user."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no assigned user."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["quickSearch"]},"text":{"type":"string","description":"Searches fromTo, description, notes, and account name."},"minAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."},"maxAmount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"An amount with value and currency."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["plType"]},"plTypes":{"type":"array","items":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"]},"minItems":1,"description":"P&L types to filter for."}},"required":["type","plTypes"]},{"type":"object","properties":{"type":{"type":"string","enum":["category"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to include. Omit to include all categories; pass an empty array to include none (use together with showUnassigned to match only uncategorized transactions)."},"showUnassigned":{"type":"boolean","default":true,"description":"Include transactions with no categorizations."}},"required":["type"]},{"type":"object","properties":{"type":{"type":"string","enum":["excludeCategory"]},"categories":{"type":"array","items":{"type":"string"},"description":"Category IDs to exclude. An empty array excludes nothing."}},"required":["type","categories"]},{"type":"object","properties":{"type":{"type":"string","enum":["counterparty"]},"counterparties":{"type":"array","items":{"type":"string"},"description":"Counterparty IDs to include. Pass an empty array to include none (use together with showUnassigned to match only categorizations that have no counterparty)."},"showUnassigned":{"type":"boolean","default":false,"description":"Include categorizations with no counterparty."}},"required":["type","counterparties"]},{"type":"object","properties":{"type":{"type":"string","enum":["usingDate"]},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"YYYY-MM-DD"}},"required":["type","startDate","endDate"]},{"type":"object","properties":{"type":{"type":"string","enum":["cashFlowDirection"]},"direction":{"type":"string","enum":["inflow","outflow"],"description":"\"inflow\" = positive amounts, \"outflow\" = negative amounts."}},"required":["type","direction"]},{"type":"object","properties":{"type":{"type":"string","enum":["transactionIds"]},"ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":100,"description":"Array of transaction IDs to match (max 100)."}},"required":["type","ids"]}]},"description":"Filter spec matching the list endpoint; resolves to all matching rows."},"excludedIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":10000,"default":[],"description":"IDs to exclude from the resolved filter result."}},"required":["mode","filter"]}]},"patch":{"type":"object","properties":{"assignedUserId":{"type":"string","nullable":true,"description":"Set the assigned user for every selected transaction. Null clears the field. Omit to leave unchanged."},"notes":{"type":"string","nullable":true,"description":"Set notes on every selected transaction. Null clears the field. Omit to leave unchanged."},"categorizationTemplate":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."}},"required":["counterparty","categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Apply a categorization template to every selected transaction. The split is computed per-row from each row`s amount and date. Replaces any existing categorizations on those rows."}}}},"required":["selection","patch"]}}}},"responses":{"200":{"description":"Transactions updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"affected":{"type":"integer","minimum":0}},"required":["success","affected"]}}}},"413":{"description":"Selection exceeds bulk cap","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["bulk_too_many"]},"count":{"type":"integer","minimum":0},"max":{"type":"integer","minimum":0}},"required":["error","count","max"]}}}}}}},"/companies/{companyId}/transactions/categorization_status":{"post":{"description":"Given up to 10,000 transaction ids, return which of them are still open and the amount of each. Built for review tooling that produced suggestions a while ago and needs to know what is still worth applying before showing it. \"Open\" is broader than uncategorized: it also covers rows whose categorizations do not sum to the amount, and incomplete transfers. `amountsById` covers every id that exists in this company, so a cluster total can be computed here rather than trusted from the caller.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"transactionIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":10000}},"required":["transactionIds"]}}}},"responses":{"200":{"description":"The subset of the given transactions that are still open.","content":{"application/json":{"schema":{"type":"object","properties":{"openTransactionIds":{"type":"array","items":{"type":"string"}},"amountsById":{"type":"object","additionalProperties":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]}}},"required":["openTransactionIds","amountsById"]}}}}}}},"/companies/{companyId}/transactions/import":{"post":{"description":"Bulk-import transactions, which is the endpoint behind CSV upload and any backfill of history. Amounts follow the same convention as single creation: signed decimal strings, negative for money out. Transit accounts are system-managed and cannot be imported into (422). Only a count comes back, so keep your own idempotency key if the import may be retried.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"transactions":{"type":"array","items":{"type":"object","properties":{"accountId":{"type":"string","minLength":1},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"fromTo":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"amountValue":{"anyOf":[{"type":"string"},{"type":"number"}]},"amountCurrency":{"type":"string"}},"required":["accountId","date","fromTo","description","amountValue","amountCurrency"]}}},"required":["transactions"]}}}},"responses":{"201":{"description":"Transactions imported","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"count":{"type":"number"}},"required":["success","count"]}}}},"422":{"description":"A target account is a system-managed transit account","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/transactions/unlink_transfer":{"post":{"description":"Break a transfer link. How much comes apart depends on `link`: `path` dissolves the whole transfer and removes the transit bookings SaaSFlow created, `counter` (the default) detaches only this transaction from its transit booking, and `transit` reopens a closed path by unlinking the two transit positions. For a directly linked transfer with no transit hop, `link` is ignored. A 409 means someone else changed the link first, so reload and retry rather than forcing it.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"transactionId":{"type":"string","minLength":1},"categorizationId":{"type":"string","minLength":1},"link":{"type":"string","enum":["counter","path","transit"],"default":"counter","description":"How much of a transit-routed transfer to unlink: \"path\" dissolves the whole transfer — both account transactions are unlinked and the auto-created transit bookings are removed; \"counter\" (default) unlinks only this transaction from its transit booking; \"transit\" reopens a closed path by unlinking only the two transit positions. For directly linked (non-transit) transfers the value is ignored."}},"required":["transactionId","categorizationId"]}}}},"responses":{"200":{"description":"Transfer unlinked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"404":{"description":"Transaction not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The link changed concurrently — reload and retry","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The transfer cannot be unlinked in this state","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/transactions/create_counter_transaction":{"post":{"description":"Book the missing other half of a transfer. When money left one account and the matching arrival was never imported, for example on a cash or wallet account nobody feeds, this creates the opposing transaction on `targetAccountId` and links the two. Use `link_transfer` instead when both sides already exist. Returns 409 if the source is already linked or was changed concurrently.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"sourceTransactionId":{"type":"string","minLength":1},"sourceCategorizationId":{"type":"string","minLength":1},"targetAccountId":{"type":"string","minLength":1}},"required":["sourceTransactionId","sourceCategorizationId","targetAccountId"]}}}},"responses":{"201":{"description":"Counter transaction created","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","format":"uuid","description":"The ID of the account associated with this transaction."},"date":{"type":"string","description":"The date of the transaction (YYYY-MM-DD)."},"fromTo":{"type":"string","nullable":true,"description":"A description of the sender or recipient of the transaction."},"description":{"type":"string","nullable":true,"description":"A user- or bank-provided description or memo for the transaction."},"amountValue":{"type":"string","description":"The numerical value of the transaction amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the transaction amount."},"categorizationsAutoAssigned":{"type":"boolean","nullable":true,"description":"Flag indicating if the categorizations were automatically assigned."},"excludeFromAutoCategorization":{"type":"boolean","description":"Flag to exclude this transaction from automatic categorization."},"notes":{"type":"string","nullable":true,"description":"Additional notes or comments for the transaction."},"source":{"type":"string","nullable":true,"description":"The source of the transaction data (e.g., bank import, manual entry)."},"assignedUserId":{"type":"string","nullable":true,"description":"The ID of the user to whom this transaction is assigned for review."},"finApiTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from FinAPI, if applicable."},"plaidTransactionId":{"type":"string","nullable":true,"description":"The transaction ID from Plaid, if applicable."},"stripeBalanceTransactionId":{"type":"string","nullable":true,"description":"The balance transaction ID from Stripe, if applicable."},"stripeInvoiceId":{"type":"string","nullable":true,"description":"The invoice ID from Stripe, if applicable."},"stripeCreditNoteId":{"type":"string","nullable":true,"description":"The credit note ID from Stripe, if applicable."},"stripePaymentIntentId":{"type":"string","nullable":true,"description":"The Stripe PaymentIntent ID from the charge source."},"stripeBillingEmail":{"type":"string","nullable":true,"description":"The customer billing email from the Stripe charge source."},"hubspotPortalId":{"type":"string","nullable":true,"description":"The HubSpot portal ID from Stripe balance transaction metadata."},"hubspotInvoiceId":{"type":"string","nullable":true,"description":"The HubSpot invoice ID from Stripe balance transaction metadata."},"hubspotCategorizationResolved":{"type":"boolean","nullable":true,"description":"Whether HubSpot categorization resolution has been attempted."},"finwayExpenseNumber":{"type":"string","nullable":true},"finwayInvoiceNumber":{"type":"string","nullable":true},"pleoExpenseId":{"type":"string","nullable":true},"createdVendorId":{"type":"string","nullable":true,"format":"uuid"},"integrationId":{"type":"string","nullable":true,"format":"uuid"},"pending":{"type":"boolean","description":"Flag indicating if the transaction is pending and not yet cleared."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"categorizations":{"type":"object","additionalProperties":{"type":"object","properties":{"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string","description":"The ID of the customer or vendor."}},"required":["type","id"],"additionalProperties":false,"description":"The customer or vendor to categorize the transaction. Null if the transaction is not mapped to a customer or vendor."},"categoryId":{"type":"string","description":"The ID of the category to categorize the transaction."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["custom"]},"startDate":{"type":"string","format":"date"},"endDate":{"type":"string","format":"date"}},"required":["type","startDate","endDate"],"additionalProperties":false}],"description":"The date rule to apply to the transaction."},"computedStartDate":{"type":"string","format":"date","description":"The start date to apply to the transaction."},"computedEndDate":{"type":"string","format":"date","description":"The end date to apply to the transaction."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The amount of the revenue."},"cogs":{"type":"number","description":"The amount of the cost of goods sold."},"cac":{"type":"number","description":"The amount of the customer acquisition cost."},"pd":{"type":"number","description":"The amount of the product development."},"ga":{"type":"number","description":"The amount of the general and administrative."},"no":{"type":"number","description":"The amount of the non-operating expenses."},"neutral":{"type":"number","description":"The amount assigned to neutral categories."}},"additionalProperties":false,"description":"The split of the transaction amount into different Profit & Loss (P&L) types within the same category."},"passThroughTax":{"type":"number","description":"The amount of the pass-through tax (e.g. VAT) that is not important for a profit & loss calculation."},"transferCounterPosition":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction that holds the categorization which is the counter object for a transfer between accounts."},"categorizationId":{"type":"string","description":"The ID of the categorization that is the counter object for a transfer between accounts."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The counter object in a transfer between accounts. Only for categorizations of type \"transfer\"."},"internalTransitLink":{"type":"object","nullable":true,"properties":{"transactionId":{"type":"string","description":"The ID of the transaction on the same transit account that holds the linked categorization."},"categorizationId":{"type":"string","description":"The ID of the linked categorization on the same transit account."}},"required":["transactionId","categorizationId"],"additionalProperties":false,"description":"The sibling categorization on the same transit account that closes an in-flight transfer. Only for transfer categorizations on transit accounts."},"sourceNote":{"type":"string","nullable":true,"description":"Read-only context from the source system, e.g. the invoice line-item description behind this categorization. Set by integrations; not user-editable."},"externalInvoiceUrn":{"type":"string","nullable":true,"description":"The external invoice this categorization was derived from, when the transaction was linked to one, as `{system}:invoice:{accountId}:{invoiceId}`. Set per categorization rather than per transaction because a single payment can settle several invoices."}},"required":["counterparty","categoryId","usingDateRule","computedStartDate","computedEndDate","plSplit","passThroughTax"],"additionalProperties":false},"description":"A record mapping categorization IDs to categorization details."}},"required":["id","companyId","accountId","date","fromTo","description","amountValue","amountCurrency","categorizationsAutoAssigned","excludeFromAutoCategorization","notes","source","assignedUserId","finApiTransactionId","plaidTransactionId","stripeBalanceTransactionId","stripeInvoiceId","stripeCreditNoteId","stripePaymentIntentId","stripeBillingEmail","hubspotPortalId","hubspotInvoiceId","hubspotCategorizationResolved","finwayExpenseNumber","finwayInvoiceNumber","pleoExpenseId","createdVendorId","integrationId","pending","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","categorizations"]}}}},"404":{"description":"Source transaction not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The source transaction is already linked or changed concurrently","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The counter transaction cannot be booked through transit","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/transactions/link_transfer":{"post":{"description":"Link two existing transactions as the two legs of one transfer, so the movement stops counting as income on one side and spend on the other. Pass the source transaction with the categorization that represents the transfer, plus the target transaction. Legs that cannot be matched directly, across currencies or dates, are routed through a transit account, which is why the resulting chain can have more than two nodes. Returns 409 if either side is already linked or changed concurrently, and 422 when the two cannot form a transfer at all.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"sourceTransactionId":{"type":"string","minLength":1},"sourceCategorizationId":{"type":"string","minLength":1},"targetTransactionId":{"type":"string","minLength":1}},"required":["sourceTransactionId","sourceCategorizationId","targetTransactionId"]}}}},"responses":{"200":{"description":"Transfer linked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"404":{"description":"Transaction not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"One side is already linked or changed concurrently","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"422":{"description":"The two transactions cannot be linked as a transfer in this state","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/integrations":{"get":{"description":"List a company's connected integrations: bank connections, payment and billing providers, and accounting tools. Each entry carries the provider, its connection state, and the sync metadata the app shows on the integrations screen.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of integrations","content":{"application/json":{"schema":{"type":"array","items":{"oneOf":[{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["stripe"],"description":"The type of integration."},"stripeMode":{"type":"string","enum":["test","live"],"description":"The mode of the Stripe integration (test or live)."},"lastAccountSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful account synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSubscriptionSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful subscription synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"missingPermissionObjectTypes":{"type":"array","items":{"type":"string"},"description":"Stripe object types (e.g. \"charges\") the connection is currently not permitted to read, typically because the account authorized the app before that permission was added. Reconnecting the integration grants the current permission set."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastAccountSyncTime","lastSubscriptionSyncTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["slack"],"description":"The type of integration."},"slackChannel":{"type":"string","description":"The Slack channel name."},"slackChannelId":{"type":"string","description":"The Slack channel ID."},"slackTeamId":{"type":"string","description":"The Slack team ID."},"slackTeamName":{"type":"string","description":"The Slack team name."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","slackChannel","slackChannelId","slackTeamId","slackTeamName","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["hubspot"],"description":"The type of integration."},"hubspotPortalId":{"type":"string","description":"The HubSpot portal (account) ID."},"lastSubscriptionSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful subscription synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","hubspotPortalId","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["datevPayroll"],"description":"The type of integration."},"lastUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful CSV upload. Null if never uploaded. Format: YYYY-MM-DDTHH:MM:SSZ"},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastUploadTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["finway"],"description":"The type of integration."},"lastWalletUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful wallet statement upload. Null if never."},"lastAntragUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful Antrag ('Alle Formate' zip) upload. Null if never."},"costCategoryMap":{"type":"object","additionalProperties":{"type":"object","properties":{"template":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Categorization template applied to every wallet payment line under this cost center. Null means skipped. `passThroughTax` and `usingDateRule` are placeholders for Finway — real tax / dates come from the Antrag line."},"label":{"type":"string","description":"Human-readable Kostenstelle label as last seen in Finway (e.g. 'Materialkosten/ Produktion'). Refreshed on every Antrag upload."},"mappedTime":{"type":"string","format":"date-time","description":"ISO datetime the mapping was created."},"mappedByUserId":{"type":"string","description":"Firebase UID that created the mapping."}},"required":["template","label","mappedTime","mappedByUserId"]},"default":{},"description":"Map keyed by Finway CostCenter1ID (e.g. '30') to the chosen SaaSFlow category. Always present in Antrag exports, stable across Finway tenants — preferred over Sachkonto string keying."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastWalletUploadTime","lastAntragUploadTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["pleo"],"description":"The type of integration."},"lastExportUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful Pleo export upload. Null if never."},"lastReimbursementsUploadTime":{"type":"string","nullable":true,"format":"date-time","default":null,"description":"ISO datetime of the last successful Pleo reimbursements (Rückerstattungen) upload. Null if never."},"plMappingField":{"type":"object","nullable":true,"properties":{"keyColumn":{"type":"string","description":"CSV column header whose value uniquely identifies the categorization bucket (e.g. 'Chart of Accounts - Code')."},"labelColumn":{"type":"string","nullable":true,"description":"Optional CSV column header used as the human-readable label for picker rows. Null falls back to using the keyColumn value as both key and label."}},"required":["keyColumn","labelColumn"],"description":"CSV column pair used to derive the categorization key + label. Null until the first upload completes the column-picker step."},"mappingValueMap":{"type":"object","additionalProperties":{"type":"object","properties":{"template":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Categorization template applied to every Pleo wallet row whose mapping field equals this entry key. Null means skipped. `passThroughTax` and `usingDateRule` are placeholders — real values come from the CSV row at apply time."},"label":{"type":"string","description":"Human-readable label for the mapping value (typically the matching \"Name\" column, e.g. \"266 Marketing Magazine FR\" when the key is \"266\"). Refreshed on every Pleo upload."},"mappedTime":{"type":"string","format":"date-time","description":"ISO datetime the mapping was created."},"mappedByUserId":{"type":"string","description":"Firebase UID that created the mapping."}},"required":["template","label","mappedTime","mappedByUserId"]},"default":{},"description":"Map keyed by the chosen CSV column's value (e.g. '266') to the user-picked SaaSFlow category template. Re-used on every subsequent upload — only new mapping values are surfaced to the picker."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastExportUploadTime","plMappingField","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["salesforce"],"description":"The type of integration."},"salesforceOrgId":{"type":"string","description":"The Salesforce org ID (15- or 18-char, starts with 00D)."},"salesforceInstanceUrl":{"type":"string","description":"The per-org Salesforce REST API host returned by OAuth (e.g. https://acme.my.salesforce.com). Required for every API call to this org."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","salesforceOrgId","salesforceInstanceUrl","id"],"additionalProperties":false}]}}}}}}}},"/companies/{companyId}/integrations/{integrationId}":{"get":{"description":"Retrieve one integration by id, including its connection state and last sync. Use this to diagnose a bank connection that has stopped importing. Returns 404 if the integration does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"integrationId","in":"path"}],"responses":{"200":{"description":"Integration details","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["stripe"],"description":"The type of integration."},"stripeMode":{"type":"string","enum":["test","live"],"description":"The mode of the Stripe integration (test or live)."},"lastAccountSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful account synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"lastSubscriptionSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful subscription synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"missingPermissionObjectTypes":{"type":"array","items":{"type":"string"},"description":"Stripe object types (e.g. \"charges\") the connection is currently not permitted to read, typically because the account authorized the app before that permission was added. Reconnecting the integration grants the current permission set."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastAccountSyncTime","lastSubscriptionSyncTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["slack"],"description":"The type of integration."},"slackChannel":{"type":"string","description":"The Slack channel name."},"slackChannelId":{"type":"string","description":"The Slack channel ID."},"slackTeamId":{"type":"string","description":"The Slack team ID."},"slackTeamName":{"type":"string","description":"The Slack team name."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","slackChannel","slackChannelId","slackTeamId","slackTeamName","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["hubspot"],"description":"The type of integration."},"hubspotPortalId":{"type":"string","description":"The HubSpot portal (account) ID."},"lastSubscriptionSyncTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful subscription synchronization. Null if never synced. Format: YYYY-MM-DDTHH:MM:SSZ"},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","hubspotPortalId","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["datevPayroll"],"description":"The type of integration."},"lastUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"The ISO datetime of the last successful CSV upload. Null if never uploaded. Format: YYYY-MM-DDTHH:MM:SSZ"},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastUploadTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["finway"],"description":"The type of integration."},"lastWalletUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful wallet statement upload. Null if never."},"lastAntragUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful Antrag ('Alle Formate' zip) upload. Null if never."},"costCategoryMap":{"type":"object","additionalProperties":{"type":"object","properties":{"template":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Categorization template applied to every wallet payment line under this cost center. Null means skipped. `passThroughTax` and `usingDateRule` are placeholders for Finway — real tax / dates come from the Antrag line."},"label":{"type":"string","description":"Human-readable Kostenstelle label as last seen in Finway (e.g. 'Materialkosten/ Produktion'). Refreshed on every Antrag upload."},"mappedTime":{"type":"string","format":"date-time","description":"ISO datetime the mapping was created."},"mappedByUserId":{"type":"string","description":"Firebase UID that created the mapping."}},"required":["template","label","mappedTime","mappedByUserId"]},"default":{},"description":"Map keyed by Finway CostCenter1ID (e.g. '30') to the chosen SaaSFlow category. Always present in Antrag exports, stable across Finway tenants — preferred over Sachkonto string keying."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastWalletUploadTime","lastAntragUploadTime","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["pleo"],"description":"The type of integration."},"lastExportUploadTime":{"type":"string","nullable":true,"format":"date-time","description":"ISO datetime of the last successful Pleo export upload. Null if never."},"lastReimbursementsUploadTime":{"type":"string","nullable":true,"format":"date-time","default":null,"description":"ISO datetime of the last successful Pleo reimbursements (Rückerstattungen) upload. Null if never."},"plMappingField":{"type":"object","nullable":true,"properties":{"keyColumn":{"type":"string","description":"CSV column header whose value uniquely identifies the categorization bucket (e.g. 'Chart of Accounts - Code')."},"labelColumn":{"type":"string","nullable":true,"description":"Optional CSV column header used as the human-readable label for picker rows. Null falls back to using the keyColumn value as both key and label."}},"required":["keyColumn","labelColumn"],"description":"CSV column pair used to derive the categorization key + label. Null until the first upload completes the column-picker step."},"mappingValueMap":{"type":"object","additionalProperties":{"type":"object","properties":{"template":{"type":"object","nullable":true,"properties":{"categoryId":{"type":"string","description":"The default category to assign to transactions from this counterparty."},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"The default date rule to apply to transactions from this counterparty."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number","description":"The share of the transaction amount that is considered revenue in percent."},"cogs":{"type":"number","description":"The share of the transaction amount that is considered cost of goods sold in percent."},"cac":{"type":"number","description":"The share of the transaction amount that is considered customer acquisition cost in percent."},"pd":{"type":"number","description":"The share of the transaction amount that is considered product development in percent."},"ga":{"type":"number","description":"The share of the transaction amount that is considered general and administrative in percent."},"no":{"type":"number","description":"The share of the transaction amount that is considered non-operating expenses in percent."},"neutral":{"type":"number","description":"The share of the transaction amount that is considered neutral in percent."}},"additionalProperties":false,"description":"The default split of the transaction amount into different Profit & Loss (P&L) types. The sum of the percentages needs to be 100%."},"passThroughTax":{"type":"number","description":"The pass-through tax (e.g. VAT) that comes on top of the amount covered by the P&L split."},"transferTargetAccountId":{"type":"string","nullable":true,"description":"Only valid when the default category is the system transfer category. The account that receives the counter transaction so each applied transfer is booked closed instead of left as an open leg. Must be an active, non-transit account of the same company that accepts manual transactions. Omit or null to leave transfers open."}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"],"additionalProperties":false,"description":"Categorization template applied to every Pleo wallet row whose mapping field equals this entry key. Null means skipped. `passThroughTax` and `usingDateRule` are placeholders — real values come from the CSV row at apply time."},"label":{"type":"string","description":"Human-readable label for the mapping value (typically the matching \"Name\" column, e.g. \"266 Marketing Magazine FR\" when the key is \"266\"). Refreshed on every Pleo upload."},"mappedTime":{"type":"string","format":"date-time","description":"ISO datetime the mapping was created."},"mappedByUserId":{"type":"string","description":"Firebase UID that created the mapping."}},"required":["template","label","mappedTime","mappedByUserId"]},"default":{},"description":"Map keyed by the chosen CSV column's value (e.g. '266') to the user-picked SaaSFlow category template. Re-used on every subsequent upload — only new mapping values are surfaced to the picker."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","lastExportUploadTime","plMappingField","id"],"additionalProperties":false},{"type":"object","properties":{"name":{"type":"string","description":"The name of the integration (usually the company name from the provider)."},"externalAccountId":{"type":"string","description":"The external account ID for the integration (usually the account ID from the provider)."},"status":{"type":"string","enum":["active","error"],"default":"active","description":"The current status of the integration."},"initialSetupComplete":{"type":"boolean","default":true,"description":"Whether the initial setup (full sync + first data refresh) has completed. False during initial setup, true once ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was created."},"createdUserId":{"type":"string","nullable":true,"description":"The ID of the user who created the entity. Null if the entity was created by the system."},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time","description":"When the entity was last updated."},"lastUpdatedUserId":{"type":"string","nullable":true,"description":"The ID of the user who last updated the entity. Null if the entity was updated by the system."},"type":{"type":"string","enum":["salesforce"],"description":"The type of integration."},"salesforceOrgId":{"type":"string","description":"The Salesforce org ID (15- or 18-char, starts with 00D)."},"salesforceInstanceUrl":{"type":"string","description":"The per-org Salesforce REST API host returned by OAuth (e.g. https://acme.my.salesforce.com). Required for every API call to this org."},"id":{"type":"string"}},"required":["name","externalAccountId","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","type","salesforceOrgId","salesforceInstanceUrl","id"],"additionalProperties":false}]}}}},"404":{"description":"Integration not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Disconnect an integration. By default (`convertToManual=true`) the accounts it managed are kept along with their transaction history and switched to manual, so nothing disappears from the books. Pass `convertToManual=false` only when the accounts and their data should go too.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"integrationId","in":"path"},{"schema":{"type":"string","enum":["true","false"],"default":"true"},"required":false,"name":"convertToManual","in":"query"}],"responses":{"200":{"description":"Integration deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/comments":{"get":{"description":"List comments. Comments are always attached to a transaction, which is how teams discuss what a payment was for. Pass `transactionId` to get the thread on one transaction; omit it to get every comment in the company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"transactionId","in":"query"}],"responses":{"200":{"description":"List of comments","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionId":{"type":"string","format":"uuid"},"text":{"type":"string","description":"The content of the comment."},"taggedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs mentioned in the comment."},"likedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs that liked the comment."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionId","text","taggedUserIds","likedUserIds","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"post":{"description":"Post a comment on a transaction. `transactionId` is required and cannot be changed later. `taggedUserIds` records which teammates are mentioned, which is how the app renders @-mentions in the thread.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"transactionId":{"type":"string","minLength":1},"text":{"type":"string","minLength":1},"taggedUserIds":{"type":"array","items":{"type":"string"}}},"required":["transactionId","text"]}}}},"responses":{"201":{"description":"Created comment","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionId":{"type":"string","format":"uuid"},"text":{"type":"string","description":"The content of the comment."},"taggedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs mentioned in the comment."},"likedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs that liked the comment."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionId","text","taggedUserIds","likedUserIds","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"/companies/{companyId}/comments/{commentId}":{"get":{"description":"Retrieve a single comment by id, including who is tagged in it and who liked it. Returns 404 if the comment does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"commentId","in":"path"}],"responses":{"200":{"description":"Comment details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionId":{"type":"string","format":"uuid"},"text":{"type":"string","description":"The content of the comment."},"taggedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs mentioned in the comment."},"likedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs that liked the comment."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionId","text","taggedUserIds","likedUserIds","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Comment not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Edit a comment's text or its tagged users. Only the fields you send change. The comment stays on the transaction it was posted to.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"commentId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","minLength":1},"taggedUserIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Updated comment","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionId":{"type":"string","format":"uuid"},"text":{"type":"string","description":"The content of the comment."},"taggedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs mentioned in the comment."},"likedUserIds":{"type":"array","items":{"type":"string"},"description":"User IDs that liked the comment."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionId","text","taggedUserIds","likedUserIds","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Comment not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a comment. The transaction it was attached to is untouched. Returns success even when there was nothing to delete.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"commentId","in":"path"}],"responses":{"200":{"description":"Comment deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/comments/{commentId}/like":{"put":{"description":"Add the calling user to a comment's likes. Idempotent: liking twice does not duplicate the user or inflate the count.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"commentId","in":"path"}],"responses":{"200":{"description":"Comment liked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/comments/{commentId}/unlike":{"put":{"description":"Remove the calling user from a comment's likes. Idempotent: unliking a comment the user never liked is a no-op that still returns success.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"commentId","in":"path"}],"responses":{"200":{"description":"Comment unliked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/notifications":{"get":{"description":"List a company's notifications, newest first, paginated. Notifications are what the app shows in its bell menu: bank connection failures, subscription changes, balance mismatches, and similar events. With no `recipientUserId` the result spans every recipient in the company. Passing another user's id requires `users:manage`; without it the response is an empty page rather than a 403. Filter with `types`, and narrow subscription-change rows further with `eventType`. `totalCount` is the unpaginated total for the active filter. `earliestCreatedTime` deliberately ignores the filter so a date picker can anchor to the real start of history, and it is only computed on the first page (`offset=0`), returning null on later pages.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"recipientUserId","in":"query"},{"schema":{"type":"string","description":"Comma-separated notification types to return, e.g. \"bankConnectionError,subscriptionChange\". Omit to return all types."},"required":false,"description":"Comma-separated notification types to return, e.g. \"bankConnectionError,subscriptionChange\". Omit to return all types.","name":"types","in":"query"},{"schema":{"type":"string","description":"For subscriptionChange notifications, return only this MRR event type: new, upgrade, downgrade, cancel, or reactivate."},"required":false,"description":"For subscriptionChange notifications, return only this MRR event type: new, upgrade, downgrade, cancel, or reactivate.","name":"eventType","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Return notifications created on or after this date (YYYY-MM-DD)."},"required":false,"description":"Return notifications created on or after this date (YYYY-MM-DD).","name":"createdAfter","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Return notifications created on or before this date (YYYY-MM-DD)."},"required":false,"description":"Return notifications created on or before this date (YYYY-MM-DD).","name":"createdBefore","in":"query"},{"schema":{"type":"string"},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string"},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"Paginated list of notifications","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","description":"The type of notification."},"recipientUserId":{"type":"string","description":"The ID of the user who should receive this notification."},"readTime":{"type":"string","nullable":true,"format":"date-time","description":"When the notification was read. Null if unread."},"payload":{"type":"object","additionalProperties":{"nullable":true},"description":"Type-specific notification data."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","recipientUserId","readTime","payload","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}},"totalCount":{"type":"number"},"entities":{"type":"object","properties":{"customers":{"type":"object","additionalProperties":{"type":"object","properties":{"name":{"type":"string","description":"Display name of the referenced entity."},"domains":{"type":"array","nullable":true,"items":{"type":"string"},"description":"Known domains, used to resolve a logo."},"logoUrl":{"type":"string","nullable":true,"description":"Resolved logo URL, or null when there is none."}},"required":["name","domains","logoUrl"]},"description":"Customer avatars keyed by customer id, for the rows on this page."}},"required":["customers"],"description":"Avatar sidecar for the rows on this page. Notification rows carry ids, not counterparty data, so join against this map client-side rather than fetching each customer."},"earliestCreatedTime":{"type":"string","nullable":true,"description":"Date of the earliest notification (YYYY-MM-DD) for this company and recipient, ignoring the active filter; null if there are none. Used to anchor a date picker."}},"required":["items","totalCount","entities","earliestCreatedTime"]}}}}}},"post":{"description":"Create a notification and return the created row. Most notifications are raised by SaaSFlow itself; use this when your own workflow needs to put something in a user's bell menu. `recipientUserId` is required, and addressing anyone other than the calling user needs cross-user rights, otherwise the request is refused with 403.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","minLength":1},"recipientUserId":{"type":"string","minLength":1},"payload":{"type":"object","additionalProperties":{"nullable":true}}},"required":["type","recipientUserId"]}}}},"responses":{"201":{"description":"Created notification","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","description":"The type of notification."},"recipientUserId":{"type":"string","description":"The ID of the user who should receive this notification."},"readTime":{"type":"string","nullable":true,"format":"date-time","description":"When the notification was read. Null if unread."},"payload":{"type":"object","additionalProperties":{"nullable":true},"description":"Type-specific notification data."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","recipientUserId","readTime","payload","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"403":{"description":"Not allowed to create a notification for another user","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/notifications/batch_delete":{"post":{"description":"Delete several notifications in one call by passing their ids. Without cross-user rights a caller only deletes notifications addressed to themselves; ids outside that scope, or belonging to another company, are skipped instead of failing the batch, so a success response does not guarantee every id was removed. Deletion is permanent. To clear the unread badge while keeping the history, use `read-all`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"notificationIds":{"type":"array","items":{"type":"string","minLength":1}}},"required":["notificationIds"]}}}},"responses":{"200":{"description":"Notifications deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/notifications/{notificationId}/read":{"put":{"description":"Mark one notification as read and return the updated row. Returns 404 if the notification does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"notificationId","in":"path"}],"responses":{"200":{"description":"Notification marked as read","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","description":"The type of notification."},"recipientUserId":{"type":"string","description":"The ID of the user who should receive this notification."},"readTime":{"type":"string","nullable":true,"format":"date-time","description":"When the notification was read. Null if unread."},"payload":{"type":"object","additionalProperties":{"nullable":true},"description":"Type-specific notification data."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","recipientUserId","readTime","payload","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Notification not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/notifications/read-all":{"put":{"description":"Mark every unread notification for the calling user in this company as read, and return how many rows changed. Other users' notifications are untouched.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"All unread notifications for the current user marked as read","content":{"application/json":{"schema":{"type":"object","properties":{"updatedCount":{"type":"number"}},"required":["updatedCount"]}}}}}}},"/companies/{companyId}/notifications/{notificationId}/unread":{"put":{"description":"Mark one notification as unread again, putting it back in the bell menu. Returns 404 if the notification does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"notificationId","in":"path"}],"responses":{"200":{"description":"Notification marked as unread","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","description":"The type of notification."},"recipientUserId":{"type":"string","description":"The ID of the user who should receive this notification."},"readTime":{"type":"string","nullable":true,"format":"date-time","description":"When the notification was read. Null if unread."},"payload":{"type":"object","additionalProperties":{"nullable":true},"description":"Type-specific notification data."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","type","recipientUserId","readTime","payload","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Notification not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/files":{"get":{"description":"List uploaded files, newest first, paginated. Files are the receipts and invoices attached to transactions as proof for the books. Each row carries `transactionIds` for what it is attached to, plus `parseStatus` and `parseData` holding the amount, date, and document type read out of the document. Pass `unassignedOnly=true` to get the ones not yet attached to anything, which is the pile a bookkeeper works through.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","enum":["true","false"]},"required":false,"name":"unassignedOnly","in":"query"},{"schema":{"type":"string"},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string"},"required":false,"name":"offset","in":"query"}],"responses":{"200":{"description":"Paginated list of files","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionIds":{"type":"array","items":{"type":"string"},"description":"The IDs of transactions this file is attached to."},"filename":{"type":"string","description":"The original filename."},"fileExtension":{"type":"string","description":"The file extension (e.g., pdf, png)."},"size":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"The file size in bytes."},"storagePath":{"type":"string","description":"The storage path in cloud storage."},"parseStatus":{"type":"string","enum":["open","running","failed","succeeded"],"description":"The status of automated document parsing."},"parseData":{"type":"object","nullable":true,"properties":{"amount":{"type":"object","nullable":true,"properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"date":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"enum":["invoice","creditNote","contract","receipt","other",null]}},"required":["amount","date","type"],"description":"Parsed data extracted from the document."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionIds","filename","fileExtension","size","storagePath","parseStatus","parseData","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}},"totalCount":{"type":"number"}},"required":["items","totalCount"]}}}}}}},"/companies/{companyId}/files/{fileId}":{"get":{"description":"Retrieve one file's metadata: filename, size, what it is attached to, and the parsed amount and date. This returns the record, not the document. Use the `/download` sub-resource for the bytes. Returns 404 if the file does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"fileId","in":"path"}],"responses":{"200":{"description":"File details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionIds":{"type":"array","items":{"type":"string"},"description":"The IDs of transactions this file is attached to."},"filename":{"type":"string","description":"The original filename."},"fileExtension":{"type":"string","description":"The file extension (e.g., pdf, png)."},"size":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"The file size in bytes."},"storagePath":{"type":"string","description":"The storage path in cloud storage."},"parseStatus":{"type":"string","enum":["open","running","failed","succeeded"],"description":"The status of automated document parsing."},"parseData":{"type":"object","nullable":true,"properties":{"amount":{"type":"object","nullable":true,"properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"date":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"enum":["invoice","creditNote","contract","receipt","other",null]}},"required":["amount","date","type"],"description":"Parsed data extracted from the document."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionIds","filename","fileExtension","size","storagePath","parseStatus","parseData","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a file record: rename it, correct the parsed amount, date, or document type, or change which transactions it is attached to. `transactionIds` replaces the attachment list rather than adding to it, so include the ids you want to keep. This is how a receipt gets matched to the payment it belongs to.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"fileId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"transactionIds":{"type":"array","items":{"type":"string"}},"filename":{"type":"string"},"parseData":{"type":"object","nullable":true,"properties":{"amount":{"type":"object","nullable":true,"properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"type":{"type":"string","nullable":true},"date":{"type":"string","nullable":true}}}}}}}},"responses":{"200":{"description":"Updated file","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"transactionIds":{"type":"array","items":{"type":"string"},"description":"The IDs of transactions this file is attached to."},"filename":{"type":"string","description":"The original filename."},"fileExtension":{"type":"string","description":"The file extension (e.g., pdf, png)."},"size":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"The file size in bytes."},"storagePath":{"type":"string","description":"The storage path in cloud storage."},"parseStatus":{"type":"string","enum":["open","running","failed","succeeded"],"description":"The status of automated document parsing."},"parseData":{"type":"object","nullable":true,"properties":{"amount":{"type":"object","nullable":true,"properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"date":{"type":"string","nullable":true},"type":{"type":"string","nullable":true,"enum":["invoice","creditNote","contract","receipt","other",null]}},"required":["amount","date","type"],"description":"Parsed data extracted from the document."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","transactionIds","filename","fileExtension","size","storagePath","parseStatus","parseData","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a file and its stored document. The transactions it was attached to are untouched, but they lose their supporting receipt, which matters if the books are audited. To detach without destroying the document, clear `transactionIds` with `PUT` instead.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"fileId","in":"path"}],"responses":{"200":{"description":"File deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/files/batch_delete":{"post":{"description":"Delete many files at once. `selection` takes either `mode: \"ids\"` with an explicit list, or `mode: \"filter\"` with the same filter the list endpoint takes plus `excludedIds` for the rows the user unticked, which is what \"select all matching\" sends. Selections resolving to more than 10,000 files are refused with 413 and a body giving the count and the cap. Stored documents go too, so this is not reversible.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"selection":{"oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["ids"]},"ids":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":10000,"description":"Explicit list of entity IDs to act on."}},"required":["mode","ids"]},{"type":"object","properties":{"mode":{"type":"string","enum":["filter"]},"filter":{"type":"object","properties":{"unassignedOnly":{"type":"boolean"}},"description":"Filter spec matching the list endpoint; resolves to all matching rows."},"excludedIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":10000,"default":[],"description":"IDs to exclude from the resolved filter result."}},"required":["mode","filter"]}]}},"required":["selection"]}}}},"responses":{"200":{"description":"Files deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"affected":{"type":"integer","minimum":0}},"required":["success","affected"]}}}},"413":{"description":"Selection exceeds bulk cap","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","enum":["bulk_too_many"]},"count":{"type":"integer","minimum":0},"max":{"type":"integer","minimum":0}},"required":["error","count","max"]}}}}}}},"/companies/{companyId}/files/upload":{"post":{"description":"Upload a receipt or invoice. The document goes in `fileBase64` as base64, capped at 10 MB by `size`. Pass `transactionIds` to attach it on upload, or leave it out and match it later. PDFs and PNGs are queued for automatic parsing of amount, date, and document type; other extensions are stored as-is and never get `parseData`. Parsing runs in the background, so the file comes back with `parseStatus: \"open\"` and fills in later. Only the new `fileId` is returned, so read the file back for the rest.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"fileBase64":{"type":"string"},"fileName":{"type":"string","minLength":1},"size":{"type":"number","maximum":10485760},"contentType":{"type":"string"},"transactionIds":{"type":"array","items":{"type":"string"}}},"required":["fileBase64","fileName","size","contentType"]}}}},"responses":{"201":{"description":"File uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"fileId":{"type":"string"}},"required":["fileId"]}}}}}}},"/companies/{companyId}/files/{fileId}/download":{"get":{"description":"Get a signed URL for downloading the stored document. The URL expires after 15 minutes and carries its own access, so treat it as a secret and fetch it when the user is ready to download rather than embedding it in a page that gets cached. Returns 404 if the file does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"fileId","in":"path"}],"responses":{"200":{"description":"Signed download URL (15 min expiry)","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]}}}},"404":{"description":"File not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/background_tasks":{"get":{"description":"List background tasks for a company. Long-running work such as bank syncs, imports, and bulk edits runs asynchronously and reports progress here. Poll this after kicking off an operation that returns a task id, and read `status` to tell running from finished and failed. Tasks with `visible: false` are high-frequency webhook refreshes that the app hides from its pending indicator; skip them if you are mirroring that behaviour. Type-specific counters (processed, total, new transactions) live in `data`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of background tasks","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["resolveTransfers","autoCategorizeTransactions","finapiRefreshAccountTransactions","plaidRefreshAccountTransactions","stripeRefreshAccountTransactions","stripeRefreshSubscriptions","stripeFullSync","stripeDeltaSync","hubspotRefreshSubscriptions","resolveHubSpotCategorizations","stripeResolveInvoiceMatches"],"description":"The type of background task."},"status":{"type":"string","enum":["open","running","failed","succeeded"],"description":"The current status of the task."},"createdTime":{"type":"string","format":"date-time"},"finishedTime":{"type":"string","nullable":true,"format":"date-time"},"error":{"type":"string","nullable":true,"description":"Error message if the task failed."},"errorDetails":{"type":"string","nullable":true,"description":"Detailed error information if the task failed."},"accountId":{"type":"string","nullable":true},"integrationId":{"type":"string","nullable":true},"visible":{"type":"boolean"},"data":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"nullable":true},{"type":"object","additionalProperties":{"nullable":true}},{"type":"array","items":{"nullable":true}}],"description":"Type-specific payload (e.g. newTransactionCount, processedCount, totalCount)."}},"required":["id","companyId","type","status","createdTime","finishedTime","error","errorDetails","accountId","integrationId","visible","data"]}}}}}}}},"/companies/{companyId}/background_tasks/{id}":{"get":{"description":"Retrieve one background task by id to check whether it is still running, what it produced, or why it failed. Returns 404 if the task does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Background task","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["resolveTransfers","autoCategorizeTransactions","finapiRefreshAccountTransactions","plaidRefreshAccountTransactions","stripeRefreshAccountTransactions","stripeRefreshSubscriptions","stripeFullSync","stripeDeltaSync","hubspotRefreshSubscriptions","resolveHubSpotCategorizations","stripeResolveInvoiceMatches"],"description":"The type of background task."},"status":{"type":"string","enum":["open","running","failed","succeeded"],"description":"The current status of the task."},"createdTime":{"type":"string","format":"date-time"},"finishedTime":{"type":"string","nullable":true,"format":"date-time"},"error":{"type":"string","nullable":true,"description":"Error message if the task failed."},"errorDetails":{"type":"string","nullable":true,"description":"Detailed error information if the task failed."},"accountId":{"type":"string","nullable":true},"integrationId":{"type":"string","nullable":true},"visible":{"type":"boolean"},"data":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"nullable":true},{"type":"object","additionalProperties":{"nullable":true}},{"type":"array","items":{"nullable":true}}],"description":"Type-specific payload (e.g. newTransactionCount, processedCount, totalCount)."}},"required":["id","companyId","type","status","createdTime","finishedTime","error","errorDetails","accountId","integrationId","visible","data"]}}}},"404":{"description":"Background task not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/agent_definitions":{"get":{"description":"List the agents that can be run for this company, each with a title and a description of what it does. The `id` here is the `definitionId` you pass when starting a run. Read this first rather than hard-coding an id, since the catalogue changes as agents are added.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"Agent definitions that can be run.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"}},"required":["id","title","description"]}}}}}}}},"/companies/{companyId}/agent_runs":{"get":{"description":"List this company's agent runs, past and in progress, with their status and which definition they came from. Use it to find a run id, or to check whether an agent is already working before starting another one.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of agent runs","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"definitionId":{"type":"string","description":"The agent definition (registry key) this run instantiates."},"status":{"type":"string","enum":["open","running","needs_input","failed","succeeded"],"description":"The current lifecycle status of the run."},"triggeredByUserId":{"type":"string","nullable":true,"format":"uuid"},"suppressNotification":{"type":"boolean"},"createdTime":{"type":"string","format":"date-time"},"finishedTime":{"type":"string","nullable":true,"format":"date-time"},"error":{"type":"string","nullable":true,"description":"Error message if the run failed."},"errorDetails":{"type":"string","nullable":true,"description":"Detailed error information if the run failed."},"sessionId":{"type":"string","nullable":true,"description":"The agent SDK session id — the handle to resume this run."},"resumedFromRunId":{"type":"string","nullable":true,"format":"uuid","description":"The terminal run this one continues, if any."},"cloudRunExecutionId":{"type":"string","nullable":true},"lastHeartbeatTime":{"type":"string","nullable":true,"format":"date-time"},"costUsd":{"type":"string","nullable":true,"description":"Accumulated model spend for the run, in USD."},"data":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"nullable":true},{"type":"object","additionalProperties":{"nullable":true}},{"type":"array","items":{"nullable":true}}],"description":"Kickoff input plus the throttled progress projection (summary, last tool, counters)."}},"required":["id","companyId","definitionId","status","triggeredByUserId","suppressNotification","createdTime","finishedTime","error","errorDetails","sessionId","resumedFromRunId","cloudRunExecutionId","lastHeartbeatTime","costUsd","data"]}}}}}}},"post":{"description":"Start an agent run. `definitionId` comes from the agent-definitions endpoint and `input` carries that agent's parameters. Returns a `runId` immediately; the agent works in the background, so poll the run or its messages. To continue a finished session rather than start fresh, pass `resumeFromRunId` and put the follow-up instruction in `input.prompt`; the earlier transcript is restored. Because agents send company data to an AI provider, a company that has not consented gets 403 with `code: \"aiConsentRequired\"` and the `providerKey` to request. The caller also needs every permission the agent uses, not just `transactions:read`. One run at a time per company (409), with a per-company rate limit (429, carrying `retryAt`).","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"definitionId":{"type":"string","minLength":1},"input":{"type":"object","additionalProperties":{"nullable":true}},"suppressNotification":{"type":"boolean"},"resumeFromRunId":{"type":"string","format":"uuid"}},"required":["definitionId"]}}}},"responses":{"201":{"description":"Agent run started","content":{"application/json":{"schema":{"type":"object","properties":{"runId":{"type":"string"}},"required":["runId"]}}}},"400":{"description":"Unknown agent definition or unresumable run","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Caller lacks a permission the agent requires, or the company has not approved AI data sharing for the agent provider","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"providerKey":{"type":"string"}},"required":["error"]}}}},"404":{"description":"The run to resume was not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"A run is already in progress, or the run to resume has not finished","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Per-company run rate limit reached","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"retryAt":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/agent_runs/{id}":{"get":{"description":"Retrieve one agent run: its status, the input it was started with, and its result once finished. Poll this after starting a run. Returns 404 if the run does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Agent run","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"definitionId":{"type":"string","description":"The agent definition (registry key) this run instantiates."},"status":{"type":"string","enum":["open","running","needs_input","failed","succeeded"],"description":"The current lifecycle status of the run."},"triggeredByUserId":{"type":"string","nullable":true,"format":"uuid"},"suppressNotification":{"type":"boolean"},"createdTime":{"type":"string","format":"date-time"},"finishedTime":{"type":"string","nullable":true,"format":"date-time"},"error":{"type":"string","nullable":true,"description":"Error message if the run failed."},"errorDetails":{"type":"string","nullable":true,"description":"Detailed error information if the run failed."},"sessionId":{"type":"string","nullable":true,"description":"The agent SDK session id — the handle to resume this run."},"resumedFromRunId":{"type":"string","nullable":true,"format":"uuid","description":"The terminal run this one continues, if any."},"cloudRunExecutionId":{"type":"string","nullable":true},"lastHeartbeatTime":{"type":"string","nullable":true,"format":"date-time"},"costUsd":{"type":"string","nullable":true,"description":"Accumulated model spend for the run, in USD."},"data":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"nullable":true},{"type":"object","additionalProperties":{"nullable":true}},{"type":"array","items":{"nullable":true}}],"description":"Kickoff input plus the throttled progress projection (summary, last tool, counters)."}},"required":["id","companyId","definitionId","status","triggeredByUserId","suppressNotification","createdTime","finishedTime","error","errorDetails","sessionId","resumedFromRunId","cloudRunExecutionId","lastHeartbeatTime","costUsd","data"]}}}},"404":{"description":"Agent run not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/agent_runs/{id}/messages":{"get":{"description":"Read the conversation of an agent run, oldest first. Poll with `afterId` set to the last id you saw to stream new messages without refetching the thread. Reading a run requires the permissions the agent itself used, so a caller with narrower access than the person who started it gets 403 rather than a filtered view.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Return only messages with an id greater than this."},"required":false,"description":"Return only messages with an id greater than this.","name":"afterId","in":"query"}],"responses":{"200":{"description":"The run conversation messages, oldest first.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"runId":{"type":"string","format":"uuid","description":"The interactive agent run this message belongs to."},"role":{"type":"string","enum":["user","assistant","system"],"description":"Who authored the message: user, assistant, or system."},"userId":{"type":"string","nullable":true,"format":"uuid","description":"The member who wrote a user message; null for assistant and system rows."},"content":{"type":"string","description":"The message text."},"createdTime":{"type":"string","format":"date-time"}},"required":["id","companyId","runId","role","userId","content","createdTime"]}}}}},"403":{"description":"Caller lacks a permission the agent used","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Run not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"post":{"description":"Send a follow-up message to a running interactive agent, up to 8000 characters. The message is queued rather than answered inline, so poll the messages endpoint for the reply. Runs that are not interactive reject this with 400, and a session that has already ended returns 409; start a new run, or resume the finished one, instead.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string","minLength":1,"maxLength":8000}},"required":["content"]}}}},"responses":{"201":{"description":"Message queued for the agent","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"runId":{"type":"string","format":"uuid","description":"The interactive agent run this message belongs to."},"role":{"type":"string","enum":["user","assistant","system"],"description":"Who authored the message: user, assistant, or system."},"userId":{"type":"string","nullable":true,"format":"uuid","description":"The member who wrote a user message; null for assistant and system rows."},"content":{"type":"string","description":"The message text."},"createdTime":{"type":"string","format":"date-time"}},"required":["id","companyId","runId","role","userId","content","createdTime"]}}}},"400":{"description":"The run is not interactive","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Caller lacks a permission the agent uses","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Run not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The session has ended","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/agent_runs/{id}/transcript":{"get":{"description":"Download the full session transcript as JSON Lines: every message and every tool call the agent made, which is the record to read when debugging what an agent actually did. Transcripts run to megabytes, so pass `tailBytes` to take only the end of the run (the cut is aligned to a line start). Without it a transcript that is too large is refused with 413. Content type is `application/x-ndjson`, not JSON.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"integer","minimum":0,"exclusiveMinimum":true,"maximum":10485760,"description":"Return only the last N bytes (aligned to a line start). Use this from tools; full transcripts can be megabytes."},"required":false,"description":"Return only the last N bytes (aligned to a line start). Use this from tools; full transcripts can be megabytes.","name":"tailBytes","in":"query"}],"responses":{"200":{"description":"The full session transcript as JSON Lines: every message and tool call of the run.","content":{"application/x-ndjson":{"schema":{"type":"string"}}}},"403":{"description":"Caller lacks a permission the agent used","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"404":{"description":"Run or transcript not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"413":{"description":"Transcript too large to display","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/ai_consents":{"get":{"description":"List every AI provider SaaSFlow can send company data to, with the current disclosure for each (which agents use it, which models, what data leaves the account, where it is processed) and whether this company has an active consent. Providers without an active consent are still listed, with a null consent. AI features that need a provider stay blocked until consent is granted.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"Per-provider AI data-sharing status: the disclosure (agents, models, data scope, processing location) and the active consent, if any.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"providerKey":{"type":"string","enum":["anthropic","cursor"]},"providerName":{"type":"string"},"processingLocation":{"type":"string"},"disclosureVersion":{"type":"integer"},"agents":{"type":"array","items":{"type":"object","properties":{"definitionId":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"modelName":{"type":"string"},"permissions":{"type":"array","items":{"type":"string","enum":["subscriptions:read","subscriptions:write","customers:read","customers:write","vendors:read","vendors:write","accounts:read","accounts:write","transactions:read","transactions:write","balances:read","balances:write","financials:read","financials:write","users:manage","api_keys:manage","integrations:manage","company:manage"],"description":"A granular permission in the form `<resource>:<action>`. Stored on companiesUsers and companiesApiKeys. When a grant has `permissions: null` it means unrestricted (legacy behavior derived from role)."}}},"required":["definitionId","title","description","modelName","permissions"]}},"consent":{"type":"object","nullable":true,"properties":{"grantedAt":{"type":"string","description":"When consent was granted (ISO 8601)."},"grantedByUserId":{"type":"string","nullable":true},"grantedByUserEmail":{"type":"string","nullable":true},"disclosureVersion":{"type":"integer","description":"The disclosure version the grant covers."}},"required":["grantedAt","grantedByUserId","grantedByUserEmail","disclosureVersion"]},"consented":{"type":"boolean"},"canApprove":{"type":"boolean"}},"required":["providerKey","providerName","processingLocation","disclosureVersion","agents","consent","consented","canApprove"]}}}}}}},"post":{"description":"Grant this company consent to share data with one AI provider. Only a company owner can do this. Send the `disclosureVersion` that was actually shown to the person accepting: if the disclosure has changed since, the request is refused with 409 and the new disclosure has to be shown again. That keeps every recorded consent tied to the exact text it was given against.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"providerKey":{"type":"string","enum":["anthropic","cursor"]},"disclosureVersion":{"type":"integer"}},"required":["providerKey","disclosureVersion"]}}}},"responses":{"201":{"description":"Consent granted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"403":{"description":"Caller is not an owner of the company","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"The disclosure changed since it was shown","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/ai_consents/{providerKey}":{"delete":{"description":"Revoke this company's consent for one AI provider. Agent runs already in flight are allowed to finish; new runs that need the provider are blocked from here on. Returns 404 when there was no active consent to revoke.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","enum":["anthropic","cursor"]},"required":true,"name":"providerKey","in":"path"}],"responses":{"200":{"description":"Consent revoked. Runs already in progress finish; new runs are blocked.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]}},"required":["ok"]}}}},"404":{"description":"No active consent for this provider","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/reports":{"get":{"description":"List a company's performance reports, newest first. The rendered document is left out here to keep the list light, so read a single report to get it. Reports are generated per month by an agent and a row appears as soon as generation starts, which means `status` can still be pending when you list.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"The company reports, newest first, without the document payload.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"definitionId":{"type":"string","description":"The agent definition (registry key) that produces this report."},"reportMonth":{"type":"string","description":"The calendar month the report covers, as YYYY-MM."},"status":{"type":"string","enum":["generating","ready","failed"],"description":"The current lifecycle status of the report."},"runId":{"type":"string","nullable":true,"format":"uuid","description":"The agent run that generated this report, if any."},"title":{"type":"string","nullable":true,"description":"The report title, available once the report is ready."},"summary":{"type":"string","nullable":true,"description":"A short plain-text abstract of the report, available once ready."},"error":{"type":"string","nullable":true,"description":"Failure reason if generation failed."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","definitionId","reportMonth","status","runId","title","summary","error","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"post":{"description":"Start generating a performance report for one month. This returns as soon as the agent run is queued, giving back a `reportId` to poll and a `runId` for following the agent, so the document is not ready yet. Because an AI agent reads company data to write it, a company that has not consented to the agent's AI provider gets a 403 carrying `code: \"aiConsentRequired\"` and the `providerKey` to request consent for. Only one report generates per company at a time (409), and there is a per-company rate limit (429, with `retryAt` when it can be retried).","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reportMonth":{"type":"string","pattern":"^\\d{4}-(0[1-9]|1[0-2])$","description":"The calendar month the report covers, as YYYY-MM."}},"required":["reportMonth"]}}}},"responses":{"201":{"description":"Report generation started","content":{"application/json":{"schema":{"type":"object","properties":{"reportId":{"type":"string"},"runId":{"type":"string"}},"required":["reportId","runId"]}}}},"400":{"description":"Invalid report month","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Caller lacks a permission the report agent requires, or the company has not approved AI data sharing for the agent provider","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"providerKey":{"type":"string"}},"required":["error"]}}}},"409":{"description":"A report is already being generated for this company","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Per-company report rate limit reached","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"retryAt":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/reports/{id}":{"get":{"description":"Retrieve one report, including its rendered document once generation has finished. Poll this after starting a report: until it is ready the document is absent and only the status tells you where it is. Returns 404 if the report does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The report, including the document once ready.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"definitionId":{"type":"string","description":"The agent definition (registry key) that produces this report."},"reportMonth":{"type":"string","description":"The calendar month the report covers, as YYYY-MM."},"status":{"type":"string","enum":["generating","ready","failed"],"description":"The current lifecycle status of the report."},"runId":{"type":"string","nullable":true,"format":"uuid","description":"The agent run that generated this report, if any."},"title":{"type":"string","nullable":true,"description":"The report title, available once the report is ready."},"summary":{"type":"string","nullable":true,"description":"A short plain-text abstract of the report, available once ready."},"error":{"type":"string","nullable":true,"description":"Failure reason if generation failed."},"document":{"type":"object","nullable":true,"properties":{"schemaVersion":{"type":"number","enum":[1]},"title":{"type":"string","minLength":1,"maxLength":140},"period":{"type":"object","properties":{"month":{"type":"string","pattern":"^\\d{4}-(0[1-9]|1[0-2])$"}},"required":["month"]},"facts":{"type":"object","properties":{"mrrBasis":{"type":"string","enum":["cmrr","mrr"]},"timeZone":{"type":"string","minLength":1},"endMrr":{"type":"number"},"priorEndMrr":{"type":"number"},"mrrGrowthPct":{"type":"number"},"endCustomerCount":{"type":"integer"},"newMrr":{"type":"number"},"churnedMrr":{"type":"number"}},"required":["mrrBasis","endMrr","priorEndMrr","mrrGrowthPct","endCustomerCount","newMrr","churnedMrr"]},"summary":{"type":"string","minLength":1,"maxLength":1500},"html":{"type":"string","minLength":1}},"required":["schemaVersion","title","period","summary","html"],"description":"The full generated report document. Null until the report is ready."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","definitionId","reportMonth","status","runId","title","summary","error","document","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Report not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"patch":{"description":"Rename a report. Only the display title changes; the document and its numbers are untouched. A report that is still generating cannot be renamed and returns 409.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200,"description":"The new display title of the report."}},"required":["title"]}}}},"responses":{"200":{"description":"Report renamed","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Report not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Report is not ready","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a report and its superseded versions. A report that is still generating returns 409; wait for the run to finish before removing it.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Report deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}},"404":{"description":"Report not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Report is still generating","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/reports/{id}/versions":{"get":{"description":"List the superseded versions of a report, oldest first. Regenerating a report keeps what it replaced, so this is the audit trail of how the numbers and commentary changed. Documents are omitted; fetch a single version for its document. The current version is not in this list, it is the report itself.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Superseded versions of the report, oldest first.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"reportId":{"type":"string","format":"uuid","description":"The report this superseded version belongs to."},"supersededBy":{"type":"string","description":"What replaced this version, e.g. chat."},"createdTime":{"type":"string","format":"date-time"}},"required":["id","companyId","reportId","supersededBy","createdTime"]}}}}}}}},"/companies/{companyId}/reports/{id}/versions/{versionId}":{"get":{"description":"Retrieve one superseded report version with its full document, for reading what an earlier run of the same report actually said. Returns 404 if the version does not exist or does not belong to this report.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"versionId","in":"path"}],"responses":{"200":{"description":"A superseded report version, including its document.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"reportId":{"type":"string","format":"uuid","description":"The report this superseded version belongs to."},"supersededBy":{"type":"string","description":"What replaced this version, e.g. chat."},"document":{"type":"object","properties":{"schemaVersion":{"type":"number","enum":[1]},"title":{"type":"string","minLength":1,"maxLength":140},"period":{"type":"object","properties":{"month":{"type":"string","pattern":"^\\d{4}-(0[1-9]|1[0-2])$"}},"required":["month"]},"facts":{"type":"object","properties":{"mrrBasis":{"type":"string","enum":["cmrr","mrr"]},"timeZone":{"type":"string","minLength":1},"endMrr":{"type":"number"},"priorEndMrr":{"type":"number"},"mrrGrowthPct":{"type":"number"},"endCustomerCount":{"type":"integer"},"newMrr":{"type":"number"},"churnedMrr":{"type":"number"}},"required":["mrrBasis","endMrr","priorEndMrr","mrrGrowthPct","endCustomerCount","newMrr","churnedMrr"]},"summary":{"type":"string","minLength":1,"maxLength":1500},"html":{"type":"string","minLength":1}},"required":["schemaVersion","title","period","summary","html"],"description":"The superseded report document."},"createdTime":{"type":"string","format":"date-time"}},"required":["id","companyId","reportId","supersededBy","document","createdTime"]}}}},"404":{"description":"Version not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/categorization_suggestions/apply":{"post":{"description":"Apply a reviewed batch of categorization suggestions in one call. Counterparties that do not exist yet go in `vendorsToCreate` and `customersToCreate` with a `tempId`; suggestions reference that same `tempId`, and the response maps each one to the real id it was created as. A suggestion pointing at a `tempId` that was never declared fails the whole request with 400. Creating counterparties needs `vendors:write` or `customers:write` on top of `transactions:write`. The result counts what landed and what was skipped. A transaction is skipped when it is unknown to this company, still pending, or already categorized, including by an earlier suggestion in the same request, so overlapping suggestions never silently overwrite each other.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"suggestions":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string"},"categoryId":{"type":"string"},"categorySystemKey":{"type":"string","nullable":true},"counterparty":{"anyOf":[{"nullable":true},{"type":"object","properties":{"type":{"type":"string","enum":["vendor","customer"]},"id":{"type":"string"}},"required":["type","id"]},{"type":"object","properties":{"type":{"type":"string","enum":["vendor","customer"]},"ref":{"type":"string"}},"required":["type","ref"]},{"type":"object","properties":{"type":{"type":"string","enum":["vendor","customer"]},"tempId":{"type":"string"}},"required":["type","tempId"]}],"default":null},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"Rules for determining the effective/accrual date range of a transaction (excluding custom)."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number"},"cogs":{"type":"number"},"cac":{"type":"number"},"pd":{"type":"number"},"ga":{"type":"number"},"no":{"type":"number"},"neutral":{"type":"number"}}},"passThroughTax":{"type":"number","default":0},"templateSource":{"type":"string"},"confidence":{"type":"string","enum":["high","medium"]},"rationale":{"type":"string"},"totalsByCurrency":{"type":"object","additionalProperties":{"type":"number"}},"transactionIds":{"type":"array","items":{"type":"string"},"minItems":1}},"required":["categoryId","usingDateRule","plSplit","transactionIds"]}},"vendorsToCreate":{"type":"array","items":{"type":"object","properties":{"tempId":{"type":"string"},"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"},"default":[]},"defaultCategorization":{"type":"object","properties":{"categoryId":{"type":"string"},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"Rules for determining the effective/accrual date range of a transaction (excluding custom)."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number"},"cogs":{"type":"number"},"cac":{"type":"number"},"pd":{"type":"number"},"ga":{"type":"number"},"no":{"type":"number"},"neutral":{"type":"number"}}},"passThroughTax":{"type":"number"}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"]},"confidence":{"type":"string","enum":["high","medium"]},"rationale":{"type":"string"}},"required":["tempId","name","defaultCategorization"]},"default":[]},"customersToCreate":{"type":"array","items":{"type":"object","properties":{"tempId":{"type":"string"},"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"},"default":[]},"defaultCategorization":{"type":"object","properties":{"categoryId":{"type":"string"},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"Rules for determining the effective/accrual date range of a transaction (excluding custom)."},"plSplit":{"type":"object","properties":{"revenue":{"type":"number"},"cogs":{"type":"number"},"cac":{"type":"number"},"pd":{"type":"number"},"ga":{"type":"number"},"no":{"type":"number"},"neutral":{"type":"number"}}},"passThroughTax":{"type":"number"}},"required":["categoryId","usingDateRule","plSplit","passThroughTax"]},"confidence":{"type":"string","enum":["high","medium"]},"rationale":{"type":"string"}},"required":["tempId","name","defaultCategorization"]},"default":[]}},"required":["suggestions"]}}}},"responses":{"200":{"description":"Suggestions applied","content":{"application/json":{"schema":{"type":"object","properties":{"appliedSuggestions":{"type":"number"},"appliedTransactions":{"type":"number"},"skippedTransactions":{"type":"number"},"createdVendors":{"type":"array","items":{"type":"object","properties":{"tempId":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"required":["tempId","id","name"]}},"createdCustomers":{"type":"array","items":{"type":"object","properties":{"tempId":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"required":["tempId","id","name"]}}},"required":["appliedSuggestions","appliedTransactions","skippedTransactions","createdVendors","createdCustomers"]}}}},"400":{"description":"A suggestion referenced an unknown new counterparty","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Caller lacks a permission required to create a counterparty","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/plan_items":{"get":{"description":"List every plan item for a company. A plan item is one budgeted line in the financial plan: a P&L type, a category, a starting `month` (YYYY-MM), and an amount rule. The rule is one of three shapes: `fixed` (an explicit amount, optionally growing by `relativeChange` each period), `average` (derived from the last `referencePeriod` months of actuals), or `reference` (a `share` of another planned category). `frequency` is how many months apart the item repeats and `duration` is how many times it repeats, with null meaning open-ended.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of plan items","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"month":{"type":"string","description":"The month this plan item applies to (YYYY-MM format)."},"plType":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"],"description":"The P&L type of the item."},"categoryId":{"type":"string","description":"The category ID for this plan item."},"amount":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["fixed"]},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The list of supported currencies."}},"required":["value","currency"]},"relativeChange":{"type":"number"}},"required":["type","amount","relativeChange"]},{"type":"object","properties":{"type":{"type":"string","enum":["average"]},"referencePeriod":{"type":"number"},"relativeChange":{"type":"number"}},"required":["type","referencePeriod","relativeChange"]},{"type":"object","properties":{"type":{"type":"string","enum":["reference"]},"ref":{"type":"object","properties":{"plType":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"],"description":"The different Profit & Loss (P&L) types for creating a typical SaaS Profit & Loss statement."},"categoryId":{"type":"string"}},"required":["plType","categoryId"]},"share":{"type":"number"},"referenceMonth":{"type":"string","enum":["same","previous"]}},"required":["type","ref","share","referenceMonth"]}],"description":"The amount configuration (fixed, average, or reference)."},"duration":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647,"description":"Duration in months. Null for indefinite."},"frequency":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"How often the item recurs."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","month","plType","categoryId","amount","duration","frequency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"post":{"description":"Add a line to the financial plan. Set `month` to the first month it applies (YYYY-MM), `plType` and `categoryId` to where it lands in the P&L, and `amount` to one of the three rule shapes (`fixed`, `average`, `reference`). Planned figures are separate from planned transactions: plan items drive the P&L plan, planned transactions drive the cash flow forecast.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"month":{"type":"string","minLength":1},"plType":{"type":"string","minLength":1},"categoryId":{"type":"string","minLength":1},"amount":{"nullable":true},"duration":{"type":"integer","nullable":true},"frequency":{"type":"integer"}},"required":["month","plType","categoryId","frequency"]}}}},"responses":{"201":{"description":"Created plan item","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"month":{"type":"string","description":"The month this plan item applies to (YYYY-MM format)."},"plType":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"],"description":"The P&L type of the item."},"categoryId":{"type":"string","description":"The category ID for this plan item."},"amount":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["fixed"]},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The list of supported currencies."}},"required":["value","currency"]},"relativeChange":{"type":"number"}},"required":["type","amount","relativeChange"]},{"type":"object","properties":{"type":{"type":"string","enum":["average"]},"referencePeriod":{"type":"number"},"relativeChange":{"type":"number"}},"required":["type","referencePeriod","relativeChange"]},{"type":"object","properties":{"type":{"type":"string","enum":["reference"]},"ref":{"type":"object","properties":{"plType":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"],"description":"The different Profit & Loss (P&L) types for creating a typical SaaS Profit & Loss statement."},"categoryId":{"type":"string"}},"required":["plType","categoryId"]},"share":{"type":"number"},"referenceMonth":{"type":"string","enum":["same","previous"]}},"required":["type","ref","share","referenceMonth"]}],"description":"The amount configuration (fixed, average, or reference)."},"duration":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647,"description":"Duration in months. Null for indefinite."},"frequency":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"How often the item recurs."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","month","plType","categoryId","amount","duration","frequency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"/companies/{companyId}/plan_items/{planItemId}":{"get":{"description":"Retrieve a single plan item by id. Returns 404 if the item does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"planItemId","in":"path"}],"responses":{"200":{"description":"Plan item details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"month":{"type":"string","description":"The month this plan item applies to (YYYY-MM format)."},"plType":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"],"description":"The P&L type of the item."},"categoryId":{"type":"string","description":"The category ID for this plan item."},"amount":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["fixed"]},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The list of supported currencies."}},"required":["value","currency"]},"relativeChange":{"type":"number"}},"required":["type","amount","relativeChange"]},{"type":"object","properties":{"type":{"type":"string","enum":["average"]},"referencePeriod":{"type":"number"},"relativeChange":{"type":"number"}},"required":["type","referencePeriod","relativeChange"]},{"type":"object","properties":{"type":{"type":"string","enum":["reference"]},"ref":{"type":"object","properties":{"plType":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"],"description":"The different Profit & Loss (P&L) types for creating a typical SaaS Profit & Loss statement."},"categoryId":{"type":"string"}},"required":["plType","categoryId"]},"share":{"type":"number"},"referenceMonth":{"type":"string","enum":["same","previous"]}},"required":["type","ref","share","referenceMonth"]}],"description":"The amount configuration (fixed, average, or reference)."},"duration":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647,"description":"Duration in months. Null for indefinite."},"frequency":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"How often the item recurs."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","month","plType","categoryId","amount","duration","frequency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Plan item not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a plan item. Only the fields you send are changed. Switching the `amount` rule to a different shape means sending the whole `amount` object, not just the field you want to change.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"planItemId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"month":{"type":"string","minLength":1},"plType":{"type":"string","minLength":1},"categoryId":{"type":"string","minLength":1},"amount":{"nullable":true},"duration":{"type":"integer","nullable":true},"frequency":{"type":"integer"}}}}}},"responses":{"200":{"description":"Updated plan item","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"month":{"type":"string","description":"The month this plan item applies to (YYYY-MM format)."},"plType":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"],"description":"The P&L type of the item."},"categoryId":{"type":"string","description":"The category ID for this plan item."},"amount":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["fixed"]},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The list of supported currencies."}},"required":["value","currency"]},"relativeChange":{"type":"number"}},"required":["type","amount","relativeChange"]},{"type":"object","properties":{"type":{"type":"string","enum":["average"]},"referencePeriod":{"type":"number"},"relativeChange":{"type":"number"}},"required":["type","referencePeriod","relativeChange"]},{"type":"object","properties":{"type":{"type":"string","enum":["reference"]},"ref":{"type":"object","properties":{"plType":{"type":"string","enum":["revenue","cogs","cac","pd","ga","no","neutral"],"description":"The different Profit & Loss (P&L) types for creating a typical SaaS Profit & Loss statement."},"categoryId":{"type":"string"}},"required":["plType","categoryId"]},"share":{"type":"number"},"referenceMonth":{"type":"string","enum":["same","previous"]}},"required":["type","ref","share","referenceMonth"]}],"description":"The amount configuration (fixed, average, or reference)."},"duration":{"type":"integer","nullable":true,"minimum":-2147483648,"maximum":2147483647,"description":"Duration in months. Null for indefinite."},"frequency":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"How often the item recurs."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","month","plType","categoryId","amount","duration","frequency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Plan item not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a plan item, removing it from every month it covered. Deletion is not blocked by other plan items whose `reference` rule points at this item's category, so check those before removing a line other plan items are derived from.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"planItemId","in":"path"}],"responses":{"200":{"description":"Plan item deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/product_groups":{"get":{"description":"List a company's product groups. Product groups bundle related products so revenue reporting can roll up by group instead of by individual product. Archived groups are included; filter on `archived` for the active ones. Results come back unsorted, so sort by `order` ascending to match the app.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of product groups","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product group."},"archived":{"type":"boolean","description":"Flag indicating if the product group is archived."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering product groups in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","archived","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"post":{"description":"Create a product group. `order` is required and sets the position in the list; pass a number higher than every existing group to append at the end. Assign products to it with `PUT /products/{productId}/change_group`.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"archived":{"type":"boolean","default":false},"order":{"type":"number"}},"required":["name","order"]}}}},"responses":{"201":{"description":"Created product group","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product group."},"archived":{"type":"boolean","description":"Flag indicating if the product group is archived."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering product groups in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","archived","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"/companies/{companyId}/product_groups/reorder":{"put":{"description":"Set the display order of product groups. Pass every group id in the order you want them shown; each group's `order` becomes its position in the array. Send the full list, because groups you omit keep their old `order` and can end up interleaved.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"productGroupIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":1000}},"required":["productGroupIds"]}}}},"responses":{"200":{"description":"Order updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/product_groups/{productGroupId}":{"get":{"description":"Retrieve a single product group by id. Returns 404 if the group does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productGroupId","in":"path"}],"responses":{"200":{"description":"Product group details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product group."},"archived":{"type":"boolean","description":"Flag indicating if the product group is archived."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering product groups in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","archived","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Product group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Rename a product group or archive it. Archiving hides the group without changing the products assigned to it.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productGroupId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"archived":{"type":"boolean"},"order":{"type":"number"}}}}}},"responses":{"200":{"description":"Updated product group","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product group."},"archived":{"type":"boolean","description":"Flag indicating if the product group is archived."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering product groups in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","name","archived","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Product group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a product group. Move every product out of the group first (`PUT /products/{productId}/change_group`), otherwise the delete fails on the foreign key from products. Archive the group with `PUT` if you only want it hidden.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productGroupId","in":"path"}],"responses":{"200":{"description":"Product group deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/products":{"get":{"description":"List a company's products, each with `eventCount` (how many subscription events reference it) and `cmrrByCurrency` (current committed MRR keyed by currency code, unconverted). Products are the plans and add-ons that subscription events are booked against, which is what makes revenue breakdowns by product possible. `productGroupId` is null for products that sit outside any group.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of products with CMRR and usage data","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product."},"productGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the product group this product belongs to. Null if ungrouped."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering products within their group in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this product (e.g., Stripe, HubSpot)."},"eventCount":{"type":"number"},"cmrrByCurrency":{"type":"object","additionalProperties":{"type":"number"}}},"required":["id","companyId","name","productGroupId","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds","eventCount","cmrrByCurrency"]}}}}}}},"post":{"description":"Create a product. Pass `productGroupId` to file it under a group straight away, or leave it null or absent for an ungrouped product. Fails with 404 if the named group does not exist. Products imported from a billing provider are created by the importer, so create by hand only for revenue you track yourself.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"productGroupId":{"type":"string","nullable":true}},"required":["name"]}}}},"responses":{"201":{"description":"Created product","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product."},"productGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the product group this product belongs to. Null if ungrouped."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering products within their group in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this product (e.g., Stripe, HubSpot)."}},"required":["id","companyId","name","productGroupId","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"404":{"description":"Product group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/products/reorder":{"put":{"description":"Reorder products inside one group, and move them into it in the same call. Every id in `productIds` is assigned to `productGroupId` (send null for the ungrouped list) and given an `order` matching its position. This is the endpoint behind drag-and-drop between groups, so send the full contents of the target group, not just the product that moved. Fails with 404 if the group does not exist.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"productGroupId":{"type":"string","nullable":true},"productIds":{"type":"array","items":{"type":"string","minLength":1},"maxItems":1000}},"required":["productGroupId","productIds"]}}}},"responses":{"200":{"description":"Group membership and order updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"404":{"description":"Product group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/products/{productId}":{"get":{"description":"Retrieve a single product by id. Unlike the list endpoint, this returns the plain product without the cMRR and event-count enrichment. Returns 404 if the product does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productId","in":"path"}],"responses":{"200":{"description":"Product details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product."},"productGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the product group this product belongs to. Null if ungrouped."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering products within their group in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this product (e.g., Stripe, HubSpot)."}},"required":["id","companyId","name","productGroupId","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"404":{"description":"Product not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a product. Only the fields you send change. Renaming is safe for reporting: subscription events reference the product by id, so past revenue follows the new name.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"productGroupId":{"type":"string","nullable":true},"order":{"type":"number"}}}}}},"responses":{"200":{"description":"Updated product","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product."},"productGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the product group this product belongs to. Null if ungrouped."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering products within their group in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this product (e.g., Stripe, HubSpot)."}},"required":["id","companyId","name","productGroupId","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"404":{"description":"Product or product group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a product. Check `eventCount` on the list endpoint first: a product still referenced by subscription events is load-bearing for MRR-by-product reporting, and removing it strips that breakdown from the history.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productId","in":"path"}],"responses":{"200":{"description":"Product deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/products/{productId}/change_group":{"put":{"description":"Move one product into a different product group, or send `productGroupId: null` to take it out of every group. Use `/products/reorder` instead when you also need to set the position inside the target group. Fails with 404 if the product or the group does not exist.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"productGroupId":{"type":"string","nullable":true}},"required":["productGroupId"]}}}},"responses":{"200":{"description":"Product group changed","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"name":{"type":"string","description":"The name of the product."},"productGroupId":{"type":"string","nullable":true,"format":"uuid","description":"The ID of the product group this product belongs to. Null if ungrouped."},"order":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"A number used for ordering products within their group in the UI."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true},"externalIds":{"type":"array","items":{"type":"string"},"description":"External IDs (URNs) linked to this product (e.g., Stripe, HubSpot)."}},"required":["id","companyId","name","productGroupId","order","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId","externalIds"]}}}},"404":{"description":"Product or product group not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/products/{productId}/external-ids":{"get":{"description":"List the external ids linked to a product. These map the product to its counterpart in a billing system (a Stripe price or product id, for instance), which is how imported subscription events find the right product.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productId","in":"path"}],"responses":{"200":{"description":"External IDs for the product","content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}}}},"put":{"description":"Replace a product's external ids with the list you send. This is a full replacement, not a merge, so include the ids you want to keep. An id already claimed by a different product in the same company is refused with 409, which stops one billing plan mapping to two products.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"productId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}},"responses":{"200":{"description":"Updated external IDs","content":{"application/json":{"schema":{"type":"object","properties":{"externalIds":{"type":"array","items":{"type":"string"}}},"required":["externalIds"]}}}},"409":{"description":"One or more externalIds already in use by another product","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/subscriptions":{"get":{"description":"List subscriptions, optionally narrowed to one customer with `customerId`. A subscription here is a container with a customer and a currency; the money lives in its subscription events, which record every new, upgrade, downgrade, cancel, and reactivate. To see what a subscription is currently worth, read its events rather than the subscription row. Subscriptions with a non-null `externalId` are mirrored from a billing provider and should be changed there, not here.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","format":"uuid"},"required":false,"name":"customerId","in":"query"}],"responses":{"200":{"description":"List of subscriptions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid","description":"The ID of the customer this subscription belongs to."},"externalId":{"type":"string","nullable":true,"description":"External subscription ID (e.g., Stripe subscription ID)."},"name":{"type":"string","nullable":true,"description":"The name of the subscription."},"currency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the subscription."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","customerId","externalId","name","currency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"post":{"description":"Create a subscription for a customer. Creating one on its own contributes nothing to MRR: add subscription events to it, starting with a `new` event, for the revenue to appear. `currency` is fixed at the subscription level, so a customer paying in two currencies needs two subscriptions. Set `externalId` only when mirroring a billing provider; a value already used by another subscription is refused with 409.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"customerId":{"type":"string","minLength":1},"name":{"type":"string","nullable":true},"currency":{"type":"string","minLength":1},"externalId":{"type":"string","nullable":true}},"required":["customerId","currency"]}}}},"responses":{"201":{"description":"Created subscription","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid","description":"The ID of the customer this subscription belongs to."},"externalId":{"type":"string","nullable":true,"description":"External subscription ID (e.g., Stripe subscription ID)."},"name":{"type":"string","nullable":true,"description":"The name of the subscription."},"currency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the subscription."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","customerId","externalId","name","currency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"409":{"description":"externalId already in use","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/subscriptions/{subscriptionId}":{"get":{"description":"Retrieve a single subscription by id. Returns 404 if the subscription does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"subscriptionId","in":"path"}],"responses":{"200":{"description":"Subscription details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid","description":"The ID of the customer this subscription belongs to."},"externalId":{"type":"string","nullable":true,"description":"External subscription ID (e.g., Stripe subscription ID)."},"name":{"type":"string","nullable":true,"description":"The name of the subscription."},"currency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the subscription."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","customerId","externalId","name","currency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Subscription not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a subscription. Only the fields you send change. This edits the container only, never the revenue: to change what a subscription is worth, add or edit its subscription events.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"subscriptionId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","nullable":true},"currency":{"type":"string","minLength":1}}}}}},"responses":{"200":{"description":"Updated subscription","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"customerId":{"type":"string","format":"uuid","description":"The ID of the customer this subscription belongs to."},"externalId":{"type":"string","nullable":true,"description":"External subscription ID (e.g., Stripe subscription ID)."},"name":{"type":"string","nullable":true,"description":"The name of the subscription."},"currency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the subscription."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","customerId","externalId","name","currency","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Subscription not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a subscription and its events, which removes its contribution from MRR history for every period it covered. Subscriptions with an `externalId` are mirrored from a billing provider and refuse deletion with 400; cancel them at the source so the next sync reflects it. To end a subscription without erasing its history, record a `cancel` event instead.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"subscriptionId","in":"path"}],"responses":{"200":{"description":"Subscription deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"400":{"description":"Cannot delete externally managed subscription","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/subscription_events":{"get":{"description":"List subscription events, optionally narrowed by `subscriptionId` or `customerId`. These events are the ledger SaaSFlow computes MRR from. Each row is a **delta**, not a level: `mrrChange` is the amount added or removed at that moment, so a subscription's current MRR is the sum of its events, and a plain cancellation shows up as a negative `mrrChange` that offsets what came before. Two timestamps matter and they are not interchangeable: `effectiveTime` is when the change hits MRR, `committedTime` is when it was agreed, which is what committed MRR is measured on. Amounts are in the parent subscription's currency.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string"},"required":false,"name":"subscriptionId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"customerId","in":"query"}],"responses":{"200":{"description":"List of subscription events","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"subscriptionId":{"type":"string","format":"uuid"},"productId":{"type":"string","format":"uuid"},"committedTime":{"type":"string","format":"date-time","description":"When the subscription change was committed."},"effectiveTime":{"type":"string","format":"date-time","description":"When the subscription change takes effect."},"mrrChange":{"type":"string","description":"The change in monthly recurring revenue."},"quantityChange":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"The change in quantity (non-monetary)."},"billingInterval":{"type":"string","enum":["day","week","month","year"],"description":"The billing interval (day, week, month, year)."},"billingIntervalCount":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"The number of billing intervals per cycle."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","subscriptionId","productId","committedTime","effectiveTime","mrrChange","quantityChange","billingInterval","billingIntervalCount","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"post":{"description":"Record a subscription event, which is how revenue actually enters SaaSFlow. `mrrChange` is a signed delta in the subscription's currency, sent as a decimal string so nothing is lost to floating point: positive for a new subscription or an upgrade, negative for a downgrade, and the negative of everything booked so far for a full cancellation. `quantityChange` is the matching seat or unit delta. `effectiveTime` is when the change hits MRR and `committedTime` is when it was agreed, and getting the two the wrong way round is the usual cause of MRR landing in the wrong month. Fails with 404 if the subscription is not in this company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"subscriptionId":{"type":"string","minLength":1},"productId":{"type":"string","minLength":1},"committedTime":{"type":"string","minLength":1},"effectiveTime":{"type":"string","minLength":1},"mrrChange":{"type":"string"},"quantityChange":{"type":"number"},"billingInterval":{"type":"string","minLength":1},"billingIntervalCount":{"type":"integer"}},"required":["subscriptionId","productId","committedTime","effectiveTime","mrrChange","quantityChange","billingInterval","billingIntervalCount"]}}}},"responses":{"201":{"description":"Created subscription event","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"subscriptionId":{"type":"string","format":"uuid"},"productId":{"type":"string","format":"uuid"},"committedTime":{"type":"string","format":"date-time","description":"When the subscription change was committed."},"effectiveTime":{"type":"string","format":"date-time","description":"When the subscription change takes effect."},"mrrChange":{"type":"string","description":"The change in monthly recurring revenue."},"quantityChange":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"The change in quantity (non-monetary)."},"billingInterval":{"type":"string","enum":["day","week","month","year"],"description":"The billing interval (day, week, month, year)."},"billingIntervalCount":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"The number of billing intervals per cycle."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","subscriptionId","productId","committedTime","effectiveTime","mrrChange","quantityChange","billingInterval","billingIntervalCount","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Subscription not found in this company","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/subscription_events/{eventId}":{"put":{"description":"Correct a subscription event. Only the fields you send change. Because events are deltas, editing one rewrites MRR from its `effectiveTime` onward, so a correction here moves historical numbers rather than only affecting the present. Events mirrored from a billing provider are overwritten on the next sync; fix those at the source.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"eventId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"productId":{"type":"string","minLength":1},"committedTime":{"type":"string","minLength":1},"effectiveTime":{"type":"string","minLength":1},"mrrChange":{"type":"string"},"quantityChange":{"type":"number"},"billingInterval":{"type":"string","minLength":1},"billingIntervalCount":{"type":"integer"}}}}}},"responses":{"200":{"description":"Updated subscription event","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"subscriptionId":{"type":"string","format":"uuid"},"productId":{"type":"string","format":"uuid"},"committedTime":{"type":"string","format":"date-time","description":"When the subscription change was committed."},"effectiveTime":{"type":"string","format":"date-time","description":"When the subscription change takes effect."},"mrrChange":{"type":"string","description":"The change in monthly recurring revenue."},"quantityChange":{"type":"number","minimum":-8388608,"maximum":8388607,"description":"The change in quantity (non-monetary)."},"billingInterval":{"type":"string","enum":["day","week","month","year"],"description":"The billing interval (day, week, month, year)."},"billingIntervalCount":{"type":"integer","minimum":-2147483648,"maximum":2147483647,"description":"The number of billing intervals per cycle."},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","subscriptionId","productId","committedTime","effectiveTime","mrrChange","quantityChange","billingInterval","billingIntervalCount","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Subscription event not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a subscription event. Its `mrrChange` is removed from every period after its `effectiveTime`, so deleting the wrong row leaves a subscription permanently over- or under-counted. To end a subscription, add a cancelling event rather than deleting the ones that created it.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"eventId","in":"path"}],"responses":{"200":{"description":"Subscription event deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}},"404":{"description":"Subscription event not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}}},"/companies/{companyId}/planned_transactions":{"get":{"description":"List planned transactions for a company. Each row is a recurrence rule, not a dated event. For individual dated occurrences, and for totals over a date range, use the `planned_transaction_occurrences` endpoint instead. Amount semantics: `amountValue` is the **net** amount excluding pass-through taxes (e.g. VAT). When `passThroughTaxAmount` is non-null, the **gross cash impact** on the bank account is `amountValue + passThroughTaxAmount`. For \"planned cash inflow / outflow\" questions, sum the gross amount; for \"planned revenue / cost net of VAT\", sum `amountValue` only.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"List of planned transactions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","nullable":true},"name":{"type":"string","description":"The name of the planned transaction."},"startDate":{"type":"string","description":"The start date of the planned transaction."},"amountValue":{"type":"string","description":"The numerical value of the planned amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the planned amount."},"passThroughTaxAmount":{"type":"string","nullable":true,"description":"The pass-through tax amount (e.g., VAT)."},"categoryId":{"type":"string","nullable":true},"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string"}},"required":["type","id"]},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"Rules for determining the effective/accrual date range of a transaction (excluding custom)."},"recurrence":{"type":"object","nullable":true,"properties":{"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number"},"lastDayOfMonth":{"type":"boolean"},"end":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"date":{"type":"string"}},"required":["type","date"]},{"type":"object","properties":{"type":{"type":"string","enum":["count"]},"count":{"type":"number"}},"required":["type","count"]},{"nullable":true}]}},"required":["frequency","interval","end"],"description":"Recurrence configuration (frequency, interval, end condition)."},"overrides":{"type":"object","additionalProperties":{"nullable":true}},"source":{"type":"string","nullable":true,"description":"The source of the planned transaction (e.g., Stripe, HubSpot)."},"externalId":{"type":"string","nullable":true},"externalSubscriptionId":{"type":"string","nullable":true},"externalInvoiceId":{"type":"string","nullable":true,"description":"The invoice id in the source system, for issued invoices."},"invoiceDate":{"type":"string","nullable":true,"description":"The date an invoice was issued for this amount. Set only when the row represents an invoice that has been issued but not yet paid; null for predicted amounts."},"serviceStartDate":{"type":"string","nullable":true,"description":"Start of the service period the issued invoice bills for."},"serviceEndDate":{"type":"string","nullable":true,"description":"End of the service period the issued invoice bills for."},"externalData":{"type":"object","nullable":true,"properties":{"invoiceNumber":{"type":"string","nullable":true},"invoiceUrl":{"type":"string","nullable":true},"collectionMethod":{"type":"string","nullable":true,"enum":["charge_automatically","send_invoice",null]},"attempted":{"type":"boolean","nullable":true},"attemptCount":{"type":"number","nullable":true},"nextPaymentAttemptDate":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true}},"required":["invoiceNumber","invoiceUrl"]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","accountId","name","startDate","amountValue","amountCurrency","passThroughTaxAmount","categoryId","counterparty","usingDateRule","recurrence","overrides","source","externalId","externalSubscriptionId","externalInvoiceId","invoiceDate","serviceStartDate","serviceEndDate","externalData","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"post":{"description":"Create a planned transaction, the expected future cash event the forecast is built from. The sign of `amountValue` is what makes it money in or money out, so send a negative value for planned spend and a positive one for planned income. It is a decimal string, and it excludes pass-through tax: put VAT in `passThroughTaxAmount` separately, since the gross hit to the bank account is the two added together. Leave `recurrence` null for a one-off. For a repeating item, `frequency` and `interval` combine (`{ frequency: \"month\", interval: 3 }` is quarterly, not monthly for three months) and `end` takes either a date or a number of occurrences. `startDate` is the first occurrence, not the day you created the rule.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"startDate":{"type":"string","minLength":1},"amountValue":{"type":"string"},"amountCurrency":{"type":"string","minLength":1},"accountId":{"type":"string","nullable":true},"categoryId":{"type":"string","nullable":true},"counterparty":{"nullable":true},"usingDateRule":{"nullable":true},"recurrence":{"nullable":true},"passThroughTaxAmount":{"type":"string","nullable":true},"overrides":{"type":"object","additionalProperties":{"nullable":true}}},"required":["name","startDate","amountValue","amountCurrency"]}}}},"responses":{"201":{"description":"Created planned transaction","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","nullable":true},"name":{"type":"string","description":"The name of the planned transaction."},"startDate":{"type":"string","description":"The start date of the planned transaction."},"amountValue":{"type":"string","description":"The numerical value of the planned amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the planned amount."},"passThroughTaxAmount":{"type":"string","nullable":true,"description":"The pass-through tax amount (e.g., VAT)."},"categoryId":{"type":"string","nullable":true},"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string"}},"required":["type","id"]},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"Rules for determining the effective/accrual date range of a transaction (excluding custom)."},"recurrence":{"type":"object","nullable":true,"properties":{"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number"},"lastDayOfMonth":{"type":"boolean"},"end":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"date":{"type":"string"}},"required":["type","date"]},{"type":"object","properties":{"type":{"type":"string","enum":["count"]},"count":{"type":"number"}},"required":["type","count"]},{"nullable":true}]}},"required":["frequency","interval","end"],"description":"Recurrence configuration (frequency, interval, end condition)."},"overrides":{"type":"object","additionalProperties":{"nullable":true}},"source":{"type":"string","nullable":true,"description":"The source of the planned transaction (e.g., Stripe, HubSpot)."},"externalId":{"type":"string","nullable":true},"externalSubscriptionId":{"type":"string","nullable":true},"externalInvoiceId":{"type":"string","nullable":true,"description":"The invoice id in the source system, for issued invoices."},"invoiceDate":{"type":"string","nullable":true,"description":"The date an invoice was issued for this amount. Set only when the row represents an invoice that has been issued but not yet paid; null for predicted amounts."},"serviceStartDate":{"type":"string","nullable":true,"description":"Start of the service period the issued invoice bills for."},"serviceEndDate":{"type":"string","nullable":true,"description":"End of the service period the issued invoice bills for."},"externalData":{"type":"object","nullable":true,"properties":{"invoiceNumber":{"type":"string","nullable":true},"invoiceUrl":{"type":"string","nullable":true},"collectionMethod":{"type":"string","nullable":true,"enum":["charge_automatically","send_invoice",null]},"attempted":{"type":"boolean","nullable":true},"attemptCount":{"type":"number","nullable":true},"nextPaymentAttemptDate":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true}},"required":["invoiceNumber","invoiceUrl"]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","accountId","name","startDate","amountValue","amountCurrency","passThroughTaxAmount","categoryId","counterparty","usingDateRule","recurrence","overrides","source","externalId","externalSubscriptionId","externalInvoiceId","invoiceDate","serviceStartDate","serviceEndDate","externalData","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}}}}},"/companies/{companyId}/planned_transaction_occurrences":{"get":{"description":"List individual dated occurrences of planned transactions within a date range, expanded from recurrence rules. Each item represents one expected cash event on a specific date. Amount semantics: `amount.value` is the **net** amount excluding pass-through taxes (e.g. VAT). When the underlying planned transaction has VAT, the tax portion is returned **separately** as `passThroughTaxAmount` and is **not** included in `amount.value`. To compute the total gross cash inflow / outflow for a period (e.g. for a cash flow forecast that matches what hits the bank account), sum `amount.value + (passThroughTaxAmount?.value ?? 0)` over all items. To compute net planned revenue / cost (excluding VAT, matching P&L), sum only `amount.value`. Use pagination (`limit`, `offset`) to fetch all items; `totalCount` is the unpaginated total.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"startDate","in":"query"},{"schema":{"type":"string","minLength":1},"required":true,"name":"endDate","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0},"required":false,"name":"offset","in":"query"},{"schema":{"type":"string"},"required":false,"name":"accountIds","in":"query"},{"schema":{"type":"string"},"required":false,"name":"categoryIds","in":"query"},{"schema":{"type":"string","enum":["0","1","true","false"]},"required":false,"name":"showUncategorized","in":"query"},{"schema":{"type":"string"},"required":false,"name":"counterpartyIds","in":"query"},{"schema":{"type":"string","enum":["0","1","true","false"]},"required":false,"name":"showUnassignedCounterparty","in":"query"},{"schema":{"type":"string"},"required":false,"name":"customerId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"vendorId","in":"query"},{"schema":{"type":"string","enum":["0","1","true","false"],"description":"When true, only occurrences backed by an issued invoice are returned (those with a non-null `invoice`), skipping predicted occurrences. Useful for listing overdue invoices in a past date range without dragging in stale predictions."},"required":false,"description":"When true, only occurrences backed by an issued invoice are returned (those with a non-null `invoice`), skipping predicted occurrences. Useful for listing overdue invoices in a past date range without dragging in stale predictions.","name":"invoicedOnly","in":"query"}],"responses":{"200":{"description":"Paginated list of planned transaction occurrences","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"plannedTransactionId":{"type":"string"},"date":{"type":"string"},"computedStartDate":{"type":"string"},"computedEndDate":{"type":"string"},"usingPeriodDays":{"type":"number"},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Net amount of this occurrence, **excluding** pass-through taxes (e.g. VAT). Sign indicates direction (positive = inflow, negative = outflow). To compute the gross cash impact on the bank account, add `passThroughTaxAmount.value` when it is non-null."},"passThroughTaxAmount":{"type":"object","nullable":true,"properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"],"description":"Pass-through tax (e.g. VAT) collected or paid on top of `amount`, when applicable. Null when the planned transaction has no pass-through tax. This amount is **not** included in `amount` — add it to `amount.value` to get the gross cash flow that hits the bank account."},"accountId":{"type":"string","nullable":true},"categoryId":{"type":"string","nullable":true},"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string"}},"required":["type","id"]},"counterpartyName":{"type":"string","nullable":true},"counterpartyLogoUrl":{"type":"string","nullable":true},"name":{"type":"string"},"startDate":{"type":"string"},"recurrence":{"nullable":true},"overrides":{"type":"object","additionalProperties":{"nullable":true}},"source":{"type":"string","nullable":true},"invoice":{"type":"object","nullable":true,"properties":{"date":{"type":"string"},"externalId":{"type":"string","nullable":true},"number":{"type":"string","nullable":true},"url":{"type":"string","nullable":true},"collectionStalled":{"type":"string","nullable":true,"description":"Set when collection of this invoice looks stalled from our side: the provider has exhausted its payment retries, never attempted collection within a grace period, or the invoice is far past due. The string states the reason. Null while collection is still progressing normally. The invoice stays open with the provider — this is a heuristic, not a write-off."}},"required":["date","externalId","number","url","collectionStalled"],"description":"Set when this occurrence is an invoice that has already been issued but is not paid yet, rather than a prediction. For those, `date` is when payment is expected (the due date) and `computedStartDate` / `computedEndDate` is the service period the invoice bills for. Null for predicted occurrences."}},"required":["id","plannedTransactionId","date","computedStartDate","computedEndDate","usingPeriodDays","amount","passThroughTaxAmount","accountId","categoryId","counterparty","counterpartyName","counterpartyLogoUrl","name","startDate","overrides","source","invoice"]}},"totalCount":{"type":"number"}},"required":["items","totalCount"]}}}}}}},"/companies/{companyId}/planned_transactions/{plannedTransactionId}":{"get":{"description":"Retrieve a single planned transaction by id, meaning the recurrence rule rather than its dated occurrences. Same amount semantics as the list endpoint: `amountValue` is net of pass-through tax. Returns 404 if it does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"plannedTransactionId","in":"path"}],"responses":{"200":{"description":"Planned transaction details","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","nullable":true},"name":{"type":"string","description":"The name of the planned transaction."},"startDate":{"type":"string","description":"The start date of the planned transaction."},"amountValue":{"type":"string","description":"The numerical value of the planned amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the planned amount."},"passThroughTaxAmount":{"type":"string","nullable":true,"description":"The pass-through tax amount (e.g., VAT)."},"categoryId":{"type":"string","nullable":true},"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string"}},"required":["type","id"]},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"Rules for determining the effective/accrual date range of a transaction (excluding custom)."},"recurrence":{"type":"object","nullable":true,"properties":{"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number"},"lastDayOfMonth":{"type":"boolean"},"end":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"date":{"type":"string"}},"required":["type","date"]},{"type":"object","properties":{"type":{"type":"string","enum":["count"]},"count":{"type":"number"}},"required":["type","count"]},{"nullable":true}]}},"required":["frequency","interval","end"],"description":"Recurrence configuration (frequency, interval, end condition)."},"overrides":{"type":"object","additionalProperties":{"nullable":true}},"source":{"type":"string","nullable":true,"description":"The source of the planned transaction (e.g., Stripe, HubSpot)."},"externalId":{"type":"string","nullable":true},"externalSubscriptionId":{"type":"string","nullable":true},"externalInvoiceId":{"type":"string","nullable":true,"description":"The invoice id in the source system, for issued invoices."},"invoiceDate":{"type":"string","nullable":true,"description":"The date an invoice was issued for this amount. Set only when the row represents an invoice that has been issued but not yet paid; null for predicted amounts."},"serviceStartDate":{"type":"string","nullable":true,"description":"Start of the service period the issued invoice bills for."},"serviceEndDate":{"type":"string","nullable":true,"description":"End of the service period the issued invoice bills for."},"externalData":{"type":"object","nullable":true,"properties":{"invoiceNumber":{"type":"string","nullable":true},"invoiceUrl":{"type":"string","nullable":true},"collectionMethod":{"type":"string","nullable":true,"enum":["charge_automatically","send_invoice",null]},"attempted":{"type":"boolean","nullable":true},"attemptCount":{"type":"number","nullable":true},"nextPaymentAttemptDate":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true}},"required":["invoiceNumber","invoiceUrl"]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","accountId","name","startDate","amountValue","amountCurrency","passThroughTaxAmount","categoryId","counterparty","usingDateRule","recurrence","overrides","source","externalId","externalSubscriptionId","externalInvoiceId","invoiceDate","serviceStartDate","serviceEndDate","externalData","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Planned transaction not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"put":{"description":"Update a planned transaction. Only the fields you send change. Edits apply to the whole rule, so changing the amount or the recurrence rewrites every future occurrence rather than just the next one, and moving `startDate` shifts the entire series. Returns 404 if it does not exist or belongs to another company.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"plannedTransactionId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1},"startDate":{"type":"string","minLength":1},"amountValue":{"type":"string"},"amountCurrency":{"type":"string","minLength":1},"accountId":{"type":"string","nullable":true},"categoryId":{"type":"string","nullable":true},"counterparty":{"nullable":true},"usingDateRule":{"nullable":true},"recurrence":{"nullable":true},"passThroughTaxAmount":{"type":"string","nullable":true},"overrides":{"type":"object","additionalProperties":{"nullable":true}}}}}}},"responses":{"200":{"description":"Updated planned transaction","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"companyId":{"type":"string","format":"uuid"},"accountId":{"type":"string","nullable":true},"name":{"type":"string","description":"The name of the planned transaction."},"startDate":{"type":"string","description":"The start date of the planned transaction."},"amountValue":{"type":"string","description":"The numerical value of the planned amount."},"amountCurrency":{"type":"string","enum":["EUR","AUD","BRL","CAD","CHF","CNY","CZK","DKK","GBP","HKD","HUF","IDR","ILS","INR","ISK","JPY","KRW","MXN","MYR","NOK","NZD","PHP","PLN","RON","SEK","SGD","THB","TRY","USD","ZAR"],"description":"The currency of the planned amount."},"passThroughTaxAmount":{"type":"string","nullable":true,"description":"The pass-through tax amount (e.g., VAT)."},"categoryId":{"type":"string","nullable":true},"counterparty":{"type":"object","nullable":true,"properties":{"type":{"type":"string","enum":["customer","vendor"]},"id":{"type":"string"}},"required":["type","id"]},"usingDateRule":{"oneOf":[{"type":"object","properties":{"type":{"type":"string","enum":["transactionDate"]}},"required":["type"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["spread"]},"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number","minimum":1}},"required":["type","frequency","interval"],"additionalProperties":false},{"type":"object","properties":{"type":{"type":"string","enum":["calendarPeriod"]},"period":{"type":"string","enum":["month","year"]},"offset":{"type":"number"}},"required":["type","period","offset"],"additionalProperties":false}],"description":"Rules for determining the effective/accrual date range of a transaction (excluding custom)."},"recurrence":{"type":"object","nullable":true,"properties":{"frequency":{"type":"string","enum":["day","week","month","year"]},"interval":{"type":"number"},"lastDayOfMonth":{"type":"boolean"},"end":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","enum":["date"]},"date":{"type":"string"}},"required":["type","date"]},{"type":"object","properties":{"type":{"type":"string","enum":["count"]},"count":{"type":"number"}},"required":["type","count"]},{"nullable":true}]}},"required":["frequency","interval","end"],"description":"Recurrence configuration (frequency, interval, end condition)."},"overrides":{"type":"object","additionalProperties":{"nullable":true}},"source":{"type":"string","nullable":true,"description":"The source of the planned transaction (e.g., Stripe, HubSpot)."},"externalId":{"type":"string","nullable":true},"externalSubscriptionId":{"type":"string","nullable":true},"externalInvoiceId":{"type":"string","nullable":true,"description":"The invoice id in the source system, for issued invoices."},"invoiceDate":{"type":"string","nullable":true,"description":"The date an invoice was issued for this amount. Set only when the row represents an invoice that has been issued but not yet paid; null for predicted amounts."},"serviceStartDate":{"type":"string","nullable":true,"description":"Start of the service period the issued invoice bills for."},"serviceEndDate":{"type":"string","nullable":true,"description":"End of the service period the issued invoice bills for."},"externalData":{"type":"object","nullable":true,"properties":{"invoiceNumber":{"type":"string","nullable":true},"invoiceUrl":{"type":"string","nullable":true},"collectionMethod":{"type":"string","nullable":true,"enum":["charge_automatically","send_invoice",null]},"attempted":{"type":"boolean","nullable":true},"attemptCount":{"type":"number","nullable":true},"nextPaymentAttemptDate":{"type":"string","nullable":true},"dueDate":{"type":"string","nullable":true}},"required":["invoiceNumber","invoiceUrl"]},"createdTime":{"type":"string","nullable":true,"format":"date-time"},"createdUserId":{"type":"string","nullable":true},"lastUpdatedTime":{"type":"string","nullable":true,"format":"date-time"},"lastUpdatedUserId":{"type":"string","nullable":true}},"required":["id","companyId","accountId","name","startDate","amountValue","amountCurrency","passThroughTaxAmount","categoryId","counterparty","usingDateRule","recurrence","overrides","source","externalId","externalSubscriptionId","externalInvoiceId","invoiceDate","serviceStartDate","serviceEndDate","externalData","createdTime","createdUserId","lastUpdatedTime","lastUpdatedUserId"]}}}},"404":{"description":"Planned transaction not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}},"required":["error"]}}}}}},"delete":{"description":"Delete a planned transaction and every occurrence it would have produced, past and future, which changes the cash flow forecast for all of them. To stop a recurring item from a date onward while keeping its history in the forecast, set the recurrence `end` instead.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"plannedTransactionId","in":"path"}],"responses":{"200":{"description":"Planned transaction deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}},"required":["success"]}}}}}}},"/companies/{companyId}/data/recurring-transactions":{"get":{"tags":["Data"],"description":"Detect recurring spend across the whole transaction history and return the series grouped by vendor id. Recurrence is inferred from the transactions themselves, not from anything the user declared: a series needs enough successive intervals at a roughly stable amount before it counts. Use this to find subscriptions a company is paying for, including ones nobody remembers signing up to. Unrelated to planned transactions, which are rules a user entered by hand.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"responses":{"200":{"description":"Recurring transaction series grouped by vendor id","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"vendorId":{"type":"string"},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]},"frequency":{"type":"string","enum":["daily","weekly","monthly","quarterly","half-yearly","yearly"]},"status":{"type":"string","enum":["active","inactive"]},"earliestItem":{"type":"object","properties":{"id":{"type":"string"},"transactionId":{"type":"string"},"categorizationId":{"type":"string"},"date":{"type":"string"},"vendorId":{"type":"string"},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]}},"required":["id","transactionId","categorizationId","date","vendorId","amount"]},"latestItem":{"type":"object","properties":{"id":{"type":"string"},"transactionId":{"type":"string"},"categorizationId":{"type":"string"},"date":{"type":"string"},"vendorId":{"type":"string"},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]}},"required":["id","transactionId","categorizationId","date","vendorId","amount"]},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"transactionId":{"type":"string"},"categorizationId":{"type":"string"},"date":{"type":"string"},"vendorId":{"type":"string"},"amount":{"type":"object","properties":{"value":{"type":"number"},"currency":{"type":"string"}},"required":["value","currency"]}},"required":["id","transactionId","categorizationId","date","vendorId","amount"]}}},"required":["id","vendorId","amount","frequency","status","earliestItem","latestItem","items"]}}}}}}}}},"/companies/{companyId}/data/batch":{"post":{"tags":["Data"],"description":"Run several analytics slices in one round trip. Each entry in `slices` is a descriptor `{ type, _id?, ...params }` where `type` names the slice and the rest are its parameters, the same ones the dedicated `/data/{slice}` endpoint takes. Pass `_id` to correlate each result back to its request. This is the endpoint a dashboard uses to fill many widgets at once; for a single aggregation the dedicated slice endpoint is simpler and typed. `timeZone` (IANA name, default UTC) decides where day and month boundaries fall, which changes what lands in a period. Invalid params in any slice fail the whole batch with 400, and a slice the caller lacks permission for fails it with 403.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"slices":{"type":"array","items":{"nullable":true}},"timeZone":{"type":"string"}},"required":["slices"]}}}},"responses":{"200":{"description":"Batch slice data","content":{"application/json":{"schema":{"nullable":true}}}},"400":{"description":"One or more slices have invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for one of the requested slices","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/mrrAtDate":{"post":{"tags":["Data"],"description":"Use this when the user asks for total MRR (or committed MRR) on a specific date. Requires permissions: subscriptions:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}","description":"Snapshot date (YYYY-MM-DD)."},"timeZone":{"type":"string","description":"IANA time zone. Defaults to BatchContext.timeZone, then UTC."},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType","date"]}}}},"responses":{"200":{"description":"mrrAtDate result","content":{"application/json":{"schema":{"type":"object","properties":{"mrr":{"type":"number"},"customerCount":{"type":"number"}},"required":["mrr","customerCount"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/mrrData":{"post":{"tags":["Data"],"description":"Use this when the user asks how MRR has changed over time, MRR trend, or monthly recurring revenue history. Requires permissions: subscriptions:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"timeZone":{"type":"string","description":"IANA time zone. Defaults to BatchContext.timeZone, then UTC."},"filterCustomerIds":{"type":"array","items":{"type":"string"}},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType"]}}}},"responses":{"200":{"description":"mrrData result","content":{"application/json":{"schema":{"type":"array","items":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"object","properties":{"mrr":{"type":"number"},"customerCount":{"type":"number"}},"required":["mrr","customerCount"]}]},"minItems":2,"maxItems":2}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/monthlyData":{"post":{"tags":["Data"],"description":"Use this when the user asks for month-by-month revenue, churn, new/upgrade/downgrade breakdowns, or movement summaries. Requires permissions: subscriptions:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"timeZone":{"type":"string","description":"IANA time zone. Defaults to BatchContext.timeZone, then UTC."},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType"]}}}},"responses":{"200":{"description":"monthlyData result","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string"},"start":{"type":"number"},"startCount":{"type":"number"},"new":{"type":"number"},"newCount":{"type":"number"},"reactivate":{"type":"number"},"reactivateCount":{"type":"number"},"upgrade":{"type":"number"},"upgradeCount":{"type":"number"},"downgrade":{"type":"number"},"downgradeCount":{"type":"number"},"cancel":{"type":"number"},"cancelCount":{"type":"number"},"currency":{"type":"number"},"currencyCount":{"type":"number"},"end":{"type":"number"},"endCount":{"type":"number"}},"required":["month","start","startCount","new","newCount","reactivate","reactivateCount","upgrade","upgradeCount","downgrade","downgradeCount","cancel","cancelCount","currency","currencyCount","end","endCount"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/customerMrr":{"post":{"tags":["Data"],"description":"Use this when the user asks for MRR broken down per customer, or which customers contribute the most MRR. Requires permissions: subscriptions:read, customers:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"limit":{"type":"integer","minimum":1},"offset":{"type":"integer","minimum":0},"timeZone":{"type":"string","description":"IANA time zone. Defaults to BatchContext.timeZone, then UTC."},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType","date"]}}}},"responses":{"200":{"description":"customerMrr result","content":{"application/json":{"schema":{"type":"object","properties":{"customers":{"type":"array","items":{"type":"object","properties":{"customerId":{"type":"string"},"value":{"type":"number"},"customer":{"type":"object","nullable":true,"properties":{"name":{"type":"string"},"countryCode":{"type":"string","nullable":true},"logoUrl":{"type":"string","nullable":true}},"required":["name","countryCode","logoUrl"]}},"required":["customerId","value","customer"]}},"totalCount":{"type":"number"}},"required":["customers","totalCount"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/mrrChangeEvents":{"post":{"tags":["Data"],"description":"Use this when the user asks for individual MRR change events (new, expansion, contraction, churn) in a period. Requires permissions: subscriptions:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"limit":{"type":"integer","minimum":1},"offset":{"type":"integer","minimum":0},"timeZone":{"type":"string","description":"IANA time zone. Defaults to BatchContext.timeZone, then UTC."},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType","startDate","endDate"]}}}},"responses":{"200":{"description":"mrrChangeEvents result","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"nullable":true}},"totalCount":{"type":"number"}},"required":["events","totalCount"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/mrrByProduct":{"post":{"tags":["Data"],"description":"Use this when the user asks how MRR is distributed across products or plans. Requires permissions: subscriptions:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType","startDate","endDate"]}}}},"responses":{"200":{"description":"mrrByProduct result","content":{"application/json":{"schema":{"nullable":true}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/analytics":{"post":{"tags":["Data"],"description":"Use this when the user asks for summary SaaS metrics (MRR, ARR, NRR, GRR, churn, LTV, CAC, burn rate, runway) over a period. Requires permissions: financials:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"metricNames":{"type":"array","items":{"type":"string","enum":["mrr","arr","revenue","cogs","cac","pd","ga","newCustomers","grossProfit","grossProfitMargin","operatingProfit","operatingProfitMargin","cacPerNewCustomer","mrrPerCustomer","mrrPerNewCustomer","cacPaybackPeriod","churnRate","churnRate12m","lifetimeInMonths","lifetimeValue","netRevenueRetention","grossRevenueRetention","netRevenueRetentionMonthly","grossRevenueRetentionMonthly","mrrGrowthRate","mrrGrowthRateAnnual","ruleOf40","netBurn","grossBurn","runway","burnMultiple"]}},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"timeZone":{"type":"string","description":"IANA time zone. Defaults to BatchContext.timeZone, then UTC."},"smoothingMonths":{"type":"integer","minimum":1,"maximum":12,"default":1},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["metricNames","startDate","endDate","mrrType"]}}}},"responses":{"200":{"description":"analytics result","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"object","properties":{"title":{"type":"string"},"subTitle":{"type":"string"},"format":{"type":"string"},"values":{"type":"object","additionalProperties":{"type":"number","nullable":true}}},"required":["title","subTitle","format","values"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/metricDefinitions":{"post":{"tags":["Data"],"description":"Use this when the user asks how a SaaS metric is defined or calculated (formula, inputs, windows, caps). Static definitions only; use the analytics slice for values. Requires permissions: financials:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"metricNames":{"type":"array","items":{"type":"string","enum":["mrr","arr","revenue","cogs","cac","pd","ga","newCustomers","grossProfit","grossProfitMargin","operatingProfit","operatingProfitMargin","cacPerNewCustomer","mrrPerCustomer","mrrPerNewCustomer","cacPaybackPeriod","churnRate","churnRate12m","lifetimeInMonths","lifetimeValue","netRevenueRetention","grossRevenueRetention","netRevenueRetentionMonthly","grossRevenueRetentionMonthly","mrrGrowthRate","mrrGrowthRateAnnual","ruleOf40","netBurn","grossBurn","runway","burnMultiple"]},"description":"Metrics to return definitions for. Omit for all metrics."},"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."}}}}}},"responses":{"200":{"description":"metricDefinitions result","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"object","properties":{"title":{"type":"string"},"subTitle":{"type":"string"},"format":{"type":"string","enum":["number","percentage","currency","months"]},"category":{"type":"string","enum":["Recurring revenue","Customers & retention","Revenue & expenses","Cash flow"]},"description":{"type":"string"},"formula":{"$ref":"#/components/schemas/MetricFormulaNode"},"notes":{"type":"array","items":{"type":"string"}},"requires":{"type":"string"}},"required":["title","subTitle","format","category","description"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/metricExplanation":{"post":{"tags":["Data"],"description":"Use this when the user asks why a metric has its value or how it was computed for a specific month: returns the formula with every part resolved to its real number, the engine value, and any cap or adjustment that made them differ. Requires permissions: financials:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"metricName":{"type":"string","enum":["mrr","arr","revenue","cogs","cac","pd","ga","newCustomers","grossProfit","grossProfitMargin","operatingProfit","operatingProfitMargin","cacPerNewCustomer","mrrPerCustomer","mrrPerNewCustomer","cacPaybackPeriod","churnRate","churnRate12m","lifetimeInMonths","lifetimeValue","netRevenueRetention","grossRevenueRetention","netRevenueRetentionMonthly","grossRevenueRetentionMonthly","mrrGrowthRate","mrrGrowthRateAnnual","ruleOf40","netBurn","grossBurn","runway","burnMultiple"]},"month":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"timeZone":{"type":"string","description":"IANA time zone. Defaults to BatchContext.timeZone, then UTC."},"smoothingMonths":{"type":"integer","minimum":1,"maximum":12,"default":1},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["metricName","month"]}}}},"responses":{"200":{"description":"metricExplanation result","content":{"application/json":{"schema":{"type":"object","properties":{"metric":{"type":"object","properties":{"metricName":{"type":"string","enum":["mrr","arr","revenue","cogs","cac","pd","ga","newCustomers","grossProfit","grossProfitMargin","operatingProfit","operatingProfitMargin","cacPerNewCustomer","mrrPerCustomer","mrrPerNewCustomer","cacPaybackPeriod","churnRate","churnRate12m","lifetimeInMonths","lifetimeValue","netRevenueRetention","grossRevenueRetention","netRevenueRetentionMonthly","grossRevenueRetentionMonthly","mrrGrowthRate","mrrGrowthRateAnnual","ruleOf40","netBurn","grossBurn","runway","burnMultiple"]},"month":{"type":"string"},"value":{"type":"number","nullable":true},"formulaValue":{"type":"number","nullable":true},"diverges":{"type":"boolean"},"divergence":{"type":"object","properties":{"code":{"type":"string","enum":["runwayCashDepleted","runwayNotBurning","runwayCapped","burnMultipleNotGrowing","burnMultipleMissingArrHistory","burnMultipleIncompleteCashWindow","burnMultipleCashPositive","inProgressMonthAnchored","missingInputTreatedAsZero","engineAdjustment"]},"message":{"type":"string"}},"required":["code","message"]},"annotatedFormula":{"$ref":"#/components/schemas/AnnotatedMetricFormulaNode"},"definition":{"type":"object","properties":{"title":{"type":"string"},"subTitle":{"type":"string"},"format":{"type":"string","enum":["number","percentage","currency","months"]},"category":{"type":"string","enum":["Recurring revenue","Customers & retention","Revenue & expenses","Cash flow"]},"description":{"type":"string"},"formula":{"$ref":"#/components/schemas/MetricFormulaNode"},"notes":{"type":"array","items":{"type":"string"}},"requires":{"type":"string"}},"required":["title","subTitle","format","category","description"]}},"required":["metricName","month","value","diverges","definition"]},"referencedMetrics":{"type":"object","additionalProperties":{"type":"object","properties":{"metricName":{"type":"string","enum":["mrr","arr","revenue","cogs","cac","pd","ga","newCustomers","grossProfit","grossProfitMargin","operatingProfit","operatingProfitMargin","cacPerNewCustomer","mrrPerCustomer","mrrPerNewCustomer","cacPaybackPeriod","churnRate","churnRate12m","lifetimeInMonths","lifetimeValue","netRevenueRetention","grossRevenueRetention","netRevenueRetentionMonthly","grossRevenueRetentionMonthly","mrrGrowthRate","mrrGrowthRateAnnual","ruleOf40","netBurn","grossBurn","runway","burnMultiple"]},"month":{"type":"string"},"value":{"type":"number","nullable":true},"formulaValue":{"type":"number","nullable":true},"diverges":{"type":"boolean"},"divergence":{"type":"object","properties":{"code":{"type":"string","enum":["runwayCashDepleted","runwayNotBurning","runwayCapped","burnMultipleNotGrowing","burnMultipleMissingArrHistory","burnMultipleIncompleteCashWindow","burnMultipleCashPositive","inProgressMonthAnchored","missingInputTreatedAsZero","engineAdjustment"]},"message":{"type":"string"}},"required":["code","message"]},"annotatedFormula":{"$ref":"#/components/schemas/AnnotatedMetricFormulaNode"},"definition":{"type":"object","properties":{"title":{"type":"string"},"subTitle":{"type":"string"},"format":{"type":"string","enum":["number","percentage","currency","months"]},"category":{"type":"string","enum":["Recurring revenue","Customers & retention","Revenue & expenses","Cash flow"]},"description":{"type":"string"},"formula":{"$ref":"#/components/schemas/MetricFormulaNode"},"notes":{"type":"array","items":{"type":"string"}},"requires":{"type":"string"}},"required":["title","subTitle","format","category","description"]}},"required":["metricName","month","value","diverges","definition"]}},"smoothing":{"type":"object","properties":{"months":{"type":"integer"},"value":{"type":"number","nullable":true},"monthValues":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string"},"value":{"type":"number","nullable":true}},"required":["month","value"]}}},"required":["months","value","monthValues"]}},"required":["metric","referencedMetrics"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/revenueExpenses":{"post":{"tags":["Data"],"description":"Use this when the user asks for revenue vs. expenses (P&L summary) for a period. Requires permissions: financials:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"filter":{"type":"object","properties":{"accountIds":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}}},"description":"Common filter shared by P&L slices."}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"revenueExpenses result","content":{"application/json":{"schema":{"type":"object","properties":{"kpis":{"type":"object","properties":{"revenue":{"type":"number"},"revenuePreviousPeriod":{"type":"number"},"revenueChange":{"type":"number"},"expenses":{"type":"number"},"expensesPreviousPeriod":{"type":"number"},"expensesChange":{"type":"number"},"nonOperating":{"type":"number"},"nonOperatingPreviousPeriod":{"type":"number"},"nonOperatingChange":{"type":"number"},"uncategorized":{"type":"number"},"uncategorizedPreviousPeriod":{"type":"number"},"uncategorizedChange":{"type":"number"},"profit":{"type":"number"},"profitPreviousPeriod":{"type":"number"},"profitChange":{"type":"number"},"plainProfit":{"type":"number"},"plainProfitPreviousPeriod":{"type":"number"},"plainProfitChange":{"type":"number"}},"required":["revenue","revenuePreviousPeriod","revenueChange","expenses","expensesPreviousPeriod","expensesChange","nonOperating","nonOperatingPreviousPeriod","nonOperatingChange","uncategorized","uncategorizedPreviousPeriod","uncategorizedChange","profit","profitPreviousPeriod","profitChange","plainProfit","plainProfitPreviousPeriod","plainProfitChange"]},"monthly":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string"},"revenue":{"type":"number"},"cogs":{"type":"number"},"cac":{"type":"number"},"pd":{"type":"number"},"ga":{"type":"number"},"nonOperating":{"type":"number"},"uncategorized":{"type":"number"}},"required":["month","revenue","cogs","cac","pd","ga","nonOperating","uncategorized"]}}},"required":["kpis","monthly"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/revenueExpensesBar":{"post":{"tags":["Data"],"description":"Use this when the user asks for revenue/expenses charted month-over-month as bars. Requires permissions: financials:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"filter":{"type":"object","properties":{"accountIds":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}}},"description":"Common filter shared by P&L slices."}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"revenueExpensesBar result","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"month":{"type":"string"},"revenueRecurring":{"type":"number"},"revenueOneOff":{"type":"number"},"revenueUsage":{"type":"number"},"revenueOther":{"type":"number"},"cogs":{"type":"number"},"cac":{"type":"number"},"pd":{"type":"number"},"ga":{"type":"number"}},"required":["month","revenueRecurring","revenueOneOff","revenueUsage","revenueOther","cogs","cac","pd","ga"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/revenueExpensesByCategory":{"post":{"tags":["Data"],"description":"Use this when the user asks which categories drive revenue or expenses over a period. Requires permissions: financials:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"filter":{"type":"object","properties":{"accountIds":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}}},"description":"Common filter shared by P&L slices."}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"revenueExpensesByCategory result","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"plType":{"type":"string"},"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"value":{"type":"number"}},"required":["id","name","value"]}}},"required":["plType","categories"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/topExpenseVendors":{"post":{"tags":["Data"],"description":"Use this when the user asks for the top expense vendors over a period. Requires permissions: financials:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"filter":{"type":"object","properties":{"accountIds":{"type":"array","items":{"type":"string"}},"categoryIds":{"type":"array","items":{"type":"string"}}},"description":"Common filter shared by P&L slices."},"limit":{"type":"integer","minimum":1}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"topExpenseVendors result","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"vendorId":{"type":"string"},"vendorName":{"type":"string"},"amount":{"type":"number"}},"required":["vendorId","vendorName","amount"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/accountBalanceKpis":{"post":{"tags":["Data"],"description":"Use this when the user asks for cash/bank account balance KPIs (current, start, end, change). Requires permissions: balances:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"accountIds":{"type":"array","items":{"type":"string"}}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"accountBalanceKpis result","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"number"},"totalPreviousPeriod":{"type":"number"},"totalChange":{"type":"number"}},"required":["total","totalPreviousPeriod","totalChange"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/accountBalanceByAccount":{"post":{"tags":["Data"],"description":"Use this when the user asks for current balances broken down per bank or cash account. Requires permissions: balances:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"accountIds":{"type":"array","items":{"type":"string"}}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"accountBalanceByAccount result","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"object","properties":{"accountId":{"type":"string"},"accountName":{"type":"string"},"value":{"type":"number"},"valuePreviousPeriod":{"type":"number"},"valueChange":{"type":"number"}},"required":["accountId","accountName","value","valuePreviousPeriod","valueChange"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/balanceTimeSeries":{"post":{"tags":["Data"],"description":"Use this when the user asks for cash/bank balances charted over time. Requires permissions: balances:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"accountIds":{"type":"array","items":{"type":"string"}}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"balanceTimeSeries result","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/balanceByCashType":{"post":{"tags":["Data"],"description":"Use this when the user asks how balances are split across cash types (operating, reserve, savings, etc.). Requires permissions: balances:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"accountIds":{"type":"array","items":{"type":"string"}}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"balanceByCashType result","content":{"application/json":{"schema":{"type":"object","properties":{"cash":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}},"nonCash":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}}},"required":["cash","nonCash"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/cashFlowTimeSeries":{"post":{"tags":["Data"],"description":"Use this when the user asks for cash inflows and outflows over time. Requires permissions: transactions:read, balances:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"accountIds":{"type":"array","items":{"type":"string"}}},"required":["endDate"]}}}},"responses":{"200":{"description":"cashFlowTimeSeries result","content":{"application/json":{"schema":{"type":"object","properties":{"inflow":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}},"outflow":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}}},"required":["inflow","outflow"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/forecastCashFlowTimeSeries":{"post":{"tags":["Data"],"description":"Run the forecastCashFlowTimeSeries aggregation. Requires permissions: transactions:read, balances:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}","description":"Start of the window (YYYY-MM-DD). Defaults to the batch context start date."},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}","description":"End of the window (YYYY-MM-DD). Typically a future date so the forecast extends past today."},"accountIds":{"type":"array","items":{"type":"string"},"description":"Restrict to these account ids. Omit to include all accounts."},"asOfDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}","description":"Cutover between actual and planned cash flow (YYYY-MM-DD). Dates on or before asOfDate come from actual transactions (inflow/outflow); dates after it come from planned (forecast) occurrences (plannedInflow/plannedOutflow). Defaults to today."}},"required":["endDate"]}}}},"responses":{"200":{"description":"forecastCashFlowTimeSeries result","content":{"application/json":{"schema":{"type":"object","properties":{"inflow":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}},"outflow":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}},"plannedInflow":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}},"plannedOutflow":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"value":{"type":"number"}},"required":["date","value"]}}},"required":["inflow","outflow","plannedInflow","plannedOutflow"],"description":"Daily cash inflows and outflows, split into actual (inflow/outflow, dates on or before asOfDate) and planned (plannedInflow/plannedOutflow, dates after asOfDate). Inflows are positive; outflows are negative (money leaving the accounts). Transfers between own accounts are excluded from the actual series. Sum the values per calendar month to reproduce the \"Cash inflows and outflows\" chart."}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/cashFlowByCategory":{"post":{"tags":["Data"],"description":"Use this when the user asks which categories drive cash inflows or outflows in a period. Requires permissions: transactions:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"cashFlowByCategory result","content":{"application/json":{"schema":{"type":"object","properties":{"categories":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"inflow":{"type":"object","additionalProperties":{"type":"number"}},"outflow":{"type":"object","additionalProperties":{"type":"number"}}},"required":["id","name","inflow","outflow"]}},"unassignedInflow":{"type":"object","additionalProperties":{"type":"number"}},"unassignedOutflow":{"type":"object","additionalProperties":{"type":"number"}},"currencyChangeEffect":{"type":"object","additionalProperties":{"type":"number"}}},"required":["categories","unassignedInflow","unassignedOutflow","currencyChangeEffect"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/profitLossData":{"post":{"tags":["Data"],"description":"Use this when the user asks for a P&L statement (revenue, costs, gross/operating/net profit) for a period. Requires permissions: financials:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"}},"required":["startDate","endDate"]}}}},"responses":{"200":{"description":"profitLossData result","content":{"application/json":{"schema":{"type":"object","properties":{"plTypes":{"type":"object","additionalProperties":{"type":"object","properties":{"categories":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"type":"number"}}}},"required":["categories"]}},"currencyChangeEffect":{"type":"object","additionalProperties":{"type":"number"}}},"required":["plTypes","currencyChangeEffect"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/periodDetails":{"post":{"tags":["Data"],"description":"Use this when the user asks for the detailed breakdown of a single period (transactions, categorizations, totals). Requires permissions: transactions:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"dateDefinition":{"type":"string","enum":["distributed","cash"]},"splitBy":{"type":"string","enum":["account","counterparty"]},"filter":{"type":"object","additionalProperties":{"nullable":true}}},"required":["startDate","endDate","dateDefinition","splitBy"]}}}},"responses":{"200":{"description":"periodDetails result","content":{"application/json":{"schema":{"type":"object","properties":{"amountBySplitId":{"type":"object","additionalProperties":{"type":"number"}},"total":{"type":"number"},"namesBySplitId":{"type":"object","additionalProperties":{"type":"string"}},"invoicedAmountBySplitId":{"type":"object","additionalProperties":{"type":"number"}},"invoicedTotal":{"type":"number"},"invoicedDueDateRange":{"type":"object","properties":{"min":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"max":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"}},"required":["min","max"]}},"required":["amountBySplitId","total"]}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/cohorts":{"post":{"tags":["Data"],"description":"Use this when the user asks about customer retention or revenue retention by cohort. Requires permissions: subscriptions:read, customers:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType"]}}}},"responses":{"200":{"description":"cohorts result","content":{"application/json":{"schema":{"nullable":true}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/customerMonthlyData":{"post":{"tags":["Data"],"description":"Use this when the user asks for one customer's month-by-month metrics or activity. For per-customer MRR in a month, request dataType \"end\" (MRR at month end). Requires permissions: subscriptions:read, customers:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"dataType":{"type":"string","enum":["start","new","reactivate","upgrade","downgrade","cancel","currency","end"],"description":"Single data type to fetch. \"start\" and \"end\" are the MRR at the period start/end month; the change types (new, reactivate, upgrade, downgrade, cancel, currency) are summed across the period."},"dataTypes":{"type":"array","items":{"type":"string","enum":["start","new","reactivate","upgrade","downgrade","cancel","currency","end"]},"description":"Multiple data types; per-customer values are summed across the requested types. Takes precedence over dataType."},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType","startDate","endDate"]}}}},"responses":{"200":{"description":"customerMonthlyData result","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"customerId":{"type":"string"},"value":{"type":"number"},"customer":{"type":"object","nullable":true,"properties":{"name":{"type":"string"},"externalIds":{"nullable":true}},"required":["name"]}},"required":["customerId","value"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/customerChanges":{"post":{"tags":["Data"],"description":"Use this when the user asks which customers changed (new/upgraded/downgraded/churned) in a period. Requires permissions: subscriptions:read, customers:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"startDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"endDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}"},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["mrrType","startDate","endDate"]}}}},"responses":{"200":{"description":"customerChanges result","content":{"application/json":{"schema":{"type":"array","items":{"nullable":true}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/data/retentionCohortDetail":{"post":{"tags":["Data"],"description":"Use this when the user asks for the customer-level detail behind a specific retention cohort cell. Requires permissions: subscriptions:read, customers:read.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"month":{"type":"string","pattern":"^\\d{4}-\\d{2}$"},"horizon":{"type":"string","enum":["monthly","annual"]},"mrrType":{"type":"string","enum":["cmrr","mrr"],"description":"MRR variant. cmrr = committed MRR (CMRR), mrr = recognised MRR."},"timeZone":{"type":"string","description":"IANA time zone. Defaults to BatchContext.timeZone, then UTC."},"productIds":{"type":"array","nullable":true,"items":{"type":"string"},"default":null,"description":"Restrict subscription metrics to these product ids. Omit or null for all products. Values are unioned; a customer on several selected products counts once."}},"required":["month","mrrType"]}}}},"responses":{"200":{"description":"retentionCohortDetail result","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"customerId":{"type":"string"},"customerName":{"type":"string","nullable":true},"mrrStart":{"type":"number"},"mrrEnd":{"type":"number"}},"required":["customerId","customerName","mrrStart","mrrEnd"]}}}}},"400":{"description":"Invalid params","content":{"application/json":{"schema":{"nullable":true}}}},"403":{"description":"Caller lacks permission for this slice","content":{"application/json":{"schema":{"nullable":true}}}}}}},"/companies/{companyId}/search":{"get":{"description":"Free-text search across a company, grouped by entity type. Use it when you know a name but not an id: one call matches customers, vendors, and accounts on name, and transactions on counterparty or description. This backs the command palette, so each category returns at most 5 hits. Categories the caller has no read permission for come back empty rather than 403, which keeps the search box usable on partial-permission grants. When you need a full, filtered, paginated list of one entity type, use that resource's own endpoint.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"companyId","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"q","in":"query"}],"responses":{"200":{"description":"Unified command-palette search results for the current company","content":{"application/json":{"schema":{"type":"object","properties":{"customers":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["customer"]},"id":{"type":"string"},"name":{"type":"string"},"logo":{"type":"boolean"}},"required":["type","id","name","logo"]}},"vendors":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["vendor"]},"id":{"type":"string"},"name":{"type":"string"},"logo":{"type":"boolean"}},"required":["type","id","name","logo"]}},"accounts":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["account"]},"id":{"type":"string"},"name":{"type":"string"},"accountType":{"type":"string","enum":["bank","cash","creditCard","loan","other"]}},"required":["type","id","name","accountType"]}},"transactions":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["transaction"]},"id":{"type":"string"},"fromTo":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"date":{"type":"string"},"amountValue":{"type":"number"},"amountCurrency":{"type":"string"},"accountId":{"type":"string"},"accountName":{"type":"string"}},"required":["type","id","fromTo","description","date","amountValue","amountCurrency","accountId","accountName"]}}},"required":["customers","vendors","accounts","transactions"]}}}}}}}}}