{"openapi":"3.1.0","info":{"title":"Roiva read API","version":"1.0.0","description":"Read an account's AI ROI figures. Every endpoint reads: nothing here records a cost, approves value or moves an initiative.\n\nAmounts are integer cents with the currency beside them, never a formatted string. Version 1 does not break — fields and endpoints are added, and removing or renaming one would be version 2.","contact":{"name":"Roiva","url":"https://roiva.ai"}},"servers":[{"url":"https://roiva.ai/api/v1"}],"security":[{"bearerAuth":[]},{"basicAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A personal access token, issued under Organization Settings → API Access. It acts as the user who issued it: remove them from the account and it stops working. A reporting token, issued under Organization Settings → Reporting Tables, reads only /reporting."},"basicAuth":{"type":"http","scheme":"basic","description":"The same token as the password, with any user name. For Power BI, whose Web connector keeps a Basic credential in its own encrypted store."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["status","message"],"properties":{"status":{"type":"integer","description":"The HTTP status, repeated so a body read alone still says it."},"message":{"type":"string","description":"What went wrong, in a sentence."}}}}}}},"paths":{"/roi":{"get":{"operationId":"getRoi","summary":"The ROI figures for a portfolio or one initiative","description":"Approved cash value and freed capacity, kept apart, with total cost, net cash value and cash ROI, over a reporting window. Freed capacity is in neither the net value nor the ROI. The same computation the ROI dashboard and the Board Brief read. Sample initiatives are left out unless include_sample is true, as the Board Brief leaves them out.","parameters":[{"name":"window","in":"query","required":false,"description":"The reporting window. Defaults to all. An unrecognized value is refused rather than answered for another period.","schema":{"type":"string","enum":["1yr","3yr","5yr","all"]}},{"name":"initiative_id","in":"query","required":false,"description":"Narrow the figures to one initiative in this account. An id this account cannot see answers 404.","schema":{"type":"integer"}},{"name":"include_sample","in":"query","required":false,"description":"Include provisioned demo initiatives, which are left out by default. A warehouse that ingests them reports figures nobody spent.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["scope","period","currency","cash_value_cents","freed_capacity_cents","realized_capacity_cents","total_cost_cents","net_value_cents","cost"],"properties":{"scope":{"type":"object","description":"What these figures cover.","required":["type"],"properties":{"type":{"type":"string","enum":["portfolio","initiative"],"description":"Whether these figures cover the whole portfolio or one initiative."},"id":{"type":"integer","description":"The initiative's id, when the scope is an initiative."},"title":{"type":"string","description":"The initiative's title, when the scope is an initiative."}}},"period":{"type":"object","description":"The stretch of time they cover.","required":["window","label"],"properties":{"window":{"type":"string","description":"The reporting window these figures cover."},"label":{"type":"string","description":"How the app names that window to a reader."},"from":{"type":"string","format":"date","description":"First day covered. Absent for all time, which has no first day."},"through":{"type":"string","format":"date","description":"Last day covered: today, in UTC, for a trailing window. A trailing window's dates move every day, so store them with the figures."}}},"currency":{"type":"string","description":"ISO 4217 code every amount below is in."},"cash_value_cents":{"type":"integer","description":"Approved value that shows in the books, in cents: the margin on revenue earned, costs that fell, losses and spend that didn't happen, and freed capacity a person showed became cash. Unapproved entries do not count."},"freed_capacity_cents":{"type":"integer","description":"Approved hours and output freed, priced at what they cost, in cents. Not cash, and in no other figure here: report it beside cash_value_cents and never add the two."},"realized_capacity_cents":{"type":"integer","description":"The part of the freed capacity a person showed became cash, in cents. It is already in cash_value_cents and already out of freed_capacity_cents."},"total_cost_cents":{"type":"integer","description":"Everything the initiatives in scope have cost, in cents."},"net_value_cents":{"type":"integer","description":"Cash value minus total cost, in cents. Freed capacity is not in it."},"roi":{"type":"number","nullable":true,"description":"Cash ROI: net_value_cents divided by total cost. Null when there is no cost to divide by, which is not the same as zero."},"value_left_out":{"type":"array","description":"Approved value in scope in another currency with no dollar figure, so in no figure here: one row a currency, in that currency's cents. Most is waiting for the ECB to publish through its period and converts then; value in a currency the ECB doesn't publish, with no period, or approved in a month locked before its rates were in doesn't.","items":{"type":"object","required":["currency","amount_cents","entries"],"properties":{"currency":{"type":"string","description":"ISO 4217 code amount_cents is in."},"amount_cents":{"type":"integer","description":"What is left out, in cents of that currency."},"entries":{"type":"integer","description":"How many entries that is."}}}},"cost_left_out":{"type":"array","description":"Cost in scope with no dollar figure, so in no figure here: one row a currency, in that currency's cents. A cost in a currency the ECB doesn't publish, or locked in its month before it was converted.","items":{"type":"object","required":["currency","amount_cents","entries"],"properties":{"currency":{"type":"string","description":"ISO 4217 code amount_cents is in."},"amount_cents":{"type":"integer","description":"What is left out, in cents of that currency."},"entries":{"type":"integer","description":"How many entries that is."}}}},"provisional_cost_cents":{"type":"integer","description":"The part of total_cost_cents in another currency counted at the ECB's latest rate, because its period's rates weren't all published yet. It moves to the period's average once they are."},"cost":{"type":"object","description":"total_cost_cents split into its one-time and running halves.","required":["capex_cents","opex_cents"],"properties":{"capex_cents":{"type":"integer","description":"The one-time part of total cost, in cents."},"opex_cents":{"type":"integer","description":"The running part of total cost, in cents."}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/initiatives":{"get":{"operationId":"listInitiatives","summary":"Every initiative in the account","description":"Ordered oldest change first, so a caller can walk forward from where they stopped. Sample initiatives are left out unless asked for.","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, up to 500.","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from the previous page. Opaque: its shape is not part of the contract.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this moment, which is what makes a repeat pull cheap. At or after, not after, so a caller passing back the last updated_at they saw cannot miss a row saved in the same millisecond.","schema":{"type":"string","format":"date-time"}},{"name":"include_sample","in":"query","required":false,"description":"Include provisioned demo initiatives, which are left out by default. A warehouse that ingests them reports figures nobody spent.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["data","page"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","title","stage","is_sample","created_at","updated_at"],"properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"hypothesis":{"type":"string","nullable":true},"stage":{"type":"string","enum":["identified","reviewing","implementing","live","paused","completed","rejected","dismissed"],"description":"Where it is in its lifecycle."},"is_sample":{"type":"boolean","description":"Provisioned demo data. Left out of lists unless include_sample=true, because a warehouse that ingests it reports figures nobody spent."},"category":{"type":"string","nullable":true},"priority":{"type":"integer","nullable":true,"description":"Lower is higher priority."},"source":{"type":"string","nullable":true,"description":"How the initiative came to exist."},"business_unit":{"type":"string","nullable":true},"department":{"type":"string","nullable":true},"gl_dimension_value":{"type":"string","nullable":true},"owner":{"type":"object","nullable":true,"description":"Who, inside this account. Names, never email addresses.","properties":{"id":{"type":"integer"},"name":{"type":"string","nullable":true,"description":"Empty when they have given no name. Never an email address."}}},"confidence_score":{"type":"number","nullable":true},"roi_time_horizon_years":{"type":"integer","nullable":true},"currency":{"type":"string","nullable":true},"expected_capex_cents":{"type":"integer","nullable":true,"description":"Amount in cents, in the currency beside it."},"expected_opex_cents":{"type":"integer","nullable":true,"description":"Amount in cents, in the currency beside it."},"expected_cost_savings_cents":{"type":"integer","nullable":true,"description":"Planned cash a falling cost saves per value period."},"expected_capacity_cents":{"type":"integer","nullable":true,"description":"Planned freed capacity per value period: hours priced at what they cost. Not cash, and in no planned ROI."},"expected_revenue_uplift_cents":{"type":"integer","nullable":true,"description":"Planned revenue per value period, top line, as the business case states it. Planned value counts it at the account's contribution margin."},"started_at":{"type":"string","format":"date-time","nullable":true},"went_live_at":{"type":"string","format":"date-time","nullable":true},"approved_at":{"type":"string","format":"date-time","nullable":true},"closed_at":{"type":"string","format":"date-time","nullable":true},"submitted_for_review_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time","description":"When the row was first written."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. This is what the list is ordered by, what a cursor points into, and what updated_since compares against."}}}},"page":{"type":"object","required":["has_more"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page. Null on the last one."},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/cost_entries":{"get":{"operationId":"listCostEntries","summary":"Every cost entry in the account","description":"What the account has spent, one row per entry, whoever or whatever recorded it. Ordered oldest change first.","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, up to 500.","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from the previous page. Opaque: its shape is not part of the contract.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this moment, which is what makes a repeat pull cheap. At or after, not after, so a caller passing back the last updated_at they saw cannot miss a row saved in the same millisecond.","schema":{"type":"string","format":"date-time"}},{"name":"initiative_id","in":"query","required":false,"description":"Only rows belonging to one initiative in this account.","schema":{"type":"integer"}},{"name":"period_from","in":"query","required":false,"description":"The start of the window. Entries are matched on where their period begins, which is how Roiva's own reports slice a period: an entry belongs to the period it starts in, so a twelve-month entry beginning in January is in Q1 and in no other quarter. Asking for a quarter therefore returns the rows the dashboard counted for it. Either bound may be given on its own.","schema":{"type":"string","format":"date"}},{"name":"period_to","in":"query","required":false,"description":"The end of the window, matched the same way — see period_from.","schema":{"type":"string","format":"date"}},{"name":"include_sample","in":"query","required":false,"description":"Include provisioned demo initiatives, which are left out by default. A warehouse that ingests them reports figures nobody spent.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["data","page"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","initiative_id","amount_cents","cost_category","created_at","updated_at"],"properties":{"id":{"type":"integer"},"initiative_id":{"type":"integer"},"amount_cents":{"type":"integer","nullable":true,"description":"Amount in cents, in the currency beside it."},"currency":{"type":"string","nullable":true},"usd_amount_cents":{"type":"integer","nullable":true,"description":"The amount in US dollars, in cents: amount_cents itself when currency is USD, converted at usd_rate otherwise, or at the rate for the day it was booked when it has no period. Null, and in no total /roi gives, for a currency the ECB doesn't publish or a cost locked in its month before it was converted."},"usd_rate":{"type":"number","nullable":true,"description":"US dollars per unit of currency: the average of the European Central Bank's daily euro reference rates from usd_rate_from to usd_rate_through, crossed through the euro. Null for a dollar entry."},"usd_rate_from":{"type":"string","format":"date","nullable":true,"description":"The first ECB day usd_rate averages."},"usd_rate_through":{"type":"string","format":"date","nullable":true,"description":"The last ECB day usd_rate averages."},"usd_rate_provisional":{"type":"boolean","description":"Whether usd_rate is the ECB's latest rate rather than its period's average, because the period's rates weren't all published yet. It moves to the average once they are, unless its month was locked before then, and updated_at changes when it does."},"cost_type":{"type":"string","nullable":true,"description":"Software, tokens, implementation, infrastructure and so on. \"adjustment\" prices a linked vendor's usage by the books, and can be negative."},"cost_category":{"type":"string","enum":["capex","opex"],"description":"Whether it is one-time or running. Follows from the type unless somebody changed it."},"period_start":{"type":"string","format":"date","nullable":true},"period_end":{"type":"string","format":"date","nullable":true},"vendor_id":{"type":"integer","nullable":true,"description":"The vendor this is spend with, and the field to group spend by — it is what Roiva's own Vendors page groups on. Resolved from vendor_name when the entry is written, so the two usually agree; they come apart when two vendors are merged, because that repoints vendor_id on the existing entries and leaves their names alone. One vendor's rows can then carry several names, and grouping on the name splits its spend. Null where no name was given, which Roiva reports apart rather than against a vendor."},"vendor_name":{"type":"string","nullable":true,"description":"The name as it was recorded, which is free text: an abbreviation, a product name, or a spelling the vendor has since been merged out of."},"description":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"is_estimated":{"type":"boolean"},"locked":{"type":"boolean","description":"In a locked accounting period, so nothing changes or deletes it, synced or typed, until the month is reopened."},"source":{"type":"object","description":"Where the record came from: typed in, recorded through the Roiva Assistant, imported, calculated, or brought in by a connection.","properties":{"type":{"type":"string","nullable":true,"description":"manual (typed in), assistant (recorded through the Roiva Assistant), csv (imported from a file), calculated (by a formula), integration (brought in by a connection) or system (written by Roiva). Not every kind of record takes every one."},"ref":{"type":"string","nullable":true,"description":"The id the originating system knows it by."},"connection_id":{"type":"integer","nullable":true}}},"created_at":{"type":"string","format":"date-time","description":"When the row was first written."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. This is what the list is ordered by, what a cursor points into, and what updated_since compares against."}}}},"page":{"type":"object","required":["has_more"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page. Null on the last one."},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/value_entries":{"get":{"operationId":"listValueEntries","summary":"Every value entry in the account","description":"What the account's initiatives have returned. Only entries whose status is \"approved\" are reported value; of those, cash counts toward ROI and freed capacity is reported beside it, never added. See counts_toward_roi on each row.","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, up to 500.","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from the previous page. Opaque: its shape is not part of the contract.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this moment, which is what makes a repeat pull cheap. At or after, not after, so a caller passing back the last updated_at they saw cannot miss a row saved in the same millisecond.","schema":{"type":"string","format":"date-time"}},{"name":"initiative_id","in":"query","required":false,"description":"Only rows belonging to one initiative in this account.","schema":{"type":"integer"}},{"name":"status","in":"query","required":false,"description":"Only entries with this status. Only \"approved\" is reported value.","schema":{"type":"string","enum":["draft","calculated","reviewed","approved","disputed","archived"]}},{"name":"period_from","in":"query","required":false,"description":"The start of the window. Entries are matched on where their period begins, which is how Roiva's own reports slice a period: an entry belongs to the period it starts in, so a twelve-month entry beginning in January is in Q1 and in no other quarter. Asking for a quarter therefore returns the rows the dashboard counted for it. Either bound may be given on its own.","schema":{"type":"string","format":"date"}},{"name":"period_to","in":"query","required":false,"description":"The end of the window, matched the same way — see period_from.","schema":{"type":"string","format":"date"}},{"name":"include_sample","in":"query","required":false,"description":"Include provisioned demo initiatives, which are left out by default. A warehouse that ingests them reports figures nobody spent.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["data","page"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","initiative_id","amount_cents","status","counts_toward_roi","created_at","updated_at"],"properties":{"id":{"type":"integer"},"initiative_id":{"type":"integer"},"amount_cents":{"type":"integer","nullable":true,"description":"Amount in cents, in the currency beside it."},"currency":{"type":"string","nullable":true},"usd_amount_cents":{"type":"integer","nullable":true,"description":"The amount in US dollars, in cents: amount_cents itself when currency is USD, converted at usd_rate otherwise. Null while it has no rate, and then it is in no total /roi gives."},"usd_rate":{"type":"number","nullable":true,"description":"US dollars per unit of currency: the average of the European Central Bank's daily euro reference rates from usd_rate_from to usd_rate_through, crossed through the euro. Null for a dollar entry."},"usd_rate_from":{"type":"string","format":"date","nullable":true,"description":"The first ECB day usd_rate averages."},"usd_rate_through":{"type":"string","format":"date","nullable":true,"description":"The last ECB day usd_rate averages."},"status":{"type":"string","enum":["draft","calculated","reviewed","approved","disputed","archived"],"description":"Only \"approved\" is reported value. The others are on their way there or were sent back, and summing every row gives a figure that disagrees with /roi."},"counts_toward_roi":{"type":"boolean","description":"Whether this entry is in the cash ROI /roi reports. True exactly when status is \"approved\" and entry_type is cash (revenue_gain, cost_savings, cost_avoidance, capacity_realized). Approved freed capacity (capacity) is reported beside ROI, never in it, so it is false. So is an entry in another currency with no dollar figure yet: /roi adds dollars, and leaves it out until it has one."},"entry_type":{"type":"string","nullable":true,"description":"The kind of value: revenue_gain (cash: the margin on revenue earned), cost_savings (cash: a bill that fell), cost_avoidance (cash: a cost that didn't happen), capacity_realized (cash: freed capacity shown to have become cash), capacity (freed capacity: hours and output freed up, priced at what they cost, not cash). Cash and freed capacity are two figures, never added together."},"period_start":{"type":"string","format":"date","nullable":true},"period_end":{"type":"string","format":"date","nullable":true},"is_estimated":{"type":"boolean"},"locked":{"type":"boolean","description":"Approved, in a locked accounting period: Roiva refuses to edit, send back or delete it until the month is reopened. False for an entry in that month not yet approved: it can still be recalculated, and can't be approved until the reopening."},"basis":{"type":"string","enum":["measured","reconstructed","estimated","none"],"description":"What it was compared with: a baseline measured before go-live (measured), one measured after go-live on work done without the AI (reconstructed), a benchmark, a judgment or a figure marked an estimate (estimated), or no baseline (none)."},"confidence_score":{"type":"number","nullable":true},"quality_score":{"type":"number","nullable":true},"notes":{"type":"string","nullable":true,"description":"What a person wrote about this figure in their own words — for a saving, typically what changed: their team's doing, or a vendor's pricing."},"provenance":{"type":"string","nullable":true,"description":"Where the number came from, in one phrase: synced from a connection, calculated from a formula, recorded through the Roiva Assistant, or entered by hand."},"formula_id":{"type":"integer","nullable":true,"description":"The formula that calculated it, for a calculated entry."},"formula_key":{"type":"string","nullable":true,"description":"That formula's key, stable across its versions."},"formula_version":{"type":"integer","nullable":true,"description":"The version of the formula the figure was computed with, recorded when it was evaluated. A formula can be revised afterwards; this stays the version that produced this figure."},"source":{"type":"object","description":"Where the record came from: typed in, recorded through the Roiva Assistant, imported, calculated, or brought in by a connection.","properties":{"type":{"type":"string","nullable":true,"description":"manual (typed in), assistant (recorded through the Roiva Assistant), csv (imported from a file), calculated (by a formula), integration (brought in by a connection) or system (written by Roiva). Not every kind of record takes every one."},"ref":{"type":"string","nullable":true,"description":"The id the originating system knows it by."},"connection_id":{"type":"integer","nullable":true}}},"approved_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time","description":"When the row was first written."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. This is what the list is ordered by, what a cursor points into, and what updated_since compares against."}}}},"page":{"type":"object","required":["has_more"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page. Null on the last one."},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/metrics":{"get":{"operationId":"listMetrics","summary":"Every metric this account can read","description":"The metric definitions behind the figures: what each one measures, its type and its unit.","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, up to 500.","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from the previous page. Opaque: its shape is not part of the contract.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this moment, which is what makes a repeat pull cheap. At or after, not after, so a caller passing back the last updated_at they saw cannot miss a row saved in the same millisecond.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["data","page"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","metric_key","name","created_at","updated_at"],"properties":{"id":{"type":"integer"},"metric_key":{"type":"string","description":"The canonical key, as the metric registry spells it."},"name":{"type":"string"},"description":{"type":"string","nullable":true},"metric_type":{"type":"string","nullable":true,"description":"count, duration, percentage, currency and so on."},"unit":{"type":"string","nullable":true},"currency_metric":{"type":"boolean","description":"Whether its readings are money."},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","description":"When the row was first written."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. This is what the list is ordered by, what a cursor points into, and what updated_since compares against."}}}},"page":{"type":"object","required":["has_more"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page. Null on the last one."},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/observations":{"get":{"operationId":"listObservations","summary":"Every metric reading in the account","description":"The readings and baselines the value formulas run on. Ordered oldest change first.","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, up to 500.","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from the previous page. Opaque: its shape is not part of the contract.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this moment, which is what makes a repeat pull cheap. At or after, not after, so a caller passing back the last updated_at they saw cannot miss a row saved in the same millisecond.","schema":{"type":"string","format":"date-time"}},{"name":"initiative_id","in":"query","required":false,"description":"Only rows belonging to one initiative in this account.","schema":{"type":"integer"}},{"name":"metric_key","in":"query","required":false,"description":"Only readings of one metric, by its canonical key.","schema":{"type":"string"}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["data","page"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","value","created_at","updated_at"],"properties":{"id":{"type":"integer"},"metric_id":{"type":"integer","nullable":true},"metric_key":{"type":"string","nullable":true},"initiative_id":{"type":"integer","nullable":true},"value":{"type":"number","nullable":true,"description":"The reading itself, in the metric's unit; money in currency."},"currency":{"type":"string","nullable":true,"description":"ISO 4217 code a money reading's value is in: a CRM's deals all in euros are in EUR. Empty for a reading that isn't money."},"role":{"type":"string","nullable":true,"description":"Whether it is a baseline or a reading."},"baseline_method":{"type":"string","nullable":true,"enum":["history","time_study","without_ai_sample","benchmark","estimate"],"description":"How a baseline was measured, when it is one. history and time_study are measured; without_ai_sample is reconstructed after go-live; benchmark and estimate are estimates."},"observed_at":{"type":"string","format":"date-time","nullable":true},"period_start":{"type":"string","format":"date","nullable":true},"period_end":{"type":"string","format":"date","nullable":true},"is_estimated":{"type":"boolean"},"sample_size":{"type":"integer","nullable":true},"quality_score":{"type":"number","nullable":true},"source":{"type":"object","description":"Where the record came from: typed in, recorded through the Roiva Assistant, imported, calculated, or brought in by a connection.","properties":{"type":{"type":"string","nullable":true,"description":"manual (typed in), assistant (recorded through the Roiva Assistant), csv (imported from a file), calculated (by a formula), integration (brought in by a connection) or system (written by Roiva). Not every kind of record takes every one."},"ref":{"type":"string","nullable":true,"description":"The id the originating system knows it by."},"connection_id":{"type":"integer","nullable":true}}},"created_at":{"type":"string","format":"date-time","description":"When the row was first written."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. This is what the list is ordered by, what a cursor points into, and what updated_since compares against."}}}},"page":{"type":"object","required":["has_more"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page. Null on the last one."},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/budgets":{"get":{"operationId":"listBudgets","summary":"Each initiative's approved budget against its actual spend","description":"The signed-off budget, not the business case the initiatives list carries. One row per initiative, including initiatives with no budget — see `budgeted`, which says which rows belong in a variance total. Ordered oldest change first, by the initiative.","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, up to 500.","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from the previous page. Opaque: its shape is not part of the contract.","schema":{"type":"string"}},{"name":"include_sample","in":"query","required":false,"description":"Include provisioned demo initiatives, which are left out by default. A warehouse that ingests them reports figures nobody spent.","schema":{"type":"boolean","default":false}},{"name":"initiative_id","in":"query","required":false,"description":"Only rows belonging to one initiative in this account.","schema":{"type":"integer"}},{"name":"budgeted","in":"query","required":false,"description":"Only initiatives that have an approved budget. Applied after the comparison runs, since it is not a column, so a page can come back shorter than limit while there are still more pages.","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["data","page"],"properties":{"data":{"type":"array","items":{"type":"object","required":["initiative_id","budgeted","status","over_parts","as_of","budget_to_date_cents","actual_to_date_cents","variance_cents"],"properties":{"initiative_id":{"type":"integer"},"budgeted":{"type":"boolean","description":"Whether an approved budget exists at all. False means the figures below compare spend against nothing, so these rows belong out of any variance total — which is what Roiva's own totals do."},"status":{"type":"string","enum":["no_budget","scheduled","within","over"],"description":"\"over\" when either half is over, because an unspent CapEx envelope early in a build must not hide an OpEx run rate that is already over. \"scheduled\" means a budget exists but has not started."},"over_parts":{"type":"array","items":{"type":"string","enum":["capex","opex"]},"description":"Which half is over, when status is \"over\"."},"currency":{"type":"string","nullable":true},"as_of":{"type":"string","format":"date","description":"The day the comparison was made — today."},"capex_budget_cents":{"type":"integer","description":"The current version's envelope."},"capex_actual_cents":{"type":"integer","description":"CapEx spent since the budget began. Zero when budgeted is false: there is no budget window to measure over, so this is not the initiative's total spend. Read /api/v1/cost_entries for that."},"capex_remaining_cents":{"type":"integer","description":"Negative when the envelope is exceeded."},"opex_budget_to_date_cents":{"type":"integer","description":"Each month's rate summed from the month the budget began through this one, at whichever version covered each month. A revision changes the months after it and never restates the ones before."},"opex_actual_cents":{"type":"integer","description":"OpEx spent since the budget began. Zero when budgeted is false: there is no budget window to measure over, so this is not the initiative's total spend. Read /api/v1/cost_entries for that."},"budget_monthly_run_rate_cents":{"type":"integer","description":"The current version's rate, as a monthly figure."},"actual_monthly_run_rate_cents":{"type":"integer","nullable":true,"description":"The average over the last 3 complete months the budget covered. Null until one complete month has passed."},"budget_to_date_cents":{"type":"integer","description":"CapEx envelope plus OpEx to date."},"actual_to_date_cents":{"type":"integer","description":"Zero when budgeted is false: there is no budget window to measure over, so this is not the initiative's total spend. Read /api/v1/cost_entries for that."},"variance_cents":{"type":"integer","description":"budget_to_date_cents minus actual_to_date_cents. Positive is under budget."},"used_ratio":{"type":"number","nullable":true,"description":"Actual over budget to date. Null when there is no budget to date to divide by."},"spent_before_budget_cents":{"type":"integer","description":"Spend dated before the month the budget began. Reported apart rather than charged against an envelope that did not exist yet, so it is in neither figure above."},"opex_budget_fiscal_year_cents":{"type":"integer","description":"The whole fiscal year's OpEx budget."},"opex_budget_fiscal_ytd_cents":{"type":"integer","description":"The part of it through as_of."},"opex_actual_fiscal_ytd_cents":{"type":"integer"},"budget_started_on":{"type":"string","format":"date","nullable":true,"description":"The month the first approved version took effect."},"revised":{"type":"boolean","description":"Whether the budget has been changed since it was first approved."},"version_count":{"type":"integer"},"revision_pending":{"type":"boolean","description":"A revision is waiting for the account Owner, because it would put the budget above the account's approval threshold. The figures here are still the approved ones."},"current_version":{"type":"object","nullable":true,"description":"The approved version in effect, or null when there is no budget yet or every version starts in a later month.","properties":{"id":{"type":"integer"},"effective_from":{"type":"string","format":"date","description":"The figures apply from the whole month containing this date, not from the day — so a version effective on the 28th covers that month in full, and budget_started_on is the first of it. There is no day proration."},"ends_on":{"type":"string","format":"date","nullable":true,"description":"The last month covered, again whole-month."},"capex_cents":{"type":"integer","description":"A one-off total, not accrued."},"opex_cents":{"type":"integer","description":"A rate, per opex_cadence."},"opex_cadence":{"type":"string","enum":["monthly","quarterly","annually"]},"source":{"type":"string","enum":["approval","revision","manual"],"description":"Whether it came from the initiative's approval, a later revision, or was set by hand."},"reason":{"type":"string","nullable":true,"description":"Why it was set, in the words of whoever set it."},"set_by":{"type":"object","nullable":true,"description":"Who, inside this account. Names, never email addresses.","properties":{"id":{"type":"integer"},"name":{"type":"string","nullable":true,"description":"Empty when they have given no name. Never an email address."}}}}}}}},"page":{"type":"object","required":["has_more"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page. Null on the last one."},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/decisions":{"get":{"operationId":"listDecisions","summary":"Every portfolio decision in the account","description":"Continue, scale, fix, pause or retire, with the rationale and the figures as they stood when it was decided. Immutable once written, so a row read today says what was known then. Ordered oldest change first.","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, up to 500.","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from the previous page. Opaque: its shape is not part of the contract.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this moment, which is what makes a repeat pull cheap. At or after, not after, so a caller passing back the last updated_at they saw cannot miss a row saved in the same millisecond.","schema":{"type":"string","format":"date-time"}},{"name":"initiative_id","in":"query","required":false,"description":"Only rows belonging to one initiative in this account.","schema":{"type":"integer"}},{"name":"outcome","in":"query","required":false,"description":"Only decisions with this outcome.","schema":{"type":"string","enum":["continue","scale","fix","pause","retire"]}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["data","page"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","subject_title","outcome","outcome_label","rationale","stage_before","stage_after","stage_changed","decided_at","created_at","updated_at"],"properties":{"id":{"type":"integer"},"initiative_id":{"type":"integer","nullable":true,"description":"Null when the initiative it was about no longer exists. subject_title still says what it was."},"subject_title":{"type":"string","description":"The initiative's title as it stood when the decision was made, so the row still reads after the initiative is renamed or gone."},"outcome":{"type":"string","enum":["continue","scale","fix","pause","retire"],"description":"What was decided."},"outcome_label":{"type":"string","description":"How Roiva words the outcome on a page."},"rationale":{"type":"string","description":"Why, in the words of whoever decided. Always present."},"stage_before":{"type":"string","enum":["identified","reviewing","implementing","live","paused","completed","rejected","dismissed"]},"stage_after":{"type":"string","enum":["identified","reviewing","implementing","live","paused","completed","rejected","dismissed"]},"stage_changed":{"type":"boolean","description":"Some outcomes leave the initiative where it was."},"decided_at":{"type":"string","format":"date-time","nullable":true,"description":"When it was decided, which is the date to report it under."},"decided_by":{"type":"object","nullable":true,"description":"Who, inside this account. Names, never email addresses.","properties":{"id":{"type":"integer"},"name":{"type":"string","nullable":true,"description":"Empty when they have given no name. Never an email address."}}},"next_review_on":{"type":"string","format":"date","nullable":true,"description":"When this is meant to be looked at again. Roiva suggests a date from the outcome; whoever decided can change it."},"currency":{"type":"string","nullable":true},"cash_value_cents":{"type":"integer","nullable":true,"description":"Approved cash value at the moment of the decision. Approved only, as everywhere: this is the figure that was on the screen, not a recomputation."},"freed_capacity_cents":{"type":"integer","nullable":true,"description":"Approved freed capacity at the moment of the decision: hours freed, priced at what they cost. Not cash, and in neither net_cents nor roi."},"cost_to_date_cents":{"type":"integer","nullable":true,"description":"Amount in cents, in the currency beside it."},"net_cents":{"type":"integer","nullable":true,"description":"cash_value_cents minus cost_to_date_cents, as of the decision."},"planned_value_cents":{"type":"integer","nullable":true,"description":"The cash the business case had promised in total: planned savings, plus planned revenue at the account's contribution margin."},"planned_value_to_date_cents":{"type":"integer","nullable":true,"description":"The part of that plan due by the decision date."},"roi":{"type":"number","nullable":true,"description":"The cash ROI as of the decision. Null where there was no cost to divide by."},"created_at":{"type":"string","format":"date-time","description":"When the row was first written."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. This is what the list is ordered by, what a cursor points into, and what updated_since compares against."}}}},"page":{"type":"object","required":["has_more"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page. Null on the last one."},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/integrations":{"get":{"operationId":"listIntegrations","summary":"Every connection in the account, and its health","description":"What each connection is, whether it is working, and when data last arrived. Credentials, stored settings and error prose are never served — see the field descriptions for what stands in for them. Ordered oldest change first.","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, up to 500.","schema":{"type":"integer","default":100,"minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"The next_cursor from the previous page. Opaque: its shape is not part of the contract.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Only rows changed at or after this moment, which is what makes a repeat pull cheap. At or after, not after, so a caller passing back the last updated_at they saw cannot miss a row saved in the same millisecond.","schema":{"type":"string","format":"date-time"}},{"name":"platform","in":"query","required":false,"description":"Only connections to one platform.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Only connections in this state.","schema":{"type":"string","enum":["active","paused","error","disconnected"]}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["data","page"],"properties":{"data":{"type":"array","items":{"type":"object","required":["id","platform","status","primary_source_categories","metric_categories","simulated","created_at","updated_at"],"properties":{"id":{"type":"integer"},"platform":{"type":"string","description":"Which system this connects to."},"name":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","paused","error","disconnected"],"description":"Whether it is working. \"error\" means syncs are failing; \"paused\" and \"disconnected\" mean somebody stopped it."},"auth_type":{"type":"string","nullable":true,"enum":["oauth","api_key","basic","webhook","github_app","service_account","file_upload"],"description":"How it authenticates. The credentials themselves are never served."},"last_sync_status":{"type":"string","nullable":true,"description":"How the most recent sync ended."},"last_synced_at":{"type":"string","format":"date-time","nullable":true,"description":"When data last arrived. The field to watch: a connection can read \"active\" and still not have synced for a week."},"reauth_required_at":{"type":"string","format":"date-time","nullable":true,"description":"Set when the platform refused the grant Roiva holds. Non-null means no amount of retrying will fix it and a person has to reconnect."},"external_account_id":{"type":"string","nullable":true,"description":"What the platform calls the account on its side, for joining to its own data."},"external_account_name":{"type":"string","nullable":true},"primary_source_categories":{"type":"array","items":{"type":"string"},"description":"The categories this connection is the canonical source for. Where two connections could serve the same category, only the primary one's readings are used."},"metric_categories":{"type":"array","items":{"type":"string"},"description":"What this platform's connections can serve at all, whether or not this one is primary."},"simulated":{"type":"boolean","description":"A connection whose syncs are answered by Roiva's simulator rather than by the platform. Only an internal Roiva account can hold one."},"connected_by":{"type":"object","nullable":true,"description":"Who, inside this account. Names, never email addresses.","properties":{"id":{"type":"integer"},"name":{"type":"string","nullable":true,"description":"Empty when they have given no name. Never an email address."}}},"created_at":{"type":"string","format":"date-time","description":"When the row was first written."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. This is what the list is ordered by, what a cursor points into, and what updated_since compares against."}}}},"page":{"type":"object","required":["has_more"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page. Null on the last one."},"has_more":{"type":"boolean"}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reporting/schema.json":{"get":{"operationId":"getReportingSchema","summary":"The reporting tables' schema","description":"Every table, column, type and description, as JSON. Public, like this document: it names columns, never an account's figures.","security":[],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["version","tables"],"properties":{"version":{"type":"string","description":"The schema version the files follow, such as v1."},"tables":{"type":"array","description":"Each table's name, grain, description and columns, with each column's name, type, nullability and description.","items":{"type":"object"}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reporting/latest":{"get":{"operationId":"getLatestReportingSnapshot","summary":"The newest reporting snapshot's manifest","description":"Which tables the newest snapshot holds, with each file's rows, size, SHA-256 and URL. 404 until an Owner or Admin turns on reporting tables and the first build completes.","responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["snapshot_id","schema_version","generated_at","tables"],"properties":{"snapshot_id":{"type":"string","description":"The snapshot every table's rows carry in snapshot_id."},"schema_version":{"type":"string","description":"The version of the reporting schema the files follow."},"generated_at":{"type":"string","format":"date-time","description":"The moment every table describes."},"tables":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"file":{"type":"string","description":"The file's name."},"url":{"type":"string","description":"Where to fetch it, with the same token."},"rows":{"type":"integer"},"bytes":{"type":"integer"},"sha256":{"type":"string","description":"SHA-256 of the file, to check a copy against."}}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reporting/latest/{table}.parquet":{"get":{"operationId":"getLatestReportingTable","summary":"One table from the newest snapshot, as Parquet","description":"The whole table as one Parquet file. Point Power BI's Parquet.Document, a warehouse loader or pandas at it.","parameters":[{"name":"table","in":"path","required":true,"description":"The table's name, from the manifest or the schema.","schema":{"type":"string","enum":["snapshot","initiatives","reported_value","value_inputs","value_supporting_documents","metric_readings","costs","approval_decisions","budgets","budget_vs_actual","portfolio_decisions","accounting_periods","ai_spend","reported_totals"]}}],"responses":{"200":{"description":"The figures.","content":{"application/vnd.apache.parquet":{"schema":{"type":"string","format":"binary","description":"One reporting table as a Parquet file."}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reporting/snapshots/{snapshot_id}":{"get":{"operationId":"getReportingSnapshot","summary":"One reporting snapshot's manifest","description":"As the newest snapshot's, for an earlier one still kept.","parameters":[{"name":"snapshot_id","in":"path","required":true,"description":"A snapshot's id, such as 20260915T070000Z. Snapshots are kept 14 days; the newest is kept until a newer one replaces it.","schema":{"type":"string","example":"20260915T070000Z"}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["snapshot_id","schema_version","generated_at","tables"],"properties":{"snapshot_id":{"type":"string","description":"The snapshot every table's rows carry in snapshot_id."},"schema_version":{"type":"string","description":"The version of the reporting schema the files follow."},"generated_at":{"type":"string","format":"date-time","description":"The moment every table describes."},"tables":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"file":{"type":"string","description":"The file's name."},"url":{"type":"string","description":"Where to fetch it, with the same token."},"rows":{"type":"integer"},"bytes":{"type":"integer"},"sha256":{"type":"string","description":"SHA-256 of the file, to check a copy against."}}}}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/reporting/snapshots/{snapshot_id}/{table}.parquet":{"get":{"operationId":"getReportingSnapshotTable","summary":"One table from an earlier snapshot, as Parquet","description":"As the newest snapshot's table, for an earlier snapshot still kept.","parameters":[{"name":"snapshot_id","in":"path","required":true,"description":"A snapshot's id, such as 20260915T070000Z. Snapshots are kept 14 days; the newest is kept until a newer one replaces it.","schema":{"type":"string","example":"20260915T070000Z"}},{"name":"table","in":"path","required":true,"description":"The table's name, from the manifest or the schema.","schema":{"type":"string","enum":["snapshot","initiatives","reported_value","value_inputs","value_supporting_documents","metric_readings","costs","approval_decisions","budgets","budget_vs_actual","portfolio_decisions","accounting_periods","ai_spend","reported_totals"]}}],"responses":{"200":{"description":"The figures.","content":{"application/vnd.apache.parquet":{"schema":{"type":"string","format":"binary","description":"One reporting table as a Parquet file."}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/initiatives/{id}":{"get":{"operationId":"getInitiative","summary":"One initiative","description":"The same fields the list serves, for an initiative a caller already has the id of. An id belonging to another account answers 404 rather than 403, because a 403 would confirm it exists.","parameters":[{"name":"id","in":"path","required":true,"description":"The initiative's Roiva id.","schema":{"type":"integer"}}],"responses":{"200":{"description":"The figures.","content":{"application/json":{"schema":{"type":"object","required":["id","title","stage","is_sample","created_at","updated_at"],"properties":{"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"hypothesis":{"type":"string","nullable":true},"stage":{"type":"string","enum":["identified","reviewing","implementing","live","paused","completed","rejected","dismissed"],"description":"Where it is in its lifecycle."},"is_sample":{"type":"boolean","description":"Provisioned demo data. Left out of lists unless include_sample=true, because a warehouse that ingests it reports figures nobody spent."},"category":{"type":"string","nullable":true},"priority":{"type":"integer","nullable":true,"description":"Lower is higher priority."},"source":{"type":"string","nullable":true,"description":"How the initiative came to exist."},"business_unit":{"type":"string","nullable":true},"department":{"type":"string","nullable":true},"gl_dimension_value":{"type":"string","nullable":true},"owner":{"type":"object","nullable":true,"description":"Who, inside this account. Names, never email addresses.","properties":{"id":{"type":"integer"},"name":{"type":"string","nullable":true,"description":"Empty when they have given no name. Never an email address."}}},"confidence_score":{"type":"number","nullable":true},"roi_time_horizon_years":{"type":"integer","nullable":true},"currency":{"type":"string","nullable":true},"expected_capex_cents":{"type":"integer","nullable":true,"description":"Amount in cents, in the currency beside it."},"expected_opex_cents":{"type":"integer","nullable":true,"description":"Amount in cents, in the currency beside it."},"expected_cost_savings_cents":{"type":"integer","nullable":true,"description":"Planned cash a falling cost saves per value period."},"expected_capacity_cents":{"type":"integer","nullable":true,"description":"Planned freed capacity per value period: hours priced at what they cost. Not cash, and in no planned ROI."},"expected_revenue_uplift_cents":{"type":"integer","nullable":true,"description":"Planned revenue per value period, top line, as the business case states it. Planned value counts it at the account's contribution margin."},"started_at":{"type":"string","format":"date-time","nullable":true},"went_live_at":{"type":"string","format":"date-time","nullable":true},"approved_at":{"type":"string","format":"date-time","nullable":true},"closed_at":{"type":"string","format":"date-time","nullable":true},"submitted_for_review_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time","description":"When the row was first written."},"updated_at":{"type":"string","format":"date-time","description":"When it last changed. This is what the list is ordered by, what a cursor points into, and what updated_since compares against."}}}}}},"400":{"description":"A parameter was not one this endpoint accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The token is missing, unknown, expired, or its holder can no longer open the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The token is good, but the account's plan does not include API access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such record in this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}