{"openapi":"3.1.0","info":{"title":"Canadian Food Inflation Index API","version":"1.0.0","description":"Free, no-auth public API for the Canadian Food Inflation Index. Three-source blended national CPI for Canadian groceries. Methodology at https://canadianfoodindex.ca/methodology. Source attribution headers are emitted on every response.","contact":{"name":"Canadian Food Inflation Index","url":"https://canadianfoodindex.ca","email":"hello@canadianfoodindex.ca"},"license":{"name":"See Terms of Use","url":"https://canadianfoodindex.ca/terms"}},"servers":[{"url":"https://canadianfoodindex.ca","description":"Production"}],"paths":{"/api/v1/inflation-index/series":{"get":{"operationId":"getInflationSeries","summary":"Get the blended Canadian Food Inflation Index for a (period, basket).","parameters":[{"name":"period","in":"query","required":true,"schema":{"type":"string","pattern":"^(20\\d{2})(-(0[1-9]|1[0-2])|-Q[1-4])?$","example":"2026-05"},"description":"Period identifier. Canonical grammar is YYYY-MM (monthly; recommended). Quarterly YYYY-Qn and yearly YYYY also supported."},{"name":"basket","in":"query","required":false,"schema":{"type":"string","default":"food_combined","example":"food_combined","enum":["food_combined","food_stores","food_restaurants"]},"description":"Basket of goods. 'food_combined' (default; combined Food rollup), 'food_stores' (groceries), or 'food_restaurants' (eating out)."}],"responses":{"200":{"description":"Blended inflation reading.","headers":{"X-Inflation-Source-Statscan-Attribution":{"description":"Statistics Canada attribution (required by Open Licence).","schema":{"type":"string"}},"X-Inflation-Source-Bankofcanada-Attribution":{"description":"Bank of Canada attribution (required by Valet Terms).","schema":{"type":"string"}},"X-Inflation-Source-Internal-Attribution":{"description":"Internal source disclosure.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexSnapshot"}}}},"400":{"description":"Bad request (invalid period or basket).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No snapshot for this (period, basket).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error (e.g., corrupt snapshot).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"options":{"operationId":"preflightSeries","summary":"CORS preflight.","responses":{"204":{"description":"No content."}}}},"/api/v1/inflation-index/snapshots":{"get":{"operationId":"listSnapshots","summary":"List every published snapshot's summary.","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"minimum":1,"maximum":500},"description":"Page size. Max 500."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"minimum":0},"description":"Page offset."}],"responses":{"200":{"description":"Page of snapshot summaries. ETag-keyed; supports If-None-Match for 304.","headers":{"ETag":{"description":"Opaque cache validator derived from total count + most-recent recorded_at.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotListResponse"}}}},"304":{"description":"Not modified (If-None-Match matched the current ETag)."},"400":{"description":"Invalid limit or offset.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"options":{"operationId":"preflightSnapshots","summary":"CORS preflight.","responses":{"204":{"description":"No content."}}}},"/api/v1/inflation-index/series.csv":{"get":{"operationId":"getInflationSeriesCsv","summary":"Get a single (period, basket) snapshot as a CSV download with embedded attribution comment lines.","parameters":[{"name":"period","in":"query","required":true,"schema":{"type":"string","pattern":"^(20\\d{2})(-(0[1-9]|1[0-2])|-Q[1-4])?$","example":"2026-05"},"description":"Period identifier (YYYY-MM canonical; quarterly + yearly also accepted)."},{"name":"basket","in":"query","required":false,"schema":{"type":"string","default":"food_combined","enum":["food_combined","food_stores","food_restaurants"]}}],"responses":{"200":{"description":"CSV body. Leading '#'-comment lines carry the attribution credits so any consumer that imports the CSV (Excel, Google Sheets, etc.) sees the credits even if response headers are stripped.","headers":{"Content-Disposition":{"description":"Suggests a download filename.","schema":{"type":"string"}}},"content":{"text/csv":{"schema":{"type":"string"}}}},"400":{"description":"Invalid period or basket.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No snapshot for the requested (period, basket).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"options":{"operationId":"preflightSeriesCsv","summary":"CORS preflight.","responses":{"204":{"description":"No content."}}}},"/api/v1/inflation-index/yoy":{"get":{"operationId":"deriveYoy","summary":"Derive a year-over-year percentage view from a stored snapshot's hybrid-storage levels block. Filters: basket × period × window (standard | trailing3) × adjustment (nsa | sa). Per ADR-0013 v4 § 'API surface'.","parameters":[{"name":"basket","in":"query","required":false,"schema":{"type":"string","default":"food_combined","enum":["food_combined","food_stores","food_restaurants"]}},{"name":"period","in":"query","required":true,"schema":{"type":"string","example":"2026-04"},"description":"Period identifier (YYYY-MM canonical)."},{"name":"window","in":"query","required":false,"schema":{"type":"string","default":"standard","enum":["standard","trailing3"]},"description":"YoY window. 'standard' = exact prior year. 'trailing3' = average of t..t-2 vs average of t-12..t-14."},{"name":"adjustment","in":"query","required":false,"schema":{"type":"string","default":"nsa","enum":["nsa","sa"]},"description":"Adjustment series. 'nsa' = raw NSA levels (always available). 'sa' = seasonally-adjusted; statscan_published for food_combined, x13_derived for sub-baskets."}],"responses":{"200":{"description":"Derived YoY percentage with provenance metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/YoyResult"}}}},"400":{"description":"Invalid period, basket, window, or adjustment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Snapshot for (basket, period) not in the cache.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Snapshot exists but lacks the levels block (pre-ADR-0013 v4 shape) OR sa requested but SA series is absent for this basket.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error or YoY derivation failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"options":{"operationId":"preflightYoy","summary":"CORS preflight.","responses":{"204":{"description":"No content."}}}},"/api/v1/inflation-index/levels":{"get":{"operationId":"readLevels","summary":"Return the raw monthly index level series from a stored snapshot's hybrid-storage levels block. Filters: basket × period × adjustment (nsa | sa); optional from/to range filter. Per ADR-0013 v4 § 'API surface'.","parameters":[{"name":"basket","in":"query","required":false,"schema":{"type":"string","default":"food_combined","enum":["food_combined","food_stores","food_restaurants"]}},{"name":"period","in":"query","required":true,"schema":{"type":"string","example":"2026-04"},"description":"Which snapshot to read from. The snapshot carries the last 15 monthly observations; without from/to that's the full window returned."},{"name":"adjustment","in":"query","required":false,"schema":{"type":"string","default":"nsa","enum":["nsa","sa"]},"description":"Adjustment series. 'nsa' = raw NSA levels. 'sa' = seasonally-adjusted; statscan_published for food_combined, x13_derived for sub-baskets."},{"name":"from","in":"query","required":false,"schema":{"type":"string","pattern":"^(20\\d{2})-(0[1-9]|1[0-2])$","example":"2025-12"},"description":"Inclusive lower bound (YYYY-MM monthly). Optional."},{"name":"to","in":"query","required":false,"schema":{"type":"string","pattern":"^(20\\d{2})-(0[1-9]|1[0-2])$","example":"2026-04"},"description":"Inclusive upper bound (YYYY-MM monthly). Optional."}],"responses":{"200":{"description":"Level series with provenance metadata.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LevelsResult"}}}},"400":{"description":"Invalid period, basket, adjustment, OR from/to (malformed YYYY-MM or from > to).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Snapshot for (basket, period) not in the cache.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"Snapshot exists but lacks the levels block OR sa requested but SA series is absent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Internal error or corrupt snapshot.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"options":{"operationId":"preflightLevels","summary":"CORS preflight.","responses":{"204":{"description":"No content."}}}},"/api/v1/inflation-index/snapshots.csv":{"get":{"operationId":"listSnapshotsCsv","summary":"Download every published snapshot's summary as a single CSV file. Useful for time-series visualization tools.","responses":{"200":{"description":"CSV body with one row per snapshot.","content":{"text/csv":{"schema":{"type":"string"}}}}}},"options":{"operationId":"preflightSnapshotsCsv","summary":"CORS preflight.","responses":{"204":{"description":"No content."}}}},"/api/v1/inflation-index/odata":{"get":{"operationId":"odataServiceDocument","summary":"OData v4 service document. Power BI / Tableau / Excel Power Query consume this to discover available entity sets.","responses":{"200":{"description":"OData v4 service document. Lists the Snapshots EntitySet.","headers":{"OData-Version":{"schema":{"type":"string","enum":["4.0"]}}},"content":{"application/json":{"schema":{"type":"object"}}}}}},"options":{"operationId":"preflightOdata","summary":"CORS preflight.","responses":{"204":{"description":"No content."}}}},"/api/v1/inflation-index/odata/$metadata":{"get":{"operationId":"odataMetadata","summary":"OData v4 $metadata (EDMX). Describes the Snapshot entity type + Snapshots entity set in CSDL XML.","responses":{"200":{"description":"EDMX metadata document (application/xml).","headers":{"OData-Version":{"schema":{"type":"string","enum":["4.0"]}}},"content":{"application/xml":{"schema":{"type":"string"}}}}}}},"/api/v1/inflation-index/odata/Snapshots":{"get":{"operationId":"odataSnapshots","summary":"OData v4 Snapshots EntitySet.","parameters":[{"name":"$top","in":"query","schema":{"type":"integer","default":1000,"maximum":5000},"description":"Page size (OData v4 $top)."},{"name":"$skip","in":"query","schema":{"type":"integer","default":0,"minimum":0},"description":"Page offset (OData v4 $skip)."},{"name":"$orderby","in":"query","schema":{"type":"string","default":"recorded_at desc"},"description":"Sort expression (OData v4 $orderby)."},{"name":"$filter","in":"query","schema":{"type":"string"},"description":"OData v4 $filter expression. Supports eq on period + basket; additional operators added on consumer request."}],"responses":{"200":{"description":"OData EntitySet response: {\"@odata.context\":..., \"value\":[...]}.","headers":{"OData-Version":{"schema":{"type":"string","enum":["4.0"]}}},"content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid $skip / $top.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"components":{"schemas":{"IndexSnapshot":{"type":"object","required":["schema_version","period","basket","blended_value","source_agreement","recorded_at","per_source"],"properties":{"schema_version":{"type":"integer","minimum":1,"example":1},"period":{"type":"string","example":"2026-05"},"basket":{"type":"string","example":"food_combined"},"blended_value":{"type":"number","description":"Confidence-weighted blended inflation reading as a percentage."},"source_agreement":{"type":"number","minimum":0,"maximum":1,"description":"0-1 scalar; exp(-variance/100) across non-fallback sources. 1.0 = full agreement OR by-definition when fewer than 2 sources contributed."},"recorded_at":{"type":"string","format":"date-time"},"per_source":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/SourceContribution"}}}},"SourceContribution":{"type":"object","required":["value","freshness_timestamp","fallback_used","source_name"],"properties":{"value":{"type":"number"},"freshness_timestamp":{"type":"string","format":"date-time"},"fallback_used":{"type":"boolean"},"source_name":{"type":"string"}}},"YoyResult":{"type":"object","required":["schema_version","basket","period","window","adjustment","yoy_pct","computed_from","recorded_at"],"properties":{"schema_version":{"type":"integer","example":1},"basket":{"type":"string","example":"food_combined"},"period":{"type":"string","example":"2026-04"},"window":{"type":"string","enum":["standard","trailing3"]},"adjustment":{"type":"string","enum":["nsa","sa"]},"yoy_pct":{"type":"number","example":3.4961},"computed_from":{"type":"object","required":["source_name","vector_id","table_id","sa_provenance","points_used"],"properties":{"source_name":{"type":"string","example":"statscan_cpi_food"},"vector_id":{"type":"integer","nullable":true,"example":41690974},"table_id":{"type":"string","nullable":true,"example":"18100004"},"sa_provenance":{"type":"string","nullable":true,"enum":["statscan_published","x13_derived"]},"points_used":{"type":"integer","example":15}}},"recorded_at":{"type":"string","format":"date-time"}}},"LevelPoint":{"type":"object","required":["period","value","release_time"],"properties":{"period":{"type":"string","example":"2026-04"},"value":{"type":"number","example":201.3},"release_time":{"type":"string","format":"date-time"}}},"LevelsResult":{"type":"object","required":["schema_version","basket","period","adjustment","source_name","vector_id","table_id","sa_provenance","points_used","series","recorded_at"],"properties":{"schema_version":{"type":"integer","example":1},"basket":{"type":"string","example":"food_combined"},"period":{"type":"string","example":"2026-04"},"adjustment":{"type":"string","enum":["nsa","sa"]},"source_name":{"type":"string","example":"statscan_cpi_food"},"vector_id":{"type":"integer","nullable":true,"example":41690974},"table_id":{"type":"string","nullable":true,"example":"18100004"},"sa_provenance":{"type":"string","nullable":true,"enum":["statscan_published","x13_derived"]},"points_used":{"type":"integer","example":15},"series":{"type":"array","items":{"$ref":"#/components/schemas/LevelPoint"}},"recorded_at":{"type":"string","format":"date-time"}}},"ApiError":{"type":"object","required":["error","error_code"],"properties":{"error":{"type":"string"},"error_code":{"type":"string","enum":["MISSING_PERIOD","INVALID_PERIOD","INVALID_BASKET","INVALID_LIMIT","INVALID_OFFSET","INVALID_WINDOW","INVALID_ADJUSTMENT","SNAPSHOT_NOT_FOUND","SNAPSHOT_CORRUPT","LEVELS_UNAVAILABLE","SA_UNAVAILABLE","YOY_DERIVATION_FAILED","RATE_LIMITED","INTERNAL"]},"hint":{"type":"string"}}},"SnapshotSummary":{"type":"object","required":["period","basket","blended_value","source_agreement","contributing_sources_count","total_sources_count","recorded_at"],"properties":{"period":{"type":"string"},"basket":{"type":"string"},"blended_value":{"type":"number"},"source_agreement":{"type":"number"},"contributing_sources_count":{"type":"integer"},"total_sources_count":{"type":"integer"},"recorded_at":{"type":"string","format":"date-time"}}},"SnapshotListResponse":{"type":"object","required":["snapshots","count","total","offset","limit","skipped_count","skipped_reasons"],"properties":{"snapshots":{"type":"array","items":{"$ref":"#/components/schemas/SnapshotSummary"}},"count":{"type":"integer","description":"Number of snapshots in THIS page (after offset+limit)."},"total":{"type":"integer","description":"Total snapshots across all pages."},"offset":{"type":"integer"},"limit":{"type":"integer"},"skipped_count":{"type":"integer"},"skipped_reasons":{"type":"array","items":{"type":"object","properties":{"filename":{"type":"string"},"reason":{"type":"string"}}}}}}}}}