{"openapi": "3.1.0", "info": {"title": "CarbonGuru API", "version": "0.1.0", "summary": "Carbon footprint analysis, history and reference data for a developer's own server.", "description": "One API over the CarbonGuru platform: carbon-footprint analysis of products, carts, text, structured items and files; the sessions, exports and history those analyses produce; reference data; offsets for the account itself; and, for an account with a connected store, catalog footprints, the live footprint read, offset ordering and webhooks. Authenticate with `Authorization: Bearer <API key>`. A test key, made in the same place, connects the same way and records every order it posts as a preview: nothing is bought. Errors are RFC 9457 problem documents. Every response carries `X-Request-ID`. Writes accept an `Idempotency-Key`. Lists page with an opaque `next_cursor`. No route accepts terms, saves a payment method or starts automatic offsetting: a signed-in person does those in the account.", "contact": {"name": "CarbonGuru", "url": "https://carbonguru.io"}}, "servers": [{"url": "https://carbonguru.io", "description": "Production"}], "security": [{"bearerAuth": []}], "tags": [{"name": "Analysis", "description": "Submit an analysis and read its session."}, {"name": "History", "description": "Past analyses, footprints and account usage."}, {"name": "Reference", "description": "NAICS, industry sectors, emission factors, countries."}, {"name": "Webhooks", "description": "Endpoint registrations and the per-attempt delivery log."}, {"name": "Service", "description": "Health and the API contract."}], "paths": {"/v1/analyses": {"post": {"tags": ["Analysis"], "operationId": "createAnalysis", "summary": "Submit one analysis", "description": "A JSON body declares exactly one input: a url, text, html with the url it came from, or structured items with the country they were bought in. Two inputs is a 422, and each answers 202 with the session id.\n\nA multipart/form-data body is a file. `country_code` is required with it, because a file carries no country of its own and nothing is assumed: the footprint is calculated for the country the caller declares, and prices with no stated currency are read in that country's currency. `kind` chooses the backend, and is otherwise decided by the extension \u2014 .pdf .docx .doc .txt .md are documents, .csv .xlsx .xls .tsv .ofx .qfx .qif .iif are accounting files. A document over 100 KB or an accounting file over 1 MB is refused here with a 413 before it is forwarded.\n\nThe two kinds finish differently. An accounting file is analysed off the request and answers 202 with links.import beside the session links. A DOCUMENT IS ANALYSED INSIDE THE REQUEST by the main app's single worker process, so the request is held for the length of the analysis \u2014 up to 180 seconds \u2014 and answers 200 with status 'completed', because by then the work is done.", "security": [{"bearerAuth": ["carbon:analyze"]}], "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AnalysisRequest"}, "examples": {"url": {"summary": "A product or cart page", "value": {"url": "https://example.com/product/1", "context_type": "product"}}, "text": {"summary": "Freeform text", "value": {"text": "3x cotton t-shirt at 25.00 each"}}, "html": {"summary": "Html the caller already holds", "value": {"html": "<html>\u2026</html>", "url": "https://example.com/product/1"}}, "items": {"summary": "Structured items", "value": {"items": [{"product_name": "Cotton t-shirt", "price": {"current_price": 25.0, "currency": "GBP"}, "quantity": 3}], "country_code": "GBR"}}}}, "multipart/form-data": {"schema": {"$ref": "#/components/schemas/AnalysisUpload"}, "encoding": {"file": {"contentType": "application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.document, text/plain, text/markdown, text/csv, text/tab-separated-values, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/x-ofx"}}}}}, "responses": {"200": {"description": "A document, analysed inside the request. status is 'completed'.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Idempotent-Replayed": {"$ref": "#/components/headers/IdempotentReplayed"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AnalysisAccepted"}}}}, "202": {"description": "Accepted. The analysis runs off the request.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Idempotent-Replayed": {"$ref": "#/components/headers/IdempotentReplayed"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AnalysisAccepted"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "409": {"$ref": "#/components/responses/Conflict"}, "413": {"$ref": "#/components/responses/PayloadTooLarge"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}, "429": {"$ref": "#/components/responses/TooManyRequests"}, "502": {"$ref": "#/components/responses/BadGateway"}}}}, "/v1/sessions": {"get": {"tags": ["History"], "operationId": "listSessions", "summary": "Recent cart and order analyses", "description": "Each entry is a Session resource carrying its items, the same shape GET /v1/sessions/{session_id} returns.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 20, "minimum": 1, "maximum": 50}}], "responses": {"200": {"description": "The account's recent analyses, newest first.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SessionList"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/sessions/{session_id}": {"get": {"tags": ["Analysis"], "operationId": "getSession", "summary": "One session and every item's full data", "description": "Every data point we hold about the caller's own item and its footprint, with no tiering of the payload. The response is the defined Session and Item resources: fields the platform holds for its own pipeline \u2014 reuse provenance pointing at another account's donor analysis, server-side paths, duplicate-detection hashes, raw extraction payloads \u2014 are not part of the resource and are not served. Owner-only.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"$ref": "#/components/parameters/SessionId"}], "responses": {"200": {"description": "The session and its items.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SessionEnvelope"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/sessions/{session_id}/progress": {"get": {"tags": ["Analysis"], "operationId": "getSessionProgress", "summary": "The session's progress stream", "description": "The activity log the browser extension and the MCP poller read. A session that is not the caller's answers with an empty message list.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"$ref": "#/components/parameters/SessionId"}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}}}}, "/v1/sessions/{session_id}/exports": {"post": {"tags": ["Analysis"], "operationId": "createSessionExport", "summary": "Export a session as a file", "description": "csv and json are ready at 200. xlsx and pdf generate and answer 202; poll the job.", "security": [{"bearerAuth": ["carbon:analyze"]}], "parameters": [{"$ref": "#/components/parameters/SessionId"}, {"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ExportRequest"}}}}, "responses": {"200": {"description": "The file is ready.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Idempotent-Replayed": {"$ref": "#/components/headers/IdempotentReplayed"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ExportJob"}}}}, "202": {"description": "The file is generating.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Idempotent-Replayed": {"$ref": "#/components/headers/IdempotentReplayed"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ExportJob"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"$ref": "#/components/responses/Conflict"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}, "429": {"$ref": "#/components/responses/TooManyRequests"}, "503": {"$ref": "#/components/responses/ServiceUnavailable"}}}}, "/v1/exports/{job_id}": {"get": {"tags": ["Analysis"], "operationId": "getExport", "summary": "The state of one export job", "security": [{"bearerAuth": ["carbon:analyze"]}], "parameters": [{"name": "job_id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/exports/{job_id}/download": {"get": {"tags": ["Analysis"], "operationId": "downloadExport", "summary": "The export file", "description": "The csv, xlsx, json or pdf the export job produced, streamed through from the platform. Ready only while the job's status is 'ready': before that the answer is 409, and after the 30-day retention has run the file is gone and the answer is 410. An export can be requested again from the session at any time.", "security": [{"bearerAuth": ["carbon:analyze"]}], "parameters": [{"name": "job_id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "The export file.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/octet-stream": {"schema": {"type": "string", "format": "binary"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"description": "The export is still generating.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "410": {"description": "The export file has expired. Request the export again.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "429": {"$ref": "#/components/responses/TooManyRequests"}}}}, "/v1/imports/{job_id}": {"get": {"tags": ["Analysis"], "operationId": "getImport", "summary": "The state of one accounting import", "description": "The import's own record beside its session: the state, the counts, and whether the enriched copy of the caller's file can be downloaded. A job that is not the caller's answers 404, with no existence leak.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"$ref": "#/components/parameters/ImportJobId"}], "responses": {"200": {"description": "The import job.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ImportJob"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "429": {"$ref": "#/components/responses/TooManyRequests"}, "503": {"$ref": "#/components/responses/ServiceUnavailable"}}}}, "/v1/imports/{job_id}/download": {"get": {"tags": ["Analysis"], "operationId": "downloadImport", "summary": "The caller's file with the carbon columns appended", "description": "The original file with cg_ columns added, streamed through from the platform. Ready only while the job's status is 'ready': before that the answer is 409, and after the 30-day retention has run the file is gone and the answer is 410. The analysis itself persists, so an export can be regenerated from the session long after the file expires.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"$ref": "#/components/parameters/ImportJobId"}], "responses": {"200": {"description": "The enriched file.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/octet-stream": {"schema": {"type": "string", "format": "binary"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"description": "The import is still processing.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "410": {"description": "The enriched file has expired. Re-upload to generate a new one, or export the session.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "429": {"$ref": "#/components/responses/TooManyRequests"}}}}, "/v1/footprints/search": {"get": {"tags": ["History"], "operationId": "searchFootprints", "summary": "Search the account's footprint history", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"name": "search", "in": "query", "schema": {"type": "string"}, "description": "Product name or CarbonGuru id."}, {"name": "url", "in": "query", "schema": {"type": "string"}}, {"name": "date_from", "in": "query", "schema": {"type": "string", "format": "date"}}, {"name": "date_to", "in": "query", "schema": {"type": "string", "format": "date"}}, {"name": "category", "in": "query", "schema": {"type": "string"}}, {"name": "merchant", "in": "query", "schema": {"type": "string"}}, {"name": "min_carbon", "in": "query", "schema": {"type": "number"}}, {"name": "max_carbon", "in": "query", "schema": {"type": "number"}}, {"name": "sort_by", "in": "query", "schema": {"type": "string", "enum": ["date", "carbon_desc", "carbon_asc", "price"], "default": "date"}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 20, "minimum": 1, "maximum": 100}}, {"name": "offset", "in": "query", "schema": {"type": "integer", "default": 0, "minimum": 0}}], "responses": {"200": {"description": "The account's matching footprints as Item resources, plus one global semantic match when `search` was given.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/FootprintSearch"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/usage": {"get": {"tags": ["History"], "operationId": "getUsage", "summary": "Account identity, plan and lifetime analysis totals", "description": "account and plan come from the platform's whoami read; lifetime comes from its analysis-stats read. period is null: analyses this period, the allowance and the reset date need the one allowance principal of ADR-010 stage 0l, which is not built, and no number is invented in the meantime.", "security": [{"bearerAuth": ["carbon:read"]}], "responses": {"200": {"description": "The account's usage summary.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Usage"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}}}}, "/v1/reference/naics": {"get": {"tags": ["Reference"], "operationId": "searchNaics", "summary": "Semantic NAICS search", "security": [{"bearerAuth": ["carbon:reference"]}], "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string", "minLength": 2}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 10, "minimum": 1, "maximum": 25}}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/reference/naics/{code}": {"get": {"tags": ["Reference"], "operationId": "getNaicsCode", "summary": "A NAICS code's place in the hierarchy", "security": [{"bearerAuth": ["carbon:reference"]}], "parameters": [{"name": "code", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/reference/sectors": {"get": {"tags": ["Reference"], "operationId": "searchSectors", "summary": "Industry-sector search across NAICS and Open CEDA", "security": [{"bearerAuth": ["carbon:reference"]}], "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string", "minLength": 2}}, {"name": "system", "in": "query", "schema": {"type": "string", "enum": ["naics", "open_ceda"]}}, {"name": "sector_level", "in": "query", "schema": {"type": "integer", "minimum": 2, "maximum": 6}}, {"name": "country_code", "in": "query", "schema": {"type": "string"}, "description": "ISO 3-letter code, for an Open CEDA emission factor."}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 10, "minimum": 1, "maximum": 25}}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/reference/sectors/{code}": {"get": {"tags": ["Reference"], "operationId": "getSector", "summary": "One NAICS or Open CEDA sector", "security": [{"bearerAuth": ["carbon:reference"]}], "parameters": [{"name": "code", "in": "path", "required": true, "schema": {"type": "string"}}, {"name": "country_code", "in": "query", "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/reference/emission-factors/{naics_code}": {"get": {"tags": ["Reference"], "operationId": "getEmissionFactor", "summary": "The emission factor for one NAICS code", "security": [{"bearerAuth": ["carbon:reference"]}], "parameters": [{"name": "naics_code", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/reference/countries": {"get": {"tags": ["Reference"], "operationId": "listCountries", "summary": "The geographic contexts available for analysis", "security": [{"bearerAuth": ["carbon:reference"]}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}}}}, "/v1/webhook-endpoints": {"get": {"tags": ["Webhooks"], "operationId": "listWebhookEndpoints", "summary": "The account's webhook endpoints", "description": "No secret is in this answer. A secret appears in exactly two responses: creating an endpoint and rotating its secret.", "security": [{"bearerAuth": ["carbon:read"]}], "responses": {"200": {"description": "The registered endpoints.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["endpoints"], "properties": {"endpoints": {"type": "array", "items": {"$ref": "#/components/schemas/WebhookEndpoint"}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}}}, "post": {"tags": ["Webhooks"], "operationId": "createWebhookEndpoint", "summary": "Register a webhook endpoint", "description": "THE SECRET IS IN THIS ANSWER ONLY. Store it when you read it: we hold a verifier for it, not the value, and no route says it again. An endpoint whose secret was lost is rotated. The URL must be https on 443 and must not resolve inside our network; a URL that is not is a 422 invalid_url, and an event we do not fire is a 422 unknown_event. A key may subscribe only to events whose scope it holds. Idempotency-Key applies to the registration and not to the secret: a replay under the same key is a 409 idempotency_secret_not_replayable.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["url", "events"], "properties": {"url": {"type": "string", "description": "An https endpoint on port 443."}, "events": {"type": "array", "minItems": 1, "items": {"type": "string"}, "description": "catalog.push.completed, footprint.updated, order.offset, charge.failed, settlement.completed, certificate.issued, analysis.completed, analysis.failed."}}}, "example": {"url": "https://shop.example/hooks/carbonguru", "events": ["catalog.push.completed", "footprint.updated"]}}}}, "responses": {"201": {"description": "Registered. The secret is stated once, here.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["endpoint", "secret"], "properties": {"endpoint": {"$ref": "#/components/schemas/WebhookEndpoint"}, "secret": {"type": "string", "description": "The signing secret, whsec_\u2026 Standard Webhooks signing. It is never returned again."}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "409": {"$ref": "#/components/responses/Conflict"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/webhook-endpoints/{endpoint_id}": {"delete": {"tags": ["Webhooks"], "operationId": "deleteWebhookEndpoint", "summary": "Stop delivering to this endpoint", "description": "The endpoint's delivery log stays readable afterwards.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"$ref": "#/components/parameters/WebhookEndpointId"}], "responses": {"204": {"description": "Deleted. No body.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/webhook-endpoints/{endpoint_id}/rotate-secret": {"post": {"tags": ["Webhooks"], "operationId": "rotateWebhookSecret", "summary": "Mint a new signing secret for this endpoint", "description": "THE SECRET IS IN THIS ANSWER ONLY. The endpoint keeps its id, its URL and its events; deliveries after this call are signed with the new secret.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"$ref": "#/components/parameters/WebhookEndpointId"}], "responses": {"200": {"description": "The new signing secret, stated once.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["secret"], "properties": {"secret": {"type": "string"}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/webhook-deliveries": {"get": {"tags": ["Webhooks"], "operationId": "listWebhookDeliveries", "summary": "The per-attempt delivery log", "description": "One row per attempt with its status code, its error and when the next attempt is due. Cursor-paged; next_cursor is opaque and the Link header carries the same cursor as rel=\"next\".", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"name": "endpoint_id", "in": "query", "required": false, "schema": {"type": "string"}}, {"name": "status", "in": "query", "required": false, "schema": {"type": "string"}, "description": "pending, delivered or failed."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string"}, "description": "A next_cursor from an earlier page. Wins over limit."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 25, "minimum": 1, "maximum": 100}}], "responses": {"200": {"description": "A page of delivery attempts.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Link": {"description": "RFC 8288. rel=\"next\" when another page exists.", "schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"type": "object", "required": ["deliveries", "next_cursor"], "properties": {"deliveries": {"type": "array", "items": {"$ref": "#/components/schemas/WebhookDelivery"}}, "next_cursor": {"type": ["string", "null"]}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/webhook-deliveries/{delivery_id}/replay": {"post": {"tags": ["Webhooks"], "operationId": "replayWebhookDelivery", "summary": "Send this delivery again", "description": "The replay is a new attempt on the same log row.", "security": [{"bearerAuth": ["carbon:read"]}], "parameters": [{"name": "delivery_id", "in": "path", "required": true, "schema": {"type": "string"}}, {"$ref": "#/components/parameters/IdempotencyKey"}], "responses": {"202": {"description": "The replay was accepted.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Idempotent-Replayed": {"$ref": "#/components/headers/IdempotentReplayed"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["delivery"], "properties": {"delivery": {"$ref": "#/components/schemas/WebhookDelivery"}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"$ref": "#/components/responses/Conflict"}}}}, "/v1/health": {"get": {"tags": ["Service"], "operationId": "getHealth", "summary": "Whether this API process is serving", "security": [], "responses": {"200": {"description": "The process is serving.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Health"}}}}}}}, "/v1/openapi.json": {"get": {"tags": ["Service"], "operationId": "getOpenapi", "summary": "This document", "security": [], "responses": {"200": {"description": "The OpenAPI 3.1 document.", "content": {"application/json": {"schema": {"type": "object"}}}}}}}, "/v1/store/status": {"get": {"tags": ["Store"], "operationId": "getStoreStatus", "summary": "The store's activation state", "description": "Armed or not, settlement mode, rails, active footprints, the last order, and which panel steps remain. Nothing here arms a store, accepts the Merchant Terms or saves a payment method: those are the merchant's own acts in the panel, and steps_remaining is how a caller learns which is outstanding. plan and allowance are null \u2014 the plan is GET /v1/usage and the allowance is not served yet. key_mode is test for a test key (every order it posts is a preview), live for any other key, and null for an OAuth caller.", "security": [{"bearerAuth": ["carbon:merchant"]}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/store/config": {"get": {"tags": ["Store"], "operationId": "getStoreConfig", "summary": "The store's embed configuration", "description": "Snippet, order-receiver endpoint, registered domain, offer type, bound environment, and key presence. Presence only \u2014 never a secret value.", "security": [{"bearerAuth": ["carbon:merchant"]}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/store/display-settings": {"get": {"tags": ["Store"], "operationId": "getDisplaySettings", "summary": "The eight display flags", "security": [{"bearerAuth": ["carbon:merchant"]}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}, "put": {"tags": ["Store"], "operationId": "updateDisplaySettings", "summary": "Set the display flags the body names", "description": "Omitted flags are left as they are; the answer carries the full updated set of all eight. An unknown flag is refused rather than dropped.", "security": [{"bearerAuth": ["carbon:merchant"]}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "additionalProperties": false, "properties": {"offer_cost_visible": {"type": "boolean"}, "other_cost_visible": {"type": "boolean"}, "climate_impact_visible": {"type": "boolean"}, "lca_visible": {"type": "boolean"}, "cg_reference_visible": {"type": "boolean"}, "portfolio_visible": {"type": "boolean"}, "cart_display": {"type": "boolean"}, "checkout_display": {"type": "boolean"}}}, "example": {"cart_display": true, "offer_cost_visible": false}}}}, "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/store/products": {"get": {"tags": ["Store"], "operationId": "listStoreProducts", "summary": "The store's footprint products", "description": "Cursor-paged. next_cursor is opaque and the Link header carries the same cursor as rel=\"next\". It is present when this page came back full, because the shipped catalog read reports no total.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "cursor", "in": "query", "schema": {"type": "string"}, "description": "A next_cursor from an earlier page. Wins over limit."}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 25, "minimum": 1, "maximum": 100}}, {"name": "state", "in": "query", "schema": {"type": "string", "enum": ["all", "active", "inactive"], "default": "all"}}, {"name": "search", "in": "query", "schema": {"type": "string"}}], "responses": {"200": {"description": "A page of footprint products.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Link": {"description": "RFC 8288. rel=\"next\" when another page may exist.", "schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"type": "object", "required": ["products", "next_cursor"], "properties": {"products": {"type": "array", "items": {"type": "object"}}, "next_cursor": {"type": ["string", "null"]}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/store/products/{product_id}/activate": {"post": {"tags": ["Store"], "operationId": "activateStoreProduct", "summary": "Show this footprint on the storefront", "description": "Capped by the merchant's plan: an over-cap call refuses with catalog_cap_exceeded. A product with no computed footprint refuses with zero_kg.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "product_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The cg_product_id from the products list."}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"$ref": "#/components/responses/Forbidden"}}}}, "/v1/store/products/{product_id}/deactivate": {"post": {"tags": ["Store"], "operationId": "deactivateStoreProduct", "summary": "Hide this footprint on the storefront", "description": "Reversible. The product is kept.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "product_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The cg_product_id from the products list."}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/store/products/{product_id}/reanalyze": {"post": {"tags": ["Store"], "operationId": "reanalyzeStoreProduct", "summary": "Recompute this product's footprint", "description": "A fresh analysis on every call, and billable. A product that came from an uploaded catalog file has no page and no pushed fields: it refuses 409 with code catalog_file and the message telling the merchant to upload the file again. To analyse a changed price, push the product again instead.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "product_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The cg_product_id from the products list."}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"description": "This product cannot be re-analyzed. code names why.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}}}}, "/v1/store/products/{product_id}": {"delete": {"tags": ["Store"], "operationId": "deleteStoreProduct", "summary": "Permanently remove this footprint", "description": "Breaks future order-to-footprint matching and starts a 30-day re-acquisition cooldown on the same URL. Deactivate instead to simply stop showing a footprint.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "product_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The cg_product_id from the products list."}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/store/scan": {"post": {"tags": ["Store"], "operationId": "startStoreScan", "summary": "Scan the registered domain for new products", "description": "Reads the merchant's registered domain and nothing else \u2014 no URL is an argument. One scan at a time, ten-minute cooldown, and queuing analyses is billable.", "security": [{"bearerAuth": ["carbon:merchant"]}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "202": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"$ref": "#/components/responses/Forbidden"}, "429": {"$ref": "#/components/responses/Forbidden"}}}, "get": {"tags": ["Store"], "operationId": "getStoreScan", "summary": "The most recent scan's state", "security": [{"bearerAuth": ["carbon:merchant"]}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/catalog": {"post": {"tags": ["Store"], "operationId": "pushCatalog", "summary": "Push a catalog", "description": "Up to 500 rows, each carrying the store's one country. Needs an API key: the intake names the merchant by the key, so an OAuth token receives 403 key_required. 202 with the push id when rows were queued, or when nothing was queued and the push record exists; 200 with a null push_id and a reason when nothing was queued and nothing was recorded. Rows inside the allowance are analysed and the rest are deferred and reported on the push record \u2014 a push is never refused for size. An unchanged row takes the content-hash exit and costs nothing; force re-analyses every row.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"$ref": "#/components/parameters/IdempotencyKey"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["rows"], "properties": {"rows": {"type": "array", "minItems": 1, "maxItems": 500, "items": {"$ref": "#/components/schemas/CatalogRow"}}, "force": {"type": "boolean", "default": false, "description": "Re-analyse every row, unchanged content included. Each re-analysis is billable."}}}, "example": {"rows": [{"product_name": "Stoneware mug", "price": {"current_price": 24.0, "currency": "USD"}, "geographic_context": {"country_code": "US"}, "identifiers": ["SKU-9"]}]}}}}, "responses": {"200": {"description": "Nothing was queued and no push record was written. reason names why and push_id is null.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CatalogPush"}}}}, "202": {"description": "The push was accepted. Poll it at links.push.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Idempotent-Replayed": {"$ref": "#/components/headers/IdempotentReplayed"}}, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CatalogPush"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "409": {"$ref": "#/components/responses/Conflict"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/pushes/{push_id}": {"get": {"tags": ["Store"], "operationId": "getPush", "summary": "One catalog push's state and counts", "description": "The push record is a file_import_jobs row whose id is the push id. sessions lists every session the push produced \u2014 a push spans more than one when its rows span countries or exceed the worker's group cap. completed_at is absent: the worker writes it, and the shipped status route does not return the column.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "push_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The id POST /v1/catalog returned."}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/pushes/{push_id}/items": {"get": {"tags": ["Store"], "operationId": "getPushItems", "summary": "The push rows that never reached a session", "description": "Skipped, deferred and unchanged rows, each with its position, first identifier and stated reason. A row refused before identity resolution has no identifier, so its position is its reference. Analysed rows are the items of the push's sessions and are read through GET /v1/sessions/{id}; those links are returned here.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "push_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The id POST /v1/catalog returned."}], "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/store/footprints": {"get": {"tags": ["Store"], "operationId": "readStoreFootprints", "summary": "Read footprints live by identifier", "description": "Up to 50 identifiers in one request, sized for one catalog page. Needs an API key: merchant-embed names the merchant by the key, so an OAuth token receives 403 key_required. Unmetered, and a read never triggers an analysis. An identifier we hold nothing for is named in missing; one that two of the merchant's products both claim is a miss too, because an ambiguous identifier is dropped rather than guessed. stale says the live read failed and the cached copy was served. The whole catalog is GET /v1/store/products.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "identifier", "in": "query", "required": true, "explode": true, "schema": {"type": "array", "minItems": 1, "maxItems": 50, "items": {"type": "string"}}, "description": "Repeat for each identifier on the page being rendered."}, {"$ref": "#/components/parameters/IfNoneMatch"}], "responses": {"200": {"description": "The current footprints for these identifiers.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "ETag": {"$ref": "#/components/headers/ETag"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["footprints", "missing", "stale"], "properties": {"footprints": {"type": "object", "additionalProperties": {"$ref": "#/components/schemas/StoreFootprint"}}, "missing": {"type": "array", "items": {"type": "string"}}, "stale": {"type": "boolean"}}}}}}, "304": {"$ref": "#/components/responses/NotModified"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/store/footprints/{identifier}": {"get": {"tags": ["Store"], "operationId": "readStoreFootprint", "summary": "Read one product's footprint live", "description": "404 when we hold nothing for this identifier. Same credential, cache rule and validator as the batched read.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "identifier", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The product's first identifier, as it was pushed."}, {"$ref": "#/components/parameters/IfNoneMatch"}], "responses": {"200": {"description": "The current footprint for this identifier.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "ETag": {"$ref": "#/components/headers/ETag"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["footprint", "stale"], "properties": {"footprint": {"$ref": "#/components/schemas/StoreFootprint"}, "stale": {"type": "boolean"}}}}}}, "304": {"$ref": "#/components/responses/NotModified"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/orders": {"post": {"tags": ["Store"], "operationId": "createOrder", "summary": "Report one paid customer order", "description": "Idempotent on order_ref: a second call with the same reference returns the first result. mode is preview while automatic offsetting is off, or for every order posted with a test key (matched, summed, nothing bought), and charged when the receiver created the order; it is null for any other receiver status, because inventing one would state something the receiver did not. A test key's order is stored as test:<order_ref>, and an order_ref that starts with test: in any letter case, or runs past 186 characters, is refused. Any field other than order_ref, currency, line_items and generic is refused. At least one of line_items and generic is required: generic is {carbon_kg} (kilograms the merchant declares) or {spend_usd} (a budget converted through the platform reverse quote at the merchant's product). Kilograms round up to the whole kilogram once, when the order is built. The order may be in any currency: the kilograms come from each product's footprint times its quantity.", "security": [{"bearerAuth": ["carbon:merchant"]}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["order_ref"], "additionalProperties": false, "description": "At least one of line_items and generic is required.", "properties": {"order_ref": {"type": "string", "maxLength": 186, "description": "The order's id in the merchant's own system. At most 186 characters; it must not start with test:."}, "currency": {"type": "string", "description": "ISO 4217, as the store recorded it. Never assumed."}, "generic": {"type": "object", "description": "An order by kilograms or by dollars, alongside or instead of line_items: exactly one of carbon_kg (kilograms the merchant declares; rounds up to the whole kilogram when the order is built) or spend_usd (a budget; converted to whole kilograms through the platform reverse quote at the merchant's product, so the charge is the product price for those kilograms plus the processing fee).", "additionalProperties": false, "minProperties": 1, "maxProperties": 1, "properties": {"carbon_kg": {"type": "number", "exclusiveMinimum": 0}, "spend_usd": {"type": "number", "exclusiveMinimum": 0}}}, "line_items": {"type": "array", "items": {"type": "object", "required": ["identifier"], "properties": {"identifier": {"type": "string"}, "quantity": {"type": "integer", "minimum": 1, "default": 1}}}}}}, "example": {"order_ref": "INV-10441", "currency": "EUR", "line_items": [{"identifier": "SKU-9", "quantity": 2}]}}}}, "responses": {"200": {"description": "The receiver's outcome for this order.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["order_ref", "mode", "status"], "properties": {"order_ref": {"type": "string"}, "mode": {"type": ["string", "null"], "enum": ["preview", "charged", null]}, "status": {"type": "string", "description": "The receiver's own status, verbatim."}, "matched": {"type": ["integer", "null"]}, "skipped_refs": {"type": ["array", "null"], "items": {"type": "string"}}, "carbon_kg": {"type": ["number", "null"]}, "token": {"type": ["string", "null"]}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}, "get": {"tags": ["Store"], "operationId": "listOrders", "summary": "The merchant's offset orders", "description": "Newest first, cursor-paged. next_cursor is opaque and the Link header carries the same cursor as rel=\"next\"; it is present when the read says another page exists. Our own WooCommerce order id is not served: the merchant's order_ref is the reference on every surface. A test key lists only the orders a test key posted, under the refs it sent; a live key lists none of them; an OAuth caller lists every order.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "cursor", "in": "query", "schema": {"type": "string"}, "description": "A next_cursor from an earlier page. Wins over limit."}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 25, "minimum": 1, "maximum": 100}}], "responses": {"200": {"description": "A page of offset orders.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Link": {"description": "RFC 8288. rel=\"next\" when another page exists.", "schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"type": "object", "required": ["orders", "next_cursor"], "properties": {"orders": {"type": "array", "items": {"$ref": "#/components/schemas/Order"}}, "next_cursor": {"type": ["string", "null"]}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/orders/quote": {"post": {"tags": ["Store"], "operationId": "quoteOrder", "summary": "Price an order's matched offsets", "description": "Side-effect free. The same product quote every channel gets, for the kilograms asked for (served from the platform's quote cache, up to 300 s, as the web cart's is): amount_usd is the product price with CarbonGuru's percentage fees in it; processing fees are applied when the order is charged. A quote never rounds. Lines that match no active product come back in skipped_refs. No fee or pricing breakdown appears anywhere merchant-facing. The WordPress quote route authenticates an sk_merchant_ key only, so a caller presenting this cut's OAuth bearer receives the platform's 401.", "security": [{"bearerAuth": ["carbon:merchant"]}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"line_items": {"type": "array", "items": {"type": "object", "required": ["identifier"], "properties": {"identifier": {"type": "string"}, "quantity": {"type": "integer", "minimum": 1, "default": 1}}}}, "generic": {"type": "object", "description": "A spend-based line, quoted the way the receiver charges it."}}}}}}, "responses": {"200": {"$ref": "#/components/responses/PlatformPayload"}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}, "503": {"$ref": "#/components/responses/ServiceUnavailable"}}}}, "/v1/orders/{order_ref}": {"get": {"tags": ["Store"], "operationId": "getOrder", "summary": "One offset order", "description": "By the reference the merchant reported the order under. An order this merchant never reported and an order belonging to someone else are the same 404. A test key finds the orders a test key posted and no other; a live key finds no order a test key posted.", "security": [{"bearerAuth": ["carbon:merchant"]}], "parameters": [{"name": "order_ref", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The order's id in the merchant's own system."}], "responses": {"200": {"description": "The offset order.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["order"], "properties": {"order": {"$ref": "#/components/schemas/Order"}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/offsets/subscriptions": {"get": {"tags": ["Offsets"], "operationId": "listOffsetSubscriptions", "summary": "List the account's recurring purchases", "description": "Active and paused monthly offset and removal purchases: {subscriptions: [{id, type, carbon_kg, amount_usd, frequency, status, next_payment, product_name, available_actions}]}.", "security": [{"bearerAuth": ["carbon:commerce"]}], "responses": {"200": {"description": "{subscriptions: [...]}"}}}}, "/v1/offsets/subscriptions/{subscription_id}": {"get": {"tags": ["Offsets"], "operationId": "getOffsetSubscription", "summary": "Read one recurring purchase", "security": [{"bearerAuth": ["carbon:commerce"]}], "parameters": [{"name": "subscription_id", "in": "path", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "{subscription: {...}}"}, "404": {"description": "Not an active or paused subscription of this account."}}}, "post": {"tags": ["Offsets"], "operationId": "manageOffsetSubscription", "summary": "Pause, resume or cancel a recurring purchase", "description": "Available to an API key once the account switched API purchasing on (403 otherwise). Pause and cancel stop future charges; resume restarts them.", "security": [{"bearerAuth": ["carbon:commerce"]}], "parameters": [{"name": "subscription_id", "in": "path", "required": true, "schema": {"type": "string"}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["action"], "properties": {"action": {"type": "string", "enum": ["pause", "resume", "cancel"]}}}, "example": {"action": "pause"}}}}, "responses": {"200": {"description": "{subscription: {...}}"}, "403": {"description": "API purchasing is off, or not this account's key."}, "409": {"description": "That transition is not available from the current status."}}}}, "/v1/offsets/purchases": {"post": {"tags": ["Offsets"], "operationId": "purchaseOffsets", "summary": "Buy offsets or removals now, charged to the account's saved card", "description": "Charges the account's saved card for a carbon purchase, one-time or monthly (the first month now; WooCommerce Subscriptions renews it monthly on the same card). Available only after the account switched API purchasing on in My Account \u2192 API (403 api_purchasing_off otherwise); the account needs a saved card on the product's rail \u2014 PayPal for offsets, Stripe for removals (409 rail_not_covered). Exactly one of carbon_kg, spend_usd or items. The order buys whole units of the product (a kilogram, or a tonne) at the ALL-IN product quote, with the web order's processing fee; a spend_usd purchase can exceed the budget by less than one kilogram's price, and a product sold by the tonne is bought by quantity (422 buy_by_quantity). Idempotency-Key is required. A decline is final: post again under a new key. A purchase is a final sale.", "security": [{"bearerAuth": ["carbon:commerce"]}], "parameters": [{"name": "Idempotency-Key", "in": "header", "required": true, "schema": {"type": "string", "maxLength": 255}}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["type"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}, "carbon_kg": {"type": "number", "exclusiveMinimum": 0}, "spend_usd": {"type": "number", "exclusiveMinimum": 0}, "product_id": {"type": ["string", "integer"], "description": "A product_id from /v1/offset-products, or the product's numeric id. Default: the account's own default product for the type."}, "items": {"type": "array", "minItems": 1, "maxItems": 100, "description": "Several items in one purchase, each its own order line, instead of carbon_kg or spend_usd. An item is an analysed item, {carbonguru_id} (its name, kilograms and Climate Impact come from the analysis), or an amount, {carbon_kg, name}. The lines round up together once, as cart lines on one product do.", "items": {"type": "object", "properties": {"carbonguru_id": {"type": "string"}, "carbon_kg": {"type": "number", "exclusiveMinimum": 0}, "name": {"type": "string"}, "original_price_usd": {"type": "number", "exclusiveMinimum": 0}}}}, "session_id": {"type": "string", "description": "The analysis an amount item belongs to; links it to the analysed item whose footprint it matches."}, "subscription_mode": {"type": "string", "enum": ["onetime", "monthly"], "default": "onetime"}}}, "examples": {"amount": {"value": {"type": "offset", "carbon_kg": 12.4, "subscription_mode": "monthly"}}, "items": {"value": {"type": "removal", "items": [{"carbonguru_id": "8ae6ed33-ec6e-48df-8336-0ebd131cede5"}, {"carbon_kg": 2.5, "name": "Office heating, September"}]}}}}}}, "responses": {"201": {"description": "The purchase: {purchase: {id, state, type, product_id, product_name, carbon_kg, requested_input, requested_kg, purchase_kg, unit, unit_kg, items, amount_charged, amount_usd, currency, decline, created, receipt_url, subscription_mode, subscription: {id, status, amount_usd, next_payment} | null}}. requested_kg is the kilograms asked for (or the kilograms spend_usd bought, or the items' kilograms); purchase_kg is what the order bought, rounded up to a whole number of the product's units (unit, unit_kg). items lists an items purchase's items (carbonguru_id, name, carbon_kg, social_cost_usd), else null."}, "200": {"description": "The purchase this Idempotency-Key already made (replayed)."}, "403": {"description": "api_purchasing_off."}, "409": {"description": "rail_not_covered."}, "422": {"description": "invalid_body, or unknown_product: product_id names no product in /v1/offset-products."}}}}, "/v1/offsets/purchases/{purchase_id}": {"get": {"tags": ["Offsets"], "operationId": "getOffsetPurchase", "summary": "Read one API purchase", "description": "State is pending, offset or failed.", "security": [{"bearerAuth": ["carbon:commerce"]}], "parameters": [{"name": "purchase_id", "in": "path", "required": true, "schema": {"type": "integer"}}], "responses": {"200": {"description": "{purchase: {...}}"}, "404": {"description": "Not this account's purchase."}}}}, "/v1/offsets/quote": {"post": {"tags": ["Offsets"], "operationId": "quoteOffsets", "summary": "Price a footprint, or price a budget", "description": "Name exactly one of carbon_kg (what does this footprint cost) or amount_usd (what does this budget buy). Quoted live on every call and never from a cache. The price is ALL-IN: cost_usd is the one number, and no fee, margin, cost-basis or per-tonne rate is returned beside it. A reverse quote answers whole kilograms, ceiling-rounded to at least one, so cost_usd can exceed amount_usd by that rounding. type picks the supplier as well as the cart: offset is priced on the CNaught products, removal on the Stripe Climate products.", "security": [{"bearerAuth": ["carbon:commerce"]}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["type"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}, "carbon_kg": {"type": "number", "exclusiveMinimum": 0}, "amount_usd": {"type": "number", "exclusiveMinimum": 0}, "product_id": {"type": ["string", "integer"], "description": "A product_id from /v1/offset-products, or the product's numeric id. A named product sets the type. Default: the account's own default product for the type."}}}, "example": {"carbon_kg": 12.4, "type": "offset"}}}}, "responses": {"200": {"description": "The live quote.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["quote"], "properties": {"quote": {"type": "object", "description": "One live quote. The price is all-in: no fee, margin, cost-basis or per-tonne rate appears anywhere on it.", "properties": {"carbon_kg": {"type": ["number", "null"]}, "type": {"type": "string", "enum": ["offset", "removal"]}, "cost_usd": {"type": ["number", "null"], "description": "What this costs, all-in. The one price."}, "amount_usd": {"type": "number", "description": "On a reverse quote, the budget that was asked about."}, "product_name": {"type": ["string", "null"]}, "requested_tons": {"type": ["number", "null"]}, "orderable_tons": {"type": ["number", "null"], "description": "The quantity actually priced. A provider sells in whole units, so a tiny footprint is charged the smallest orderable quantity."}, "provider_min_tons": {"type": ["number", "null"]}, "orderable_kg": {"type": "number", "description": "orderable_tons in kilograms."}, "product_id": {"type": "string", "description": "The /v1/offset-products id of the product quoted on."}, "unit": {"type": "string", "enum": ["kg", "tonne"], "description": "The unit the product is bought in."}, "unit_kg": {"type": "number", "description": "The unit in kilograms: 1 or 1000."}, "purchase_kg": {"type": "number", "description": "What an order for carbon_kg would buy: rounded up to a whole number of the product's units, at least one. The quote itself prices carbon_kg exactly."}, "purchase_cost_usd": {"type": ["number", "null"], "description": "The all-in price of purchase_kg."}}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}, "502": {"$ref": "#/components/responses/BadGateway"}}}}, "/v1/offsets/cart": {"get": {"tags": ["Offsets"], "operationId": "getOffsetCart", "summary": "One of the two carts", "description": "type is required. The shipped read returns both carts' lines in one list, so this route filters by line type and sums the totals over what is left. The cart belongs to the account the credential resolves to and survives between requests through the WooCommerce session, which is keyed on the user: this API carries no cookie and needs none.", "security": [{"bearerAuth": ["carbon:commerce"]}], "parameters": [{"name": "type", "in": "query", "required": true, "schema": {"type": "string", "enum": ["offset", "removal"]}, "description": "Which cart. Required: there are two, and the shipped read returns both at once. offset settles through PayPal, removal through Stripe."}], "responses": {"200": {"description": "The named cart, read back.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["cart"], "properties": {"cart": {"type": "object", "required": ["type", "items", "totals", "links"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}, "items": {"type": "array", "items": {"type": "object", "properties": {"item_key": {"type": "string", "description": "Delete this line at DELETE /v1/offsets/cart/items/{item_key}."}, "name": {"type": "string"}, "description": {"type": "string"}, "quantity": {"type": ["number", "null"]}, "type": {"type": "string", "enum": ["offset", "removal"]}, "carbon_kg": {"type": ["number", "null"]}, "metric_tons": {"type": ["number", "null"]}, "cost_usd": {"type": ["number", "null"], "description": "This line's price, all-in."}, "social_cost_usd": {"type": ["number", "null"]}, "carbonguru_id": {"type": ["string", "null"], "description": "The analysed item this offset is for."}, "portfolio_name": {"type": ["string", "null"]}, "subscription_mode": {"type": ["string", "null"], "enum": ["monthly", null]}, "subscription_recurring_price_usd": {"type": ["number", "null"]}}}}, "totals": {"type": "object", "description": "Totals of THIS cart. total_cost_usd is the amount checkout charges for it: one all-in number.", "properties": {"item_count": {"type": "integer"}, "total_carbon_kg": {"type": "number"}, "total_cost_usd": {"type": "number"}}}, "links": {"type": "object", "properties": {"checkout": {"type": "string"}, "cart": {"type": "string"}}}}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}, "delete": {"tags": ["Offsets"], "operationId": "clearOffsetCart", "summary": "Empty one cart", "description": "Empties the cart type names and leaves the other cart alone. emptied names the cart that was emptied; the answer is that cart, read back.", "security": [{"bearerAuth": ["carbon:commerce"]}], "parameters": [{"name": "type", "in": "query", "required": true, "schema": {"type": "string", "enum": ["offset", "removal"]}, "description": "Which cart. Required: there are two, and the shipped read returns both at once. offset settles through PayPal, removal through Stripe."}], "responses": {"200": {"description": "The named cart was emptied and is returned.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["emptied", "cart"], "properties": {"emptied": {"type": "array", "items": {"type": "string"}}, "cart": {"type": "object", "required": ["type", "items", "totals", "links"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}, "items": {"type": "array", "items": {"type": "object", "properties": {"item_key": {"type": "string", "description": "Delete this line at DELETE /v1/offsets/cart/items/{item_key}."}, "name": {"type": "string"}, "description": {"type": "string"}, "quantity": {"type": ["number", "null"]}, "type": {"type": "string", "enum": ["offset", "removal"]}, "carbon_kg": {"type": ["number", "null"]}, "metric_tons": {"type": ["number", "null"]}, "cost_usd": {"type": ["number", "null"], "description": "This line's price, all-in."}, "social_cost_usd": {"type": ["number", "null"]}, "carbonguru_id": {"type": ["string", "null"], "description": "The analysed item this offset is for."}, "portfolio_name": {"type": ["string", "null"]}, "subscription_mode": {"type": ["string", "null"], "enum": ["monthly", null]}, "subscription_recurring_price_usd": {"type": ["number", "null"]}}}}, "totals": {"type": "object", "description": "Totals of THIS cart. total_cost_usd is the amount checkout charges for it: one all-in number.", "properties": {"item_count": {"type": "integer"}, "total_carbon_kg": {"type": "number"}, "total_cost_usd": {"type": "number"}}}, "links": {"type": "object", "properties": {"checkout": {"type": "string"}, "cart": {"type": "string"}}}}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/offsets/cart/items": {"post": {"tags": ["Offsets"], "operationId": "addOffsetCartItem", "summary": "Add one line to a cart", "description": "Name carbonguru_id (an item this account analysed, optionally with its session_id) or carbon_kg with a name (an ad-hoc footprint). A session_id on its own names no footprint and is refused, which is what the shipped route does. Adding charges nothing; subscription_mode monthly makes the line recurring and it still charges nothing until a person completes the checkout in a browser. The answer is the named cart, read back.", "security": [{"bearerAuth": ["carbon:commerce"]}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["type"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}, "carbonguru_id": {"type": "string", "description": "An item this account analysed."}, "session_id": {"type": "string", "description": "The analysis session the item belongs to. A companion to the two identities, never one itself."}, "carbon_kg": {"type": "number", "exclusiveMinimum": 0}, "name": {"type": "string", "description": "Required with carbon_kg."}, "subscription_mode": {"type": "string", "enum": ["onetime", "monthly"], "default": "onetime"}, "product_id": {"type": ["string", "integer"], "description": "The product the line buys: a product_id from /v1/offset-products, or its numeric id. Default: the account's default product for the type (GET /v1/offsets/defaults). A named product sets the cart; an unknown one is refused (422 unknown_product)."}}}, "example": {"carbonguru_id": "cg-9f2a41", "type": "offset"}}}}, "responses": {"201": {"description": "The named cart, read back.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["cart"], "properties": {"cart": {"type": "object", "required": ["type", "items", "totals", "links"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}, "items": {"type": "array", "items": {"type": "object", "properties": {"item_key": {"type": "string", "description": "Delete this line at DELETE /v1/offsets/cart/items/{item_key}."}, "name": {"type": "string"}, "description": {"type": "string"}, "quantity": {"type": ["number", "null"]}, "type": {"type": "string", "enum": ["offset", "removal"]}, "carbon_kg": {"type": ["number", "null"]}, "metric_tons": {"type": ["number", "null"]}, "cost_usd": {"type": ["number", "null"], "description": "This line's price, all-in."}, "social_cost_usd": {"type": ["number", "null"]}, "carbonguru_id": {"type": ["string", "null"], "description": "The analysed item this offset is for."}, "portfolio_name": {"type": ["string", "null"]}, "subscription_mode": {"type": ["string", "null"], "enum": ["monthly", null]}, "subscription_recurring_price_usd": {"type": ["number", "null"]}}}}, "totals": {"type": "object", "description": "Totals of THIS cart. total_cost_usd is the amount checkout charges for it: one all-in number.", "properties": {"item_count": {"type": "integer"}, "total_carbon_kg": {"type": "number"}, "total_cost_usd": {"type": "number"}}}, "links": {"type": "object", "properties": {"checkout": {"type": "string"}, "cart": {"type": "string"}}}}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/offsets/cart/items/{item_key}": {"delete": {"tags": ["Offsets"], "operationId": "removeOffsetCartItem", "summary": "Drop one cart line", "description": "The key is the item_key a cart read returned, and it belongs to exactly one of the two carts, so no type is needed. A key naming no line in this account's cart answers 404. Both carts are returned, because the shipped removal does not say which one the line was in.", "security": [{"bearerAuth": ["carbon:commerce"]}], "parameters": [{"name": "item_key", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The item_key from GET /v1/offsets/cart."}], "responses": {"200": {"description": "Both carts, read back after the removal.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["carts"], "properties": {"carts": {"type": "array", "items": {"type": "object", "required": ["type", "items", "totals", "links"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}, "items": {"type": "array", "items": {"type": "object", "properties": {"item_key": {"type": "string", "description": "Delete this line at DELETE /v1/offsets/cart/items/{item_key}."}, "name": {"type": "string"}, "description": {"type": "string"}, "quantity": {"type": ["number", "null"]}, "type": {"type": "string", "enum": ["offset", "removal"]}, "carbon_kg": {"type": ["number", "null"]}, "metric_tons": {"type": ["number", "null"]}, "cost_usd": {"type": ["number", "null"], "description": "This line's price, all-in."}, "social_cost_usd": {"type": ["number", "null"]}, "carbonguru_id": {"type": ["string", "null"], "description": "The analysed item this offset is for."}, "portfolio_name": {"type": ["string", "null"]}, "subscription_mode": {"type": ["string", "null"], "enum": ["monthly", null]}, "subscription_recurring_price_usd": {"type": ["number", "null"]}}}}, "totals": {"type": "object", "description": "Totals of THIS cart. total_cost_usd is the amount checkout charges for it: one all-in number.", "properties": {"item_count": {"type": "integer"}, "total_carbon_kg": {"type": "number"}, "total_cost_usd": {"type": "number"}}}, "links": {"type": "object", "properties": {"checkout": {"type": "string"}, "cart": {"type": "string"}}}}}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/offsets/checkout": {"post": {"tags": ["Offsets"], "operationId": "getOffsetCheckoutLink", "summary": "The checkout link for one cart", "description": "IT BUYS NOTHING. This route reads the cart and returns two URLs; it posts to no order, payment or checkout-processing route, and no /v1/ route does. A key cannot start charging an account (ADR-010 \u00a71): for the account's own offsets the API builds a cart and returns a checkout link, and the purchase happens in the browser, where a person sees the price and pays. offset settles through PayPal and removal through Stripe; the type on the URL picks which. The cart comes back beside the links so a caller can show what is about to be bought.", "security": [{"bearerAuth": ["carbon:commerce"]}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["type"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}}}, "example": {"type": "offset"}}}}, "responses": {"200": {"description": "Where to complete the purchase. Nothing was bought.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["checkout_url", "cart_url", "cart"], "properties": {"checkout_url": {"type": "string"}, "cart_url": {"type": "string"}, "cart": {"type": "object", "required": ["type", "items", "totals", "links"], "properties": {"type": {"type": "string", "enum": ["offset", "removal"]}, "items": {"type": "array", "items": {"type": "object", "properties": {"item_key": {"type": "string", "description": "Delete this line at DELETE /v1/offsets/cart/items/{item_key}."}, "name": {"type": "string"}, "description": {"type": "string"}, "quantity": {"type": ["number", "null"]}, "type": {"type": "string", "enum": ["offset", "removal"]}, "carbon_kg": {"type": ["number", "null"]}, "metric_tons": {"type": ["number", "null"]}, "cost_usd": {"type": ["number", "null"], "description": "This line's price, all-in."}, "social_cost_usd": {"type": ["number", "null"]}, "carbonguru_id": {"type": ["string", "null"], "description": "The analysed item this offset is for."}, "portfolio_name": {"type": ["string", "null"]}, "subscription_mode": {"type": ["string", "null"], "enum": ["monthly", null]}, "subscription_recurring_price_usd": {"type": ["number", "null"]}}}}, "totals": {"type": "object", "description": "Totals of THIS cart. total_cost_usd is the amount checkout charges for it: one all-in number.", "properties": {"item_count": {"type": "integer"}, "total_carbon_kg": {"type": "number"}, "total_cost_usd": {"type": "number"}}}, "links": {"type": "object", "properties": {"checkout": {"type": "string"}, "cart": {"type": "string"}}}}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/offsets/history": {"get": {"tags": ["Offsets"], "operationId": "listOffsetPurchases", "summary": "The account's completed offset purchases", "description": "Newest first, cursor-paged. next_cursor is opaque and the Link header carries the same cursor as rel=\"next\"; it is present when this page came back full, because the shipped read reports no total. count is the number of purchases on this page. Customer prices only: the ledger behind it holds the supplier cost and CarbonGuru's fee and neither is served.", "security": [{"bearerAuth": ["carbon:commerce"]}], "parameters": [{"name": "cursor", "in": "query", "schema": {"type": "string"}, "description": "A next_cursor from an earlier page. Wins over limit."}, {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 20, "minimum": 1, "maximum": 50}}], "responses": {"200": {"description": "A page of offset purchases.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Link": {"description": "RFC 8288. rel=\"next\" when another page may exist.", "schema": {"type": "string"}}}, "content": {"application/json": {"schema": {"type": "object", "required": ["purchases", "count", "next_cursor"], "properties": {"purchases": {"type": "array", "items": {"type": "object", "description": "One completed offset purchase. Customer prices only.", "properties": {"carbonguru_id": {"type": ["string", "null"]}, "item_name": {"type": ["string", "null"]}, "type": {"type": ["string", "null"], "enum": ["offset", "removal", null]}, "carbon_kg": {"type": ["number", "null"]}, "cost_usd": {"type": ["number", "null"], "description": "What was paid to offset it."}, "social_cost_usd": {"type": ["number", "null"]}, "status": {"type": ["string", "null"], "description": "delivered, processing, failed, canceled, refunded or pending."}, "order_id": {"type": ["string", "null"]}, "order_total_usd": {"type": ["number", "null"]}, "item_price_usd": {"type": ["number", "null"], "description": "What the analysed item itself cost. Not part of the offset price."}, "purchased_at": {"type": ["string", "null"]}}}}, "count": {"type": "integer"}, "next_cursor": {"type": ["string", "null"]}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/offsets/impact": {"get": {"tags": ["Offsets"], "operationId": "getOffsetImpact", "summary": "The account's lifetime offset totals", "description": "Kilograms offset, kilograms removed, the total of both, what was spent and how many purchases. Counts only purchases that reached the provider.", "security": [{"bearerAuth": ["carbon:commerce"]}], "responses": {"200": {"description": "The account's lifetime totals.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["impact"], "properties": {"impact": {"type": "object", "properties": {"total_offset_kg": {"type": ["number", "null"]}, "total_removals_kg": {"type": ["number", "null"]}, "total_carbon_kg": {"type": ["number", "null"]}, "total_spend_usd": {"type": ["number", "null"]}, "purchase_count": {"type": ["number", "null"]}}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}, "/v1/offsets/defaults": {"get": {"tags": ["Offsets"], "operationId": "getOffsetDefaults", "summary": "The account's default offset and removal products", "description": "What a purchase or cart line buys when it names no product: one offset product, one removal product. account_chosen is false for the platform's own default.", "security": [{"bearerAuth": ["carbon:commerce"]}], "responses": {"200": {"description": "{defaults: {offset, removal}}", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["defaults"], "properties": {"defaults": {"type": "object", "properties": {"offset": {"type": ["object", "null"], "properties": {"product_id": {"type": "string", "description": "The /v1/offset-products id."}, "name": {"type": "string"}, "type": {"type": "string", "enum": ["offset", "removal"]}, "unit": {"type": "string", "enum": ["kg", "tonne"]}, "unit_kg": {"type": "number"}, "account_chosen": {"type": "boolean"}}}, "removal": {"type": ["object", "null"], "properties": {"product_id": {"type": "string", "description": "The /v1/offset-products id."}, "name": {"type": "string"}, "type": {"type": "string", "enum": ["offset", "removal"]}, "unit": {"type": "string", "enum": ["kg", "tonne"]}, "unit_kg": {"type": "number"}, "account_chosen": {"type": "boolean"}}}}}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}, "put": {"tags": ["Offsets"], "operationId": "setOffsetDefaults", "summary": "Set the default offset and/or removal product", "description": "What the web's Set as Default does. Name offset, removal or both, each a product_id from /v1/offset-products or its numeric id; the removal default is a Stripe Climate product. An API key may set the defaults only once the account switched API purchasing on (403 key_not_permitted otherwise); a store key never.", "security": [{"bearerAuth": ["carbon:commerce"]}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"offset": {"type": ["string", "integer"]}, "removal": {"type": ["string", "integer"]}}}, "example": {"removal": "climsku_frontier_offtake_portfolio_2028"}}}}, "responses": {"200": {"description": "{defaults: {offset, removal}}, read back."}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}, "422": {"$ref": "#/components/responses/UnprocessableContent"}}}}, "/v1/offset-products": {"get": {"tags": ["Offsets"], "operationId": "listOffsetProducts", "summary": "The offset and removal catalog", "description": "Reference data, unmetered, scope carbon:reference \u2014 the one route in this group that is not carbon:commerce. The whole catalog comes back in one answer; the shipped route pages nothing. No prices: quote a stated quantity with POST /v1/offsets/quote.", "security": [{"bearerAuth": ["carbon:reference"]}], "responses": {"200": {"description": "Every product an account can buy.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object", "required": ["products", "count"], "properties": {"products": {"type": "array", "items": {"type": "object", "description": "One catalog product. It carries no price: the rate on the WordPress route behind it is the supplier's per-tonne price before fees, which no customer pays. Quote a stated quantity with POST /v1/offsets/quote.", "properties": {"product_id": {"type": ["string", "null"]}, "name": {"type": ["string", "null"]}, "description": {"type": ["string", "null"]}, "type": {"type": ["string", "null"], "enum": ["offset", "removal", null]}, "provider": {"type": ["string", "null"]}, "min_qty_tons": {"type": ["number", "null"]}, "unit": {"type": ["string", "null"], "enum": ["kg", "tonne", null], "description": "The unit the product is bought in. A purchase rounds up to a whole number of units."}, "unit_kg": {"type": ["number", "null"], "description": "The unit in kilograms: 1 or 1000."}, "min_quantity_kg": {"type": ["number", "null"], "description": "The smallest purchase: one unit."}, "is_portfolio": {"type": ["boolean", "null"]}, "delivery_year": {"type": ["number", "null"]}, "url": {"type": ["string", "null"]}, "pathways": {"type": "array", "items": {"type": "object"}}}}}, "count": {"type": "integer"}}}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/PaymentRequired"}, "403": {"$ref": "#/components/responses/Forbidden"}, "404": {"$ref": "#/components/responses/NotFound"}}}}}, "components": {"securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer", "description": "An API key from /my-account/api/ (`sk_merchant_\u2026`), or a CarbonGuru OAuth access token. A test key (`sk_merchant_test_\u2026`) connects like a live key; every order it posts is recorded as a preview and nothing is bought. Auth runs before body validation, and every failure returns the same 401."}}, "headers": {"XRequestID": {"description": "The request id, echoed from the caller when it is a sane token, else minted. On every response and in every log line.", "schema": {"type": "string"}}, "IdempotentReplayed": {"description": "Present and true when this response was stored by an earlier request under the same Idempotency-Key and replayed rather than processed again.", "schema": {"type": "string", "enum": ["true"]}}, "RetryAfter": {"description": "Seconds to wait before retrying, passed through from the platform's own rate limit.", "schema": {"type": "string"}}, "Deprecation": {"description": "RFC 9745. Present only on a deprecated route, as an IMF-fixdate. No /v1/ route is deprecated today.", "schema": {"type": "string"}}, "Sunset": {"description": "RFC 8594. The date a deprecated route stops answering, as an IMF-fixdate. Present only beside Deprecation.", "schema": {"type": "string"}}, "ETag": {"description": "A strong validator for this response body. Send it back as If-None-Match to be answered 304 when nothing has changed.", "schema": {"type": "string"}}}, "parameters": {"SessionId": {"name": "session_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The analysis session id."}, "ImportJobId": {"name": "job_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The import job id POST /v1/analyses returned in links.import. It is also the analysis session id."}, "IdempotencyKey": {"name": "Idempotency-Key", "in": "header", "required": false, "schema": {"type": "string", "minLength": 1, "maxLength": 255}, "description": "A key of the caller's choosing, held 24 hours and scoped to the account, the method and the path. The same key with the same body replays the stored response with Idempotent-Replayed: true. The same key with a different body is a 409 idempotency_key_reuse. A request still running under the key is a 409 idempotency_in_progress. A key longer than 255 characters or holding control characters is a 422. A write whose record cannot be stored is still processed once, without the replay guarantee. A response carrying a secret is never replayed: the second request receives a 409 idempotency_secret_not_replayable."}, "WebhookEndpointId": {"name": "endpoint_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "The endpoint id the registration returned."}, "IfNoneMatch": {"name": "If-None-Match", "in": "header", "required": false, "schema": {"type": "string"}, "description": "An ETag from an earlier read of the same resource. The response is 304 with no body when nothing has changed."}}, "schemas": {"Problem": {"type": "object", "description": "RFC 9457 problem document. Served as application/problem+json.", "required": ["type", "title", "status", "detail", "code", "request_id"], "properties": {"type": {"type": "string", "description": "A stable identifier for this problem kind."}, "title": {"type": "string"}, "status": {"type": "integer"}, "detail": {"type": "string"}, "code": {"type": "string", "description": "The machine-readable name to branch on."}, "request_id": {"type": "string"}, "errors": {"type": "array", "description": "Field-level problems, when the refusal is about the request's fields.", "items": {"type": "object"}}, "required_scope": {"type": "string", "description": "Present on insufficient_scope."}, "retry_after": {"type": "string", "description": "Present on a 429, beside the Retry-After header."}, "max_size_bytes": {"type": "integer", "description": "Present on file_too_large."}, "min_tier": {"type": "string", "description": "Present on feature_not_entitled."}, "current_tier": {"type": "string", "description": "Present on feature_not_entitled."}, "upgrade_url": {"type": "string", "description": "Present on feature_not_entitled."}, "details": {"type": "object", "description": "Present on mapping_limit_exceeded."}}}, "AnalysisRequest": {"type": "object", "description": "Exactly one input. html and url together are one input, not two.", "properties": {"url": {"type": "string", "description": "A product or cart page the platform fetches, or the address html came from."}, "context_type": {"type": "string", "enum": ["product", "cart", "multi_product", "order_list"], "default": "product"}, "timeout": {"type": "integer", "default": 60, "minimum": 10, "maximum": 120}, "text": {"type": "string", "minLength": 10, "description": "Freeform text: a shopping list, a receipt, an order confirmation."}, "html": {"type": "string", "minLength": 50, "description": "Page html the caller already holds. Needs url."}, "source_name": {"type": "string", "description": "A label for a text analysis."}, "items": {"type": "array", "description": "Structured items. Needs country_code.", "items": {"$ref": "#/components/schemas/AnalysisItem"}}, "country_code": {"type": "string", "description": "The country these items were bought in. Required with items; nothing is defaulted."}}}, "AnalysisItem": {"type": "object", "required": ["product_name", "price"], "properties": {"product_name": {"type": "string"}, "description": {"type": "string"}, "category": {"type": "string"}, "quantity": {"type": "integer", "minimum": 1, "default": 1}, "price": {"type": "object", "required": ["current_price"], "properties": {"current_price": {"type": "number", "exclusiveMinimum": 0}, "currency": {"type": "string", "description": "ISO 4217. Omitted takes the declared country's currency."}}}}}, "AnalysisUpload": {"type": "object", "required": ["file", "country_code"], "properties": {"file": {"type": "string", "format": "binary", "description": "The document or accounting file. 100 KB for a document, 1 MB for an accounting file."}, "country_code": {"type": "string", "description": "ISO 3-letter code of the country this file's purchases or products belong to. Required; nothing is assumed."}, "kind": {"type": "string", "enum": ["document", "accounting"], "description": "Omitted, the file extension decides."}}}, "AnalysisAccepted": {"type": "object", "required": ["session_id", "status", "links"], "properties": {"session_id": {"type": "string"}, "status": {"type": "string", "enum": ["processing", "completed"], "description": "completed only for a document upload, which finishes inside the request."}, "links": {"type": "object", "properties": {"session": {"type": "string"}, "progress": {"type": "string"}, "import": {"type": "string", "description": "Present for an accounting file: its import record."}}}}}, "ImportJob": {"type": "object", "required": ["job_id", "status", "counts", "enriched_file", "links"], "properties": {"job_id": {"type": "string"}, "session_id": {"type": ["string", "null"], "description": "Present once the worker has created the analysis session. It is the job id."}, "status": {"type": ["string", "null"], "description": "parsing, uploaded, processing, ready, failed, expired or cleaned."}, "parser_id": {"type": ["string", "null"], "description": "The format the platform detected."}, "error": {"type": ["string", "null"]}, "counts": {"type": "object", "properties": {"items": {"type": ["integer", "null"]}, "processed": {"type": ["integer", "null"]}, "failed": {"type": ["integer", "null"]}}}, "enriched_file": {"type": "object", "properties": {"available": {"type": "boolean", "description": "Whether the download answers 200 right now."}, "url": {"type": "string"}}}, "links": {"type": "object", "properties": {"import": {"type": "string"}, "session": {"type": "string"}, "progress": {"type": "string"}}}}}, "ExportRequest": {"type": "object", "required": ["format"], "properties": {"format": {"type": "string", "enum": ["csv", "xlsx", "json", "pdf"]}}}, "ExportJob": {"type": "object", "required": ["job_id", "status", "format", "links"], "properties": {"job_id": {"type": "string"}, "status": {"type": "string", "enum": ["ready", "generating", "failed"]}, "format": {"type": "string", "enum": ["csv", "xlsx", "json", "pdf"]}, "download_url": {"type": "string"}, "links": {"type": "object", "properties": {"export": {"type": "string"}}}}}, "Usage": {"type": "object", "properties": {"account": {"type": "object", "properties": {"user_id": {"type": "string"}, "email": {"type": ["string", "null"]}, "display_name": {"type": ["string", "null"]}, "account_type": {"type": "string"}, "member_since": {"type": ["string", "null"]}}}, "scopes": {"type": ["array", "null"], "items": {"type": "string"}}, "plan": {"type": ["object", "null"], "description": "The account's CarbonGuru subscription, as the platform reports it."}, "lifetime": {"type": "object", "properties": {"total_items": {"type": ["integer", "null"]}, "total_carbon_kg": {"type": ["number", "null"]}, "avg_carbon_kg": {"type": ["number", "null"]}, "total_sessions": {"type": ["integer", "null"]}}}, "period": {"type": "null", "description": "Analyses this period, allowance and reset date. Not served yet; no number is invented."}}}, "Item": {"type": "object", "description": "One analysed item and its footprint. The fields below are the whole resource: a field absent from a response is one the platform holds nothing for on that item. Nothing outside this list is served, including anything added to the platform's own row later.", "properties": {"carbonguru_id": {"type": "string", "description": "The item's public handle on every CarbonGuru surface."}, "session_id": {"type": "string", "description": "The analysis session this item belongs to."}, "item_number": {"type": "integer", "description": "The item's position in its session, one-based."}, "status": {"type": "string"}, "source_type": {"type": "string", "description": "How the item entered the platform: file, url, merchant_catalog, merchant_page."}, "product_name": {"type": ["string", "null"]}, "activity": {"type": ["string", "null"], "description": "The activity line the analysis read."}, "description": {"type": ["string", "null"]}, "category": {"type": ["string", "null"]}, "quantity": {"type": ["integer", "null"], "description": "Present when the source row carries a quantity of its own."}, "price": {"type": ["number", "null"]}, "currency": {"type": ["string", "null"], "description": "ISO 4217."}, "price_source": {"type": ["string", "null"]}, "price_date": {"type": ["string", "null"]}, "original_url": {"type": ["string", "null"], "description": "The page this item was read from."}, "image_url": {"type": ["string", "null"]}, "merchant": {"type": ["string", "null"]}, "carbon_kg": {"type": ["number", "null"], "description": "The footprint, kg CO2e."}, "pcf": {"type": ["number", "null"], "description": "The process-LCA footprint, where one applies."}, "co2e_per_currency_unit": {"type": ["number", "null"], "description": "The emission factor applied: kg CO2e per unit of the item's currency."}, "social_cost_usd": {"type": ["number", "null"], "description": "Social cost of carbon, USD (EPA 2023)."}, "offset_cost_usd": {"type": ["number", "null"]}, "offset_provider": {"type": ["string", "null"]}, "offset_product_id": {"type": ["string", "null"]}, "offset_quote_source": {"type": ["string", "null"]}, "offset_cache_age_s": {"type": ["integer", "null"], "description": "Age of this quote in seconds."}, "offset_requested_qty_tons": {"type": ["number", "null"]}, "offset_orderable_qty_tons": {"type": ["number", "null"]}, "offset_provider_min_tons": {"type": ["number", "null"]}, "offset_display_cost_usd": {"type": ["number", "null"]}, "offset_orderable_cost_usd": {"type": ["number", "null"]}, "offset_defaults_applied": {"type": ["boolean", "null"]}, "offset_warnings": {"type": ["array", "null"], "items": {}}, "is_offset": {"type": ["boolean", "null"], "description": "The account has bought offsets for this footprint."}, "is_removal": {"type": ["boolean", "null"], "description": "The account has bought removals for this footprint."}, "offset_type": {"type": ["string", "null"], "enum": ["offset", "removal", null]}, "offset_status": {"type": ["string", "null"]}, "offset_date": {"type": ["string", "null"]}, "is_acted_on": {"type": ["boolean", "null"]}, "acted_on_date": {"type": ["string", "null"]}, "lca_type": {"type": ["string", "null"], "enum": ["eio", "process", null]}, "naics_code": {"type": ["string", "null"]}, "naics_title": {"type": ["string", "null"]}, "naics_vintage": {"type": ["string", "null"], "description": "2017 or 2022. Null on rows written before the column existed."}, "factor_naics_code_2017": {"type": ["string", "null"], "description": "The USEEIO factor code the footprint was computed from."}, "sector_code": {"type": ["string", "null"], "description": "Open CEDA sector code, for a non-US country."}, "sector_name": {"type": ["string", "null"]}, "emission_factor_source": {"type": ["string", "null"], "description": "USEEIO, Open CEDA, and so on."}, "impact_factor_name": {"type": ["string", "null"]}, "impact_factor_id": {"type": ["string", "null"]}, "reference_product": {"type": ["string", "null"]}, "mapping_strategy": {"type": ["string", "null"], "description": "How the item was classified."}, "justification": {"type": ["string", "null"], "description": "Why this classification was chosen."}, "country_code": {"type": ["string", "null"], "description": "ISO 3-letter code the footprint was calculated for."}, "country_detection_method": {"type": ["string", "null"], "description": "How the country was determined: declared, llm, meta, tld."}, "price_adjusted": {"type": ["number", "null"], "description": "The price deflated to the factor's reference year."}, "price_reference_year": {"type": ["integer", "null"]}, "price_adjustment_factor": {"type": ["number", "null"]}, "price_adjustment_applied": {"type": ["boolean", "null"]}, "manual_price": {"type": ["number", "null"]}, "manual_carbon_footprint": {"type": ["number", "null"]}, "manual_price_date": {"type": ["string", "null"]}, "manual_price_source": {"type": ["string", "null"]}, "original_price": {"type": ["number", "null"], "description": "The price a manual override replaced."}, "original_carbon_footprint": {"type": ["number", "null"], "description": "The footprint a manual override replaced."}, "correction_date": {"type": ["string", "null"]}, "correction_reason": {"type": ["string", "null"]}, "pcf_comparison": {"type": ["object", "null"], "description": "The full comparison against a reference product."}, "pcf_comparison_product_name": {"type": ["string", "null"]}, "pcf_comparison_sector": {"type": ["string", "null"]}, "pcf_comparison_country": {"type": ["string", "null"]}, "pcf_comparison_pcf_value": {"type": ["number", "null"], "description": "The reference PCF, kg CO2e."}, "pcf_comparison_percent_diff": {"type": ["number", "null"]}, "pcf_comparison_status": {"type": ["string", "null"], "enum": ["higher", "lower", "similar", "no_match", null]}, "pcf_comparison_timestamp": {"type": ["string", "null"]}, "product_sku": {"type": ["string", "null"]}, "product_upc": {"type": ["string", "null"]}, "product_mpn": {"type": ["string", "null"]}, "product_model_number": {"type": ["string", "null"]}, "product_brand_part_number": {"type": ["string", "null"]}, "product_asin": {"type": ["string", "null"]}, "product_gtin": {"type": ["string", "null"]}, "product_item_number": {"type": ["string", "null"]}, "manufacturer_sku": {"type": ["string", "null"]}, "part_number": {"type": ["string", "null"]}, "product_identifiers": {"type": ["object", "null"]}, "order_number": {"type": ["string", "null"]}, "order_index": {"type": ["integer", "null"]}, "wc_product_url": {"type": ["string", "null"], "description": "The footprint page for this item."}, "analyzed_at": {"type": ["string", "null"]}}}, "Session": {"type": "object", "description": "One analysis session and its items. As with Item, the fields below are the whole resource.", "properties": {"id": {"type": "string"}, "status": {"type": ["string", "null"], "enum": ["running", "completed", "failed", "incomplete", null]}, "error": {"type": ["string", "null"]}, "title": {"type": ["string", "null"]}, "source_type": {"type": ["string", "null"]}, "analysis_type": {"type": ["string", "null"]}, "lca_type": {"type": ["string", "null"]}, "item_count": {"type": ["integer", "null"]}, "country_code": {"type": ["string", "null"]}, "currency": {"type": ["string", "null"]}, "merchant": {"type": ["string", "null"]}, "source_url": {"type": ["string", "null"]}, "input_urls": {"type": ["array", "null"], "items": {"type": "string"}}, "total_carbon_kg": {"type": ["number", "null"]}, "total_price": {"type": ["number", "null"]}, "total_social_cost_usd": {"type": ["number", "null"]}, "total_offset_cost_usd": {"type": ["number", "null"]}, "pcf_comparison": {"type": ["object", "null"], "description": "The session-level comparison for the total footprint."}, "is_acted_on": {"type": ["boolean", "null"]}, "acted_on_date": {"type": ["string", "null"]}, "acted_on_count": {"type": ["integer", "null"]}, "actionable_count": {"type": ["integer", "null"]}, "orders": {"type": ["array", "null"], "items": {"type": "object"}}, "order_count": {"type": ["integer", "null"]}, "date_created": {"type": ["string", "null"]}, "items": {"type": "array", "items": {"$ref": "#/components/schemas/Item"}}}}, "SessionEnvelope": {"type": "object", "required": ["session"], "properties": {"session": {"$ref": "#/components/schemas/Session"}}}, "SessionList": {"type": "object", "required": ["sessions", "count"], "properties": {"sessions": {"type": "array", "items": {"$ref": "#/components/schemas/Session"}}, "count": {"type": "integer"}}}, "FootprintSearch": {"type": "object", "required": ["footprints", "count"], "properties": {"footprints": {"type": "array", "items": {"$ref": "#/components/schemas/Item"}}, "count": {"type": "integer", "description": "Footprints on this page."}, "total_available": {"type": ["integer", "null"], "description": "Footprints matching the filter across every page."}, "global_semantic_match": {"type": ["object", "null"], "description": "One match from the global corpus, present when `search` was given. It names no session, account or identifier.", "properties": {"found": {"type": "boolean"}, "score": {"type": ["number", "null"]}, "product_name": {"type": ["string", "null"]}, "naics_code": {"type": ["string", "null"]}, "co2e_per_currency_unit": {"type": ["number", "null"]}, "carbon_kg": {"type": ["number", "null"]}}}}}, "CatalogRow": {"type": "object", "description": "One product in a catalog push. The platform's own row schema, unchanged: the store's country on every row, a price above zero in that country's currency, and at least one of identifiers or source_url as the row's identity.", "required": ["product_name", "price", "geographic_context"], "properties": {"product_name": {"type": "string", "minLength": 1, "maxLength": 512}, "price": {"type": "object", "required": ["current_price", "currency"], "properties": {"current_price": {"type": "number", "exclusiveMinimum": 0, "description": "In major units of the row's currency: 1,000 JPY is 1000."}, "currency": {"type": "string", "minLength": 3, "maxLength": 3, "description": "ISO 4217. It must be the currency the platform holds for the declared country, or the row is skipped."}}}, "geographic_context": {"type": "object", "required": ["country_code"], "properties": {"country_code": {"type": "string", "minLength": 2, "maxLength": 8, "description": "The country the merchant declares this product is sold in. No default exists."}}}, "identifiers": {"type": "array", "items": {"type": "string"}, "description": "The row's identity. The first one is the row's reference everywhere else in this API."}, "source_url": {"type": "string", "maxLength": 2048, "description": "Must be on the merchant's registered domain, or the row is keyed by its first identifier instead."}, "category": {"type": "string"}, "description": {"type": "string"}, "materials": {"type": "string"}, "origin_country": {"type": "string"}, "variation_of": {"type": "string", "maxLength": 191, "description": "The parent's primary identifier, when this row is one variation of a product."}}}, "CatalogPush": {"type": "object", "description": "What the intake did with the pushed rows. push_id is null only when nothing was queued and no push record was written.", "required": ["push_id", "counts", "skipped", "links"], "properties": {"push_id": {"type": ["string", "null"], "description": "The push's id, which reads back at GET /v1/pushes/{push_id}."}, "counts": {"type": "object", "properties": {"analyzed_now": {"type": ["integer", "null"], "description": "Rows queued for analysis. These are the billable rows."}, "already_analyzed": {"type": ["integer", "null"], "description": "Rows whose content is unchanged, in flight, or cooling down. Not billed."}, "deferred": {"type": ["integer", "null"], "description": "Rows past the allowance. They stay on the push record."}, "skipped_total": {"type": ["integer", "null"], "description": "Rows refused at intake, before any truncation of the list below."}}}, "skipped": {"type": "array", "maxItems": 50, "items": {"type": "object"}, "description": "Up to 50 refused rows, each with its position and stated reason. The full list is on the push record."}, "allowance_remaining": {"type": ["integer", "null"], "description": "Analyses left in the period. Null means unlimited."}, "reason": {"type": ["string", "null"], "description": "Present on the 200: the intake's own word for why nothing was queued, such as not_entitled or allowance_reached."}, "links": {"type": "object", "properties": {"push": {"type": "string"}}}}}, "StoreFootprint": {"type": "object", "description": "One merchant footprint, live. The fields below are the whole resource: a field absent from a response is one the analysis did not fill. The names are this API's own \u2014 the platform stores them under its WooCommerce meta keys, which are not served.", "required": ["identifier"], "properties": {"identifier": {"type": "string", "description": "The identifier this footprint was asked for under."}, "carbonguru_id": {"type": "string", "description": "The analysed item's public handle on every CarbonGuru surface."}, "session_id": {"type": "string", "description": "The analysis session this footprint came from."}, "item_type": {"type": "string"}, "analyzed_at": {"type": "string", "description": "When the analysis that produced this footprint ran."}, "product_name": {"type": "string", "description": "The product as the store named it."}, "original_item_name": {"type": "string", "description": "The name the analysis read, before any normalization."}, "description": {"type": "string"}, "category": {"type": "string"}, "currency": {"type": "string", "description": "ISO 4217, the currency the analysed price was read in."}, "source_url": {"type": "string", "description": "The merchant's own product page this footprint is for. A merchant product has no CarbonGuru-hosted page: the acquisition path creates no consumer session and no consumer footprint product."}, "source_domain": {"type": "string"}, "carbon_kg": {"type": "string", "description": "The footprint, kg CO2e."}, "carbon_lbs": {"type": "string"}, "co2e_per_currency_unit": {"type": "string", "description": "The emission factor applied: kg CO2e per unit of the product's currency."}, "carbon_intensity": {"type": "string"}, "social_cost_usd": {"type": "string", "description": "Social cost of carbon, USD (EPA 2023)."}, "offset_price_usd": {"type": "string", "description": "What the merchant is charged to offset this footprint. All-in."}, "removal_price_usd": {"type": "string", "description": "What the merchant is charged to remove this footprint. All-in."}, "offset_cost_usd": {"type": "string", "description": "The quote the analysis itself carried."}, "naics_code": {"type": "string"}, "naics_title": {"type": "string"}, "sector_code": {"type": "string"}, "sector_name": {"type": "string"}, "justification": {"type": "string", "description": "Why the analysis classified the product this way."}, "lca_type": {"type": "string"}, "mapping_strategy": {"type": "string"}, "emission_factor_source": {"type": "string"}, "country_code": {"type": "string", "description": "The country the footprint was computed for: the one the store declared."}, "country_detection_method": {"type": "string"}, "confidence": {"type": "string"}, "data_quality": {"type": "string"}, "comparison": {"type": "object", "description": "The reference product this footprint was measured against. Absent when the analysis produced no comparison.", "properties": {"product_name": {"type": "string"}, "sector": {"type": "string"}, "country": {"type": "string"}, "pcf_value": {"type": "string"}, "percent_diff": {"type": "string"}, "status": {"type": "string"}, "computed_at": {"type": "string"}}}}}, "Order": {"type": "object", "description": "One offset order the merchant reported. Our own WooCommerce order id is not part of the resource: the merchant's order_ref is the reference on every surface.", "properties": {"order_ref": {"type": "string", "description": "The order's id in the merchant's own system, as it was reported."}, "status": {"type": "string", "description": "preview, pending, offset or failed."}, "offer_type": {"type": "string", "description": "offset or removal."}, "carbon_kg": {"type": ["number", "string", "null"], "description": "The kilograms matched for this order: each product's footprint times its quantity."}, "receipt_url": {"type": ["string", "null"], "description": "The CarbonGuru-hosted proof page for this order."}, "created_at": {"type": ["string", "null"]}}}, "WebhookEndpoint": {"type": "object", "description": "One registered webhook endpoint. The signing secret is not part of the resource: it is stated once at registration and once at each rotation, and nowhere else.", "properties": {"id": {"type": "string"}, "url": {"type": "string"}, "events": {"type": "array", "items": {"type": "string"}, "description": "The events this endpoint subscribes to."}, "status": {"type": "string", "description": "active or disabled. An endpoint is disabled after repeated delivery failures."}, "created_at": {"type": ["string", "null"]}, "failure_count": {"type": ["integer", "null"], "description": "Consecutive failed deliveries. It resets on a success."}, "disabled_at": {"type": ["string", "null"]}}}, "WebhookDelivery": {"type": "object", "description": "One delivery attempt. Read it when an endpoint stopped receiving: it says what we sent, what came back, and when the next attempt is due.", "properties": {"id": {"type": "string"}, "endpoint_id": {"type": "string"}, "event_id": {"type": "string", "description": "The event this delivery carries. Retries of one event share it."}, "event_type": {"type": "string"}, "status": {"type": "string", "description": "pending, delivered or failed."}, "attempt_count": {"type": ["integer", "null"]}, "last_status_code": {"type": ["integer", "null"], "description": "The HTTP status the endpoint answered with."}, "last_error": {"type": ["string", "null"]}, "created_at": {"type": ["string", "null"]}, "delivered_at": {"type": ["string", "null"]}, "next_attempt_at": {"type": ["string", "null"], "description": "Null when no further attempt is due."}}}, "Health": {"type": "object", "properties": {"status": {"type": "string"}, "service": {"type": "string"}, "version": {"type": "string"}, "environment": {"type": "string"}}}}, "responses": {"PlatformPayload": {"description": "The platform's payload, passed through unchanged.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/json": {"schema": {"type": "object"}}}}, "Unauthorized": {"description": "No valid credential. Every authentication failure returns this one document.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "PaymentRequired": {"description": "The account's plan does not include this capability. Carries min_tier and upgrade_url.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "Forbidden": {"description": "A missing scope, an exhausted analysis allowance, or an object that is not the caller's.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "NotFound": {"description": "No such resource.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "UnprocessableContent": {"description": "The request's fields are wrong. errors[] names them.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "BadGateway": {"description": "The platform did not complete the request.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "ServiceUnavailable": {"description": "The platform is not serving this capability.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "Conflict": {"description": "An Idempotency-Key was reused with a different body (idempotency_key_reuse), or a request under the same key is still running (idempotency_in_progress).", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "PayloadTooLarge": {"description": "The uploaded file is over the limit for its kind: 100 KB for a document, 1 MB for an accounting file.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "NotModified": {"description": "The caller's If-None-Match still matches. No body.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "ETag": {"$ref": "#/components/headers/ETag"}}}, "TooManyRequests": {"description": "The platform's rate limit. Retry-After carries its own stated wait.", "headers": {"X-Request-ID": {"$ref": "#/components/headers/XRequestID"}, "Retry-After": {"$ref": "#/components/headers/RetryAfter"}}, "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}}}}