{"openapi":"3.1.0","info":{"title":"Integration Runtime API","version":"1.0.0-emitted","description":"Multi-product SaaS integration runtime — product backend API.\n\nProduct backends authenticate to the runtime using HMAC-signed requests (ADR-007 §4).\nEndpoints in this spec are intended for **product backends**; the runtime's internal\nadmin console (`/internal/*`) is documented separately and is Cloudflare Access-protected.\n\n**Source of truth.** This file is generated from Zod schemas in the runtime codebase\nvia `pnpm emit-openapi` (ADR-012 §4). Do not edit by hand — the spec-diff CI gate fails\non drift.\n\n## Errors\n\nEvery error the runtime itself produces is RFC 7807 `application/problem+json`.\n`code` is the machine-readable discriminator — **switch on `code`, never on `title` or\n`detail`**, which are human-facing and may be reworded in any release. `request_id` is on\nevery problem body; quote it when you escalate.\n\n`code` is an open string, not an enum: new codes ship in MINOR releases and a client must\nnot reject one it has not seen. Fall back on the status class — `4xx` means do not retry\nunless it is a `429`; `5xx` means retry with jittered backoff.\n\n**Request-schema validation failures** are `400 request.validation_failed`, in the same\nenvelope as everything else. They additionally carry an `errors` array: one entry per\noffending field, whose `field` is prefixed with where the value was read from — `body.`,\n`query.`, `path.`, `header.` or `cookie.` — so the same name in two targets stays\ndistinguishable. `errors[].code` is a validator diagnostic label, useful in a log and NOT a\nstable branch point; branch on the top-level `code`. The array is capped at 20 entries and\neach `message` is truncated, so a corrected request may surface further failures.\n\nThe table below is the catalogue for this, the product-backend API. The same machine-readable\nlist is emitted as the `x-ir-error-codes` extension on `info`.\n\n| Code | Status | Cause | What to do | Retry |\n|---|---|---|---|---|\n| `quickbooks.invoice_update_payload_invalid` | 400 | The frozen Invoice edit has invalid request/change identity, source, revision, existing line IDs, CAD totals or supported fields. No job is admitted. | Correct the explicitly approved change before first submission. Preserve any existing body, business key and SyncToken after uncertain admission. | not without a change |\n| `quickbooks.invoice_update_source_mismatch` | 400 | The original Invoice edit names a company that differs from the scoped native QuickBooks connection. No job is admitted. | Inspect the original company and connection mapping. Do not repin or re-key an edit whose provider outcome is uncertain. | not without a change |\n| `payment.invalid_input` | 400 | The linked Payment has invalid positive decimal amounts, native references, Invoice linkage or update selectors. No job is admitted. | Correct the original business input before its first submission. Preserve any existing immutable request and reconcile uncertainty instead of changing its key. | not without a change |\n| `quickbooks.adjustment_payload_invalid` | 400 | The source-bound credit, refund, application or void envelope is invalid. No job is admitted. | Use the explicit operation-specific envelope and original native source identity. Never re-key a return whose provider outcome is uncertain. | not without a change |\n| `master.not_found` | 404 | The scoped exact-name or provider-id lookup returned no current record, or the selected ERP mapping does not exist. | Choose a verified provider id or retain the missing mapping for investigation; re-sign reads. | re-sign first |\n| `master.rate_limited` | 429 | The shared provider bucket, serialized read slot or provider response refused this controlled master lookup. | Wait for Retry-After or bounded backoff, then re-sign the same scoped read. | re-sign first |\n| `master.provider_unconfirmed` | 502 | The provider read, native grant, operation configuration or response fields could not be confirmed. No mapping is updated. | Preserve the mapping and command; re-sign one read after backoff and investigate continued refusal. | once, then escalate |\n| `master.ambiguous` | 409 | Exact-name lookup returned multiple provider records; choosing one automatically would make the ERP mapping ambiguous. | Read and bind one explicit verified provider id in the same company. | not without a change |\n| `master.unsupported` | 409 | The provider Item or current tax definition is outside the supported bounded master-data shape or effective-rate rules. | Use a supported current definition and verified provider id; do not invent rates. | not without a change |\n| `master.connection_unconfirmed` | 409 | The scoped QuickBooks connection, live native grant or company binding could not be confirmed before this master operation. | Refresh the selected connection and confirm its exact company before retrying. | not without a change |\n| `master.identity_changed` | 409 | The company or native token generation changed during the controlled read. Returned provider evidence is discarded. | Re-read the selected connection and re-sign the original scoped read. | re-sign first |\n| `master.reconciliation_mismatch` | 409 | The explicit provider id does not match the pending immutable command fields, ERP identity or advancing update version. | Investigate the original command and provider evidence; do not change its identity to retry. | not without a change |\n| `master.mapping_conflict` | 409 | A stable provider binding, pending command or concurrent revision prevents the requested local master binding. | Read the current mapping and reconcile its exact pending command before changing any fields. | not without a change |\n| `master.invalid_command` | 400 | Master admission requires one strict immutable command whose command id, entity, recipe and requested object identity agree. No job is created. | Correct the command envelope before preparing and submitting a new reviewed request. | not without a change |\n| `master.company_mismatch` | 409 | The frozen master command company differs from the current scoped QuickBooks connection company at admission. No job is created. | Select the intended connection and verify the company before submitting this command. | not without a change |\n| `master.invalid_query` | 400 | The master lookup or mapping read contains unknown or duplicate query keys. No provider HTTP or local update is attempted. | Use one tenant_id and one documented selector, then sign a fresh request. | not without a change |\n| `usage.window_too_dense` | 422 | The bounded job-usage window contains more than 10000 retained jobs or 200 provider/operation groups. No partial counts are returned. | Choose a shorter window_minutes and send a freshly signed request. | not without a change |\n| `financial_write_tax_declaration_missing` | 400 | Invoice admission requires tax_declarations with exactly one declaration for every provider sales line; the sibling or a line declaration is missing. No job is created. | Add every required tax declaration, then re-sign and resubmit the corrected request. | not without a change |\n| `financial_write_tax_payload_invalid` | 400 | The Invoice-only create payload is malformed: provider-native customer/item/tax references are missing, declarations are invalid, automatic customer/items inputs are present, or the Invoice contains Id, SyncToken or sparse update selectors. No job is created. | Use existing QuickBooks references in the selected company, remove helper inputs and update selectors, correct the document or declarations, then re-sign and resubmit. | not without a change |\n| `financial_write_tax_path_unsupported` | 400 | Invoice admission received request TxnTaxDetail, a calculation mode other than TaxExcluded, or a line other than top-level SalesItemLineDetail. No job is created. | Use the supported TaxExcluded sales-line calculation path, then re-sign and resubmit the corrected request. | not without a change |\n| `hmac.signature_invalid` | 401 | Any failure in signature verification: a missing or malformed `X-IR-App-Id` / `X-IR-Key-Id` / `X-IR-Nonce` / `X-IR-Signature` / `X-IR-Timestamp`, a timestamp more than 300s from the runtime's clock, a nonce outside 22–64 characters, an unknown or revoked key, or a signature that does not match. These are deliberately indistinguishable so the response cannot be used to enumerate valid app/key pairs. | Re-derive the canonical payload (ADR-007 §4) and check it against the published HMAC test vectors, verify the app and key are active, and check your clock. The runtime records the specific reason in its own logs against your `request_id`. | not without a change |\n| `hmac.nonce_reused` | 401 | The `X-IR-Nonce` on this request has already been accepted. Nonces are single-use; this is the replay defence working as designed. | Never retry a signed request byte-for-byte. Generate a fresh nonce and timestamp and re-sign. See “Retrying a 429” — this is the error a naive retry after a quota 429 produces. | re-sign first |\n| `rate_limit.shield_shed` | 429 | The pre-authentication shield, keyed on client IP address. It runs before HMAC verification and exists to shed floods, not to express the product contract. | Wait out `Retry-After` (the full window, 60s), add jitter, resend. Because the decision happens before authentication, the nonce was not consumed. **Byte-identical replay is only safe inside the 300s signature timestamp window** — roughly four `Retry-After` cycles. Past that the original `X-IR-Timestamp` falls outside the window and you get `401 hmac.signature_invalid`, which is indistinguishable from a signing bug. Re-sign if you have been backing off for more than a couple of minutes. | safe to replay |\n| `rate_limit.quota_exceeded` | 429 | The per-application quota, keyed on the authenticated `app_id`. It is evaluated AFTER HMAC verification. | Wait out `Retry-After`, add jitter, and **re-sign with a fresh nonce and timestamp**. The rejected request already consumed its nonce, so replaying it verbatim returns `401 hmac.nonce_reused`. | re-sign first |\n| `authz.ip_not_allowed` | 403 | The service key you signed with is restricted to a set of IP ranges, and the address this request arrived from is not in any of them. Evaluated AFTER signature verification, so it never reveals whether a key exists. It also fires when the runtime cannot determine your source address at all, in which case a restricted key refuses every request rather than silently dropping the restriction. | Send from an allowlisted address, or ask your platform operator to widen the key's allowed IP ranges (they can read the exact address the request arrived from off your `request_id`). Retrying from the same place fails identically. The rejected request already consumed its nonce, so any retry must be re-signed with a fresh nonce and timestamp. | not without a change |\n| `authz.provider_not_enabled` | 403 | The provider named in `provider_key` is not enabled for your application. | Ask your platform operator to enable the provider for the application. | not without a change |\n| `authz.tenant_mismatch` | 403 | The `tenant_id` is not a tenant of the authenticated application. | Send a `tenant_id` that belongs to your app. A tenant of another application is never visible to you, by design. | not without a change |\n| `connection.not_found` | 404 | The connection id or QuickBooks company identity is not present in the authenticated app and tenant scope. The runtime deliberately returns the same response for a foreign company identity. | Check the connection, tenant and QuickBooks company ids before re-signing the request. | not without a change |\n| `calendar.grant_not_found` | 404 | The Calendar grant id is not present in the authenticated app and tenant scope. Foreign grant ids are deliberately indistinguishable from unknown ids. | Check the tenant and grant ids before re-signing the request. | not without a change |\n| `calendar.resource_not_found` | 404 | The Calendar resource binding id is not present in the authenticated app and tenant scope. Foreign resource ids are deliberately indistinguishable from unknown ids. | List the scoped grant's current resources and use a returned resource id. | not without a change |\n| `calendar.page_not_found` | 404 | The Calendar page id is not present in the authenticated app and tenant scope. Foreign page ids are deliberately indistinguishable from unknown ids. | Use the page URL from the completed Calendar job manifest, or start a new root read if it was lost. | not without a change |\n| `calendar.continuation_not_found` | 404 | The opaque Calendar continuation handle is absent from the authenticated app and tenant scope or does not identify a retained page traversal. | Start a new root Calendar discovery or event read; do not substitute or guess another traversal handle. | not without a change |\n| `calendar.invalid_input` | 400 | The Calendar discovery or event-read payload does not match the strict operation contract, identity binding, page-size limit or event-window rules. | Correct the request fields and immutable grant or resource identity, then re-sign and submit a new request. | not without a change |\n| `calendar.unsupported_representation` | 400 | The Calendar event read requests a representation that the runtime does not preserve truthfully; only series-and-exceptions is supported. | Use the series-and-exceptions representation and submit a newly signed event-read request. | not without a change |\n| `calendar.invalid_continuation` | 400 | The retained Calendar continuation state no longer validates against its protected scope, operation, query or bounded private-state schema. | Discard the continuation handle and start a new root Calendar discovery or event read. | not without a change |\n| `calendar.resource_denied` | 403 | Persisted Calendar grant, principal, registration, credential or resource identity does not match the scoped binding required by the request. | Do not substitute public ids across grants or tenants. Fetch current scoped status and resource metadata before starting a new operation. | not without a change |\n| `calendar.resource_detached` | 403 | The Calendar resource binding exists in the authenticated scope but has already been detached and cannot authorize a provider read. | List the grant's current resources and select an active binding before starting another read. | not without a change |\n| `calendar.grant_revoked` | 403 | The persisted Calendar grant is disconnected or revoked, so it cannot authorize discovery or event reads. | Reconnect the Calendar account and repeat discovery before selecting resources or reading events. | not without a change |\n| `calendar.grant_disabled` | 403 | The Calendar grant or its application provider configuration is disabled for discovery and event reads. | Ask the platform operator to enable Calendar access, then fetch grant status before retrying. | not without a change |\n| `calendar.insufficient_scopes` | 403 | The Calendar token or application policy does not include every scope required by the requested capability. | Reconnect with the required Calendar scopes, then repeat discovery before starting provider reads. | not without a change |\n| `calendar.reconnect_required` | 409 | The Calendar connection or token lifecycle is not currently active enough to authorize provider discovery or reads. | Reconnect the Calendar account, then fetch current grant status and repeat discovery before retrying. | not without a change |\n| `calendar.generation_changed` | 409 | The persisted Calendar callback or token generation no longer matches the generation captured by this request or resource binding. | Fetch current grant status, repeat discovery and create a new explicit selection or read request. | not without a change |\n| `calendar.selection_changed` | 409 | A selection or detach retry no longer names the same resource binding, grant generation or idempotent intent. | Fetch the current selected-resource list. Preserve the original idempotency key only for an exact retry; otherwise start a new explicit intent. | not without a change |\n| `calendar.page_pending` | 409 | The scoped Calendar page exists, but its owning job has not completed and published the matching immutable result manifest. | Poll the owning job until it completes, then fetch the page URL from that job's result manifest. | re-sign first |\n| `calendar.page_expired` | 410 | The authenticated Calendar page traversal reached its fixed root expiry or its retained payload was pruned. | Start a new root Calendar discovery or event read; page access cannot extend or revive the traversal. | not without a change |\n| `calendar.discovery_expired` | 410 | The Calendar discovery page is older than the bounded selection-proof window and can no longer authorize a resource choice. | Run Calendar discovery again and select the resource from the newly returned discovery page. | not without a change |\n| `calendar.continuation_expired` | 410 | The Calendar continuation belongs to a root traversal whose fixed expiry elapsed or whose retained private provider state was pruned. | Start a new root Calendar discovery or event read; the expired continuation cannot be renewed or replayed. | not without a change |\n| `calendar.state_unavailable` | 503 | The runtime cannot safely interpret the persisted Calendar lifecycle or authorization state. | Retry once with a freshly signed request, then escalate with the request id. | once, then escalate |\n| `oauth.lifecycle_busy` | 409 | A callback exchange, token refresh, reconnect, provider execution or a different disconnect request currently owns this connection's durable OAuth lifecycle generation. | Wait briefly, then re-sign and retry. For disconnect, retain the original idempotency key. | re-sign first |\n| `oauth.disconnect_retryable` | 503 | Intuit revocation or the local disconnected projection did not complete. The encrypted retry material remains fenced from callback, refresh and provider execution paths. | Re-sign and retry with the same disconnect idempotency key. Escalate if one retry still fails. | once, then escalate |\n| `request.payload_too_large` | 413 | The raw request body exceeded the `/v1` body limit. Enforced before authentication, so it fires without the body ever being hashed. | Send a smaller body. Chunking at the transport layer does not help — the limit is on the request, not on a single frame. | not without a change |\n| `request.validation_failed` | 400 | The request did not match the operation's declared schema for its body, query, path, header or cookie parameters — a missing or empty required field, a wrong type, or a query parameter repeated where one value is expected. Raised by the shared validation hook before the handler runs, so no side effect has occurred. Properties the schema does not declare are IGNORED rather than rejected, so sending extra fields is not what caused this. A body that is not parseable JSON is rejected EARLIER, by the framework, and returns `400 text/plain` instead — see “Errors that are not problem+json”. | Read the `errors` array: each entry names the offending `field`, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`). Fix the request and resend; on a signed route, re-sign it. Switch on the top-level `code` — `errors[].code` is a validator diagnostic label and is not stable. The list is capped at 20 entries, so a fixed request may surface further ones. | not without a change |\n| `payload_too_large` | 400 | On `POST /v1/operations/execute`, the serialized `payload` field exceeded the maximum job-input size. Distinct from `request.payload_too_large`, which is the whole-body limit at a different layer. | Reduce `payload`, or reference bulk data out of band. | not without a change |\n| `idempotency_key_required` | 400 | The requested operation has `risk_level = financial` and no idempotency key was supplied in either `idempotency_key` or the `Idempotency-Key` header. | Supply a stable key derived from the business event, not a fresh UUID per attempt. See “Idempotency keys”. | not without a change |\n| `connect.return_url_not_allowed` | 400 | `return_url` is unparseable, is not `https`, or its scheme+host is not an exact match in the application's configured callback allowlist. Suffix matching is deliberately not used. | Use an allowlisted return URL, or have the operator add yours to the allowlist. | not without a change |\n| `connect.scopes_not_permitted` | 400 | The requested `scopes` are not a subset of the scopes configured for this provider. | Request a narrower scope set. | not without a change |\n| `quickbooks.consent_frozen` | 409 | New production QuickBooks consent is temporarily paused while the provider authorization boundary is reviewed. No connect session or authorization URL is created. | Do not retry automatically. Wait for an operator notice before starting a new QuickBooks Connect or Reconnect ceremony. | not without a change |\n| `connect.session_not_found` | 410 | The connect session does not exist, has expired, was already completed, or its token did not match. The four are collapsed into one response on purpose — distinguishing them would let a caller probe session state. | Start a new connect session. | not without a change |\n| `sync_profile.not_found` | 404 | No sync profile with that id is reachable for the authenticated application and tenant. A profile belonging to another tenant reads as not-found rather than forbidden. | Check `sync_profile_id` and `tenant_id`. | not without a change |\n| `mapping.unknown_object_type` | 400 | The dry-run could not shape a provider payload because the object type stored **on the sync profile** is not one the provider adapter knows. This is a stored-configuration problem, not a problem with the request body. | Have the operator correct the sync profile. Changing the request will not help. | not without a change |\n| `recipe_not_found` | 404 | No recipe is deployed for the `recipe_key` + `recipe_version` pair supplied. | Use a deployed pair. Recipe versions are pinned deliberately so a runtime deploy cannot silently change the steps you asked for. | not without a change |\n| `operation_admission_mismatch` | 400 | The requested provider or object type conflicts with the deployed recipe, or the recipe's operation is associated with a different provider in the catalog. No job is created or published. | Use the exact provider and object type declared by the deployed recipe. If the catalog association is wrong, have an operator correct the runtime configuration rather than changing the request. | not without a change |\n| `operation_app_not_permitted` | 403 | The authenticated runtime app is not permitted to admit this recipe. Recipe product labels are bound explicitly to routable runtime app ids, and shared recipes still require a routable app. | Use a recipe permitted for the authenticated app. Do not retry under another tenant or alter the recipe version to bypass this application boundary. | not without a change |\n| `recipe_version_retired` | 409 | The requested or replayed recipe version is still registered for historical inspection but is below the minimum safe version for that recipe. | Submit a new job using the minimum version named in the response. Do not replay the retired job unchanged. | not without a change |\n| `invoice_identity_unavailable` | 409 | Invoice admission cannot establish the destination QuickBooks company. | Connect the intended company and resubmit. Historical writes without immutable identity require operator review; never re-key an uncertain write. | not without a change |\n| `idempotency_key_conflict` | 409 | The idempotency key belongs to a job with a different payload, company, recipe version, operation, or object identity, or historical payload evidence is unavailable. | Use the original payload and identity exactly. An existing internal invoice is immutable; do not change keys to bypass a conflict. Missing historical evidence requires operator review. | not without a change |\n| `operation_not_enabled` | 403 | The provider operation exists in the catalog but is not enabled. Financial-write operations ship disabled and stay disabled until a documented operator sign-off has been filed. | Ask your platform operator to enable the operation. This is a governance gate, not a transient condition. | not without a change |\n| `operation_not_attestable` | 403 | The provider operation is disabled AND can never be enabled: no §6.5 attestation may cover it, so the governance gate that would enable it refuses too. Distinct from `operation_not_enabled`, which is a state an operator can change. | Do not ask an operator to enable this operation and do not retry — both the enable endpoint and the sign-off route refuse the request. Treat it as permanently unavailable for this runtime version. | not without a change |\n| `tenant_operation_not_enabled` | 403 | The provider operation is enabled in the catalog, but not for YOUR tenant. Financial-write operations are rolled out one tenant at a time, so an operation can be live for another tenant of the same application and still be closed for yours. | Ask your platform operator to enable the operation for this tenant. Distinct from `operation_not_enabled`, which means the operation is disabled for everyone — this is a per-tenant governance gate, not a transient condition. | not without a change |\n| `tenant_enablement_unavailable` | 503 | The runtime could not read the per-tenant enablement state for this operation, so it refused rather than guess. This is an infrastructure condition, not a permission decision. | Retry with a FRESHLY SIGNED request (new `X-IR-Nonce` + `X-IR-Timestamp`) and ordinary backoff. Replaying the identical signed request fails `hmac.nonce_reused`: this refusal is raised inside the handler, after the auth middleware has already consumed the nonce. No `Retry-After` header is sent on this response. If it persists, escalate — the runtime is deliberately refusing financial writes while the rollout state is unknown. | re-sign first |\n| `sync_job.not_found` | 404 | No sync job with that id exists for the authenticated application and tenant. Cross-tenant reads are reported as not-found. | Check the job id and `tenant_id`. | not without a change |\n| `calendar.replay_requires_new_read` | 409 | The source job belongs to a paginated Google Calendar read. Its continuation and authorization state are managed by the Calendar page flow, so generic sync-job replay is refused without creating a replacement job. | Start a new root Calendar read with a newly signed request. Do not replay the stored page job or reuse its continuation as a generic sync-job input. | not without a change |\n| `sync_job.not_replayable` | 409 | The job cannot be replayed, or publication of its replacement was deferred or refused. Causes include missing recipe/input, unavailable historical Invoice identity, a payload conflict, or an unconfirmed delivery outcome. The original dead-letter record stays open when replacement publication is not confirmed. | If instance contains a replacement job polling URL, poll that job and inspect its failure code; do not repeatedly replay the original or create an Invoice with a new key. Escalate terminal or unconfirmed outcomes to your platform operator for reconciliation. A deferred replacement may recover through the publication sweep. | not without a change |\n| `sync_job.replay_in_progress` | 409 | The source job is still queued or processing. Its step results are not stable yet, so creating a replay could run beside the original or skip a verifier whose latest run later fails. | Wait until the source job reaches a terminal status, fetch it again, then submit a newly signed replay request. Do not create a second operation while the original is active. | re-sign first |\n| `request.route_not_found` | 404 | No route matched the path + method, and every path-scoped middleware let the request through. The runtime does not emit `405`: a wrong method on a known path produces this code — but only after the path's middleware runs, because middleware is bound to the PATH, not the method. On a signed path, an UNSIGNED wrong-method request therefore surfaces as `401 hmac.signature_invalid` (and an over-limit one as `429`) before the router's not-found can render. | Check the path and method against the spec. A resource that exists but is not yours produces a resource-specific 404 (`sync_job.not_found`, `sync_profile.not_found`) instead, so this code always means the URL or verb is wrong. | not without a change |\n| `internal_error` | 500 | An unhandled fault in the runtime. This is also what a request carrying an unrecognised `app_id` currently produces on some paths. | Retry once with backoff, then escalate quoting `request_id`. For non-idempotent operations, confirm whether the write landed before retrying — a 500 does not mean nothing happened. Do not loop: a persistent 500 is an incident, not congestion. | once, then escalate |\n\n### Errors that are not problem+json\n\nOne response does not carry the envelope, and one carries it only sometimes. A client that\nassumes `problem+json` universally will mis-handle the first:\n\n- **A request body that is not parseable JSON** returns `400` as `text/plain`, with the body\n  `Malformed JSON in request body`. It is rejected by the framework before the schema layer\n  runs, so it carries no `code` and no `errors` — unlike a well-formed body that merely fails\n  the schema, which is `400 request.validation_failed` in the normal envelope. (Schema\n  failures were listed in this section in earlier releases, as an `application/json` body\n  with no `code`. They are no longer an exception; see above. Unrouted paths and unhandled\n  faults were also listed here as `text/plain` in earlier releases; they now carry the\n  envelope — `404 request.route_not_found` and `500 internal_error` in the table above.)\n- **The browser OAuth callbacks** report a *provider-side* failure by redirecting (`302`)\n  to your `return_url` with error parameters in the query string, because the user agent at\n  that point is a human's browser and a JSON body would be a dead end. They do still return\n  a problem body — `410 connect.session_not_found` — when the session itself is unknown,\n  expired, already completed, or its token does not match, so a browser can land on raw\n  JSON. Handle both.\n\nErrors from the operator console are not listed and are not part of this contract.\n\n## Rate limits and the 429 contract\n\nTwo independent inbound limiters guard `/v1`. They are distinguished by the\nproblem-details `code` on the `429` body, so a client can tell which one it hit:\n\n- **`rate_limit.shield_shed`** — a pre-authentication shield keyed on the **client IP\n  address**, at **15000 requests per 60 seconds**. It runs before HMAC verification and\n  exists to shed floods, not to express the product contract.\n- **`rate_limit.quota_exceeded`** — the per-application quota keyed on the\n  **authenticated `app_id`**, at **3000 requests per 60 seconds**. This is the number to\n  design against.\n\n**The counters are per Cloudflare location, not global.** Cloudflare's rate-limiting\nprimitive keeps a separate, eventually-consistent counter in each location that serves\nyour traffic, so the effective worldwide ceiling is roughly `limit × the number of\nlocations your traffic reaches` — a caller spread across many regions can exceed the\nnominal number before seeing a `429`. Treat both limits as DoS and runaway-loop\nceilings rather than a billing-grade quota; do not build metering, billing or capacity\nplanning on them.\n\n### Retrying a 429 — the two codes differ\n\n**`rate_limit.shield_shed` is safe to replay byte-for-byte.** It is decided before\nauthentication, so the request's nonce (if it carried one) was not consumed. Wait out\n`Retry-After`, add jitter, and resend the identical signed request.\n\n**`rate_limit.quota_exceeded` is not.** Read on — this is the one that surprises people.\n\nA `rate_limit.quota_exceeded` rejection happens **after** HMAC verification, which means\nthe request's `X-IR-Nonce` has already been consumed. Replaying the identical signed\nrequest therefore returns `401 hmac.nonce_reused` — not a second `429`, and not a\nsuccess. **Every retry MUST be re-signed with a fresh `X-IR-Nonce` and a fresh\n`X-IR-Timestamp`.** Apply jittered backoff so a fleet of retrying clients does not\nresynchronize onto the next window boundary.\n\n### Response headers\n\nA `429` carries `Retry-After`. Its value is the **full window length in seconds (60)**,\nnot the time remaining in the current window: the underlying rate-limiting binding\nreports only whether a request was allowed and exposes no reset timestamp, so any\nseconds-until-reset value the runtime printed would be a guess presented as a fact. One\nfull window is correct-or-conservative instead.\n\n`RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` are deliberately **not**\nsent. The binding exposes no remaining-quota or reset value to report, and because the\ncounters are per-location any single global remaining-quota number would be actively\nmisleading. The limit actually in force is stated in the `429` problem-details body,\nwhere it cannot be mistaken for a standardised live counter.\n\n### Paths that are not rate limited\n\n`GET /v1/health` (liveness probe), `GET /v1/connections/oauth/callback` (the end-user\nbrowser redirect, where a `429` would\nbreak a human's connect flow irrecoverably) and `GET /v1/openapi.json` (SDK codegen and\nCI tooling) are exempt by design. Provider webhook ingest lives outside `/v1`, under\n`/webhooks/*`, and is not covered by these limiters at all.\n\n## Idempotency keys\n\n`POST /v1/operations/execute` accepts an idempotency key, in the `idempotency_key` body\nfield or the `Idempotency-Key` header. The body field wins if both are sent.\n\n**It is required for any operation whose risk level is financial.** Omitting it there is a\n`400 idempotency_key_required` — the runtime will not accept a money-moving instruction it\ncannot recognise as a repeat.\n\n### What it guarantees\n\nAn original (non-replay) job claims its key **per connection**. Re-sending the same\nrequest identity returns `202` with the **original** `job_id` instead of creating another\noriginal job.\n\nFor Invoice CREATE, `same request identity` is deliberately strict: the canonical payload\nfingerprint, pinned QuickBooks company, recipe key and version, operation, object type, and\ninternal object id must all match the original job. Missing historical fingerprint evidence\nor any mismatch returns `409 idempotency_key_conflict`; no replacement job is created.\n\nFor non-Invoice operations, the route compares the recipe, operation, object type, and\ninternal object id, but it does not currently fingerprint the payload. Never reuse their\nkeys with changed input: a matching route identity returns the original job.\n\nFor the request-id-proven Invoice CREATE path, the runtime also persists a stable\nprovider-side request token derived from the full Invoice identity and caller key. Bounded\nautomatic recovery reuses that token. If the original provider-call clock is too old,\nmissing, or invalid, the runtime fails closed for operator reconciliation instead of\npromising that an automatic reissue is still duplicate-safe.\n\n### Choosing and preserving a key\n\nDerive keys from the business event they represent (an invoice id plus the operation, say),\nand deliberately exclude the recipe version. Never derive one from a retry counter, a\ntimestamp, or a fresh UUID per attempt — a per-attempt UUID switches route-level\ndeduplication off while looking like it is on.\n\nOnce a key has been issued, preserve it exactly, including a historical version suffix.\nDo not move a queued or historical v1/v2 attempt to v3 by changing its key, payload, or\nrecipe version. Reconcile an ambiguous prior attempt with an operator instead of creating\na new financial identity.\n\nScope and lifetime, precisely:\n\n- **The original-job claim is scoped to the connection.** The same key against a different\n  `connection_id` is a different route-level identity. That does not bypass downstream\n  business-object reservations, which also bind app, tenant, provider, object type, and\n  internal object identity.\n- **The route claim lasts for the original job's retained lifetime.** There is no separate\n  expiry window. If retention later removes that job, the route index no longer carries\n  the claim, but a downstream business-object reservation may still exist. Never infer\n  from job retention alone that an old financial event is safe to resubmit or re-key.\n- **Operator replays preserve rather than free the identity.** A replay job is excluded from\n  the original-job unique index so it can copy the original key and metadata. The original\n  job remains the public key claimant while retained, and Invoice replay also shares the\n  persisted reservation and payload fingerprint. This is not permission to submit a new\n  business event under that key.\n- **Without a key there is no route-level deduplication.** For non-financial operations the\n  field is optional, and every call without one creates a distinct job; operation-specific\n  downstream reservation rules may still prevent a duplicate provider object.\n\nA key does not make retrying free at the HTTP layer: the signed request still needs a fresh\nnonce and timestamp on every attempt (see “Retrying a 429”). Idempotency and replay\nprotection are separate mechanisms solving separate problems.\n\n## Enum stability\n\nAdding a member to a closed enum is a breaking change, which on this API costs a URL\nversion bump. Most enums here describe state machines the runtime owns and expects to\nextend, so they are declared **extensible**: marked `x-ir-enum-open: true` and carrying an\n“Extensible enum” note in their description.\n\n**If a field is marked extensible, tolerating unknown values is part of the contract.** Map\na value you do not recognise to an explicit “unrecognised” case and carry on; do not throw,\nand do not reject the response. A client that hard-fails on an unseen member has no claim\nwhen a MINOR release adds one. Language enums that raise on an unknown value — a PHP backed\nenum's `from()`, a Python `Enum(value)`, a strict schema validator — are the usual way this\nbreaks; prefer the non-raising variant at the boundary.\n\nAn enum with no such marking is closed, and gaining a member would be a MAJOR release.\nToday every enum in this spec is extensible.","x-ir-error-codes":[{"code":"quickbooks.invoice_update_payload_invalid","status":400,"cause":"The frozen Invoice edit has invalid request/change identity, source, revision, existing line IDs, CAD totals or supported fields. No job is admitted.","action":"Correct the explicitly approved change before first submission. Preserve any existing body, business key and SyncToken after uncertain admission.","retry":"never"},{"code":"quickbooks.invoice_update_source_mismatch","status":400,"cause":"The original Invoice edit names a company that differs from the scoped native QuickBooks connection. No job is admitted.","action":"Inspect the original company and connection mapping. Do not repin or re-key an edit whose provider outcome is uncertain.","retry":"never"},{"code":"payment.invalid_input","status":400,"cause":"The linked Payment has invalid positive decimal amounts, native references, Invoice linkage or update selectors. No job is admitted.","action":"Correct the original business input before its first submission. Preserve any existing immutable request and reconcile uncertainty instead of changing its key.","retry":"never"},{"code":"quickbooks.adjustment_payload_invalid","status":400,"cause":"The source-bound credit, refund, application or void envelope is invalid. No job is admitted.","action":"Use the explicit operation-specific envelope and original native source identity. Never re-key a return whose provider outcome is uncertain.","retry":"never"},{"code":"master.not_found","status":404,"cause":"The scoped exact-name or provider-id lookup returned no current record, or the selected ERP mapping does not exist.","action":"Choose a verified provider id or retain the missing mapping for investigation; re-sign reads.","retry":"after-resign"},{"code":"master.rate_limited","status":429,"cause":"The shared provider bucket, serialized read slot or provider response refused this controlled master lookup.","action":"Wait for Retry-After or bounded backoff, then re-sign the same scoped read.","retry":"after-resign"},{"code":"master.provider_unconfirmed","status":502,"cause":"The provider read, native grant, operation configuration or response fields could not be confirmed. No mapping is updated.","action":"Preserve the mapping and command; re-sign one read after backoff and investigate continued refusal.","retry":"once-then-escalate"},{"code":"master.ambiguous","status":409,"cause":"Exact-name lookup returned multiple provider records; choosing one automatically would make the ERP mapping ambiguous.","action":"Read and bind one explicit verified provider id in the same company.","retry":"never"},{"code":"master.unsupported","status":409,"cause":"The provider Item or current tax definition is outside the supported bounded master-data shape or effective-rate rules.","action":"Use a supported current definition and verified provider id; do not invent rates.","retry":"never"},{"code":"master.connection_unconfirmed","status":409,"cause":"The scoped QuickBooks connection, live native grant or company binding could not be confirmed before this master operation.","action":"Refresh the selected connection and confirm its exact company before retrying.","retry":"never"},{"code":"master.identity_changed","status":409,"cause":"The company or native token generation changed during the controlled read. Returned provider evidence is discarded.","action":"Re-read the selected connection and re-sign the original scoped read.","retry":"after-resign"},{"code":"master.reconciliation_mismatch","status":409,"cause":"The explicit provider id does not match the pending immutable command fields, ERP identity or advancing update version.","action":"Investigate the original command and provider evidence; do not change its identity to retry.","retry":"never"},{"code":"master.mapping_conflict","status":409,"cause":"A stable provider binding, pending command or concurrent revision prevents the requested local master binding.","action":"Read the current mapping and reconcile its exact pending command before changing any fields.","retry":"never"},{"code":"master.invalid_command","status":400,"cause":"Master admission requires one strict immutable command whose command id, entity, recipe and requested object identity agree. No job is created.","action":"Correct the command envelope before preparing and submitting a new reviewed request.","retry":"never"},{"code":"master.company_mismatch","status":409,"cause":"The frozen master command company differs from the current scoped QuickBooks connection company at admission. No job is created.","action":"Select the intended connection and verify the company before submitting this command.","retry":"never"},{"code":"master.invalid_query","status":400,"cause":"The master lookup or mapping read contains unknown or duplicate query keys. No provider HTTP or local update is attempted.","action":"Use one tenant_id and one documented selector, then sign a fresh request.","retry":"never"},{"code":"usage.window_too_dense","status":422,"cause":"The bounded job-usage window contains more than 10000 retained jobs or 200 provider/operation groups. No partial counts are returned.","action":"Choose a shorter window_minutes and send a freshly signed request.","retry":"never"},{"code":"financial_write_tax_declaration_missing","status":400,"cause":"Invoice admission requires tax_declarations with exactly one declaration for every provider sales line; the sibling or a line declaration is missing. No job is created.","action":"Add every required tax declaration, then re-sign and resubmit the corrected request.","retry":"never"},{"code":"financial_write_tax_payload_invalid","status":400,"cause":"The Invoice-only create payload is malformed: provider-native customer/item/tax references are missing, declarations are invalid, automatic customer/items inputs are present, or the Invoice contains Id, SyncToken or sparse update selectors. No job is created.","action":"Use existing QuickBooks references in the selected company, remove helper inputs and update selectors, correct the document or declarations, then re-sign and resubmit.","retry":"never"},{"code":"financial_write_tax_path_unsupported","status":400,"cause":"Invoice admission received request TxnTaxDetail, a calculation mode other than TaxExcluded, or a line other than top-level SalesItemLineDetail. No job is created.","action":"Use the supported TaxExcluded sales-line calculation path, then re-sign and resubmit the corrected request.","retry":"never"},{"code":"hmac.signature_invalid","status":401,"cause":"Any failure in signature verification: a missing or malformed `X-IR-App-Id` / `X-IR-Key-Id` / `X-IR-Nonce` / `X-IR-Signature` / `X-IR-Timestamp`, a timestamp more than 300s from the runtime's clock, a nonce outside 22–64 characters, an unknown or revoked key, or a signature that does not match. These are deliberately indistinguishable so the response cannot be used to enumerate valid app/key pairs.","action":"Re-derive the canonical payload (ADR-007 §4) and check it against the published HMAC test vectors, verify the app and key are active, and check your clock. The runtime records the specific reason in its own logs against your `request_id`.","retry":"never"},{"code":"hmac.nonce_reused","status":401,"cause":"The `X-IR-Nonce` on this request has already been accepted. Nonces are single-use; this is the replay defence working as designed.","action":"Never retry a signed request byte-for-byte. Generate a fresh nonce and timestamp and re-sign. See “Retrying a 429” — this is the error a naive retry after a quota 429 produces.","retry":"after-resign"},{"code":"rate_limit.shield_shed","status":429,"cause":"The pre-authentication shield, keyed on client IP address. It runs before HMAC verification and exists to shed floods, not to express the product contract.","action":"Wait out `Retry-After` (the full window, 60s), add jitter, resend. Because the decision happens before authentication, the nonce was not consumed. **Byte-identical replay is only safe inside the 300s signature timestamp window** — roughly four `Retry-After` cycles. Past that the original `X-IR-Timestamp` falls outside the window and you get `401 hmac.signature_invalid`, which is indistinguishable from a signing bug. Re-sign if you have been backing off for more than a couple of minutes.","retry":"safe"},{"code":"rate_limit.quota_exceeded","status":429,"cause":"The per-application quota, keyed on the authenticated `app_id`. It is evaluated AFTER HMAC verification.","action":"Wait out `Retry-After`, add jitter, and **re-sign with a fresh nonce and timestamp**. The rejected request already consumed its nonce, so replaying it verbatim returns `401 hmac.nonce_reused`.","retry":"after-resign"},{"code":"authz.ip_not_allowed","status":403,"cause":"The service key you signed with is restricted to a set of IP ranges, and the address this request arrived from is not in any of them. Evaluated AFTER signature verification, so it never reveals whether a key exists. It also fires when the runtime cannot determine your source address at all, in which case a restricted key refuses every request rather than silently dropping the restriction.","action":"Send from an allowlisted address, or ask your platform operator to widen the key's allowed IP ranges (they can read the exact address the request arrived from off your `request_id`). Retrying from the same place fails identically. The rejected request already consumed its nonce, so any retry must be re-signed with a fresh nonce and timestamp.","retry":"never"},{"code":"authz.provider_not_enabled","status":403,"cause":"The provider named in `provider_key` is not enabled for your application.","action":"Ask your platform operator to enable the provider for the application.","retry":"never"},{"code":"authz.tenant_mismatch","status":403,"cause":"The `tenant_id` is not a tenant of the authenticated application.","action":"Send a `tenant_id` that belongs to your app. A tenant of another application is never visible to you, by design.","retry":"never"},{"code":"connection.not_found","status":404,"cause":"The connection id or QuickBooks company identity is not present in the authenticated app and tenant scope. The runtime deliberately returns the same response for a foreign company identity.","action":"Check the connection, tenant and QuickBooks company ids before re-signing the request.","retry":"never"},{"code":"calendar.grant_not_found","status":404,"cause":"The Calendar grant id is not present in the authenticated app and tenant scope. Foreign grant ids are deliberately indistinguishable from unknown ids.","action":"Check the tenant and grant ids before re-signing the request.","retry":"never"},{"code":"calendar.resource_not_found","status":404,"cause":"The Calendar resource binding id is not present in the authenticated app and tenant scope. Foreign resource ids are deliberately indistinguishable from unknown ids.","action":"List the scoped grant's current resources and use a returned resource id.","retry":"never"},{"code":"calendar.page_not_found","status":404,"cause":"The Calendar page id is not present in the authenticated app and tenant scope. Foreign page ids are deliberately indistinguishable from unknown ids.","action":"Use the page URL from the completed Calendar job manifest, or start a new root read if it was lost.","retry":"never"},{"code":"calendar.continuation_not_found","status":404,"cause":"The opaque Calendar continuation handle is absent from the authenticated app and tenant scope or does not identify a retained page traversal.","action":"Start a new root Calendar discovery or event read; do not substitute or guess another traversal handle.","retry":"never"},{"code":"calendar.invalid_input","status":400,"cause":"The Calendar discovery or event-read payload does not match the strict operation contract, identity binding, page-size limit or event-window rules.","action":"Correct the request fields and immutable grant or resource identity, then re-sign and submit a new request.","retry":"never"},{"code":"calendar.unsupported_representation","status":400,"cause":"The Calendar event read requests a representation that the runtime does not preserve truthfully; only series-and-exceptions is supported.","action":"Use the series-and-exceptions representation and submit a newly signed event-read request.","retry":"never"},{"code":"calendar.invalid_continuation","status":400,"cause":"The retained Calendar continuation state no longer validates against its protected scope, operation, query or bounded private-state schema.","action":"Discard the continuation handle and start a new root Calendar discovery or event read.","retry":"never"},{"code":"calendar.resource_denied","status":403,"cause":"Persisted Calendar grant, principal, registration, credential or resource identity does not match the scoped binding required by the request.","action":"Do not substitute public ids across grants or tenants. Fetch current scoped status and resource metadata before starting a new operation.","retry":"never"},{"code":"calendar.resource_detached","status":403,"cause":"The Calendar resource binding exists in the authenticated scope but has already been detached and cannot authorize a provider read.","action":"List the grant's current resources and select an active binding before starting another read.","retry":"never"},{"code":"calendar.grant_revoked","status":403,"cause":"The persisted Calendar grant is disconnected or revoked, so it cannot authorize discovery or event reads.","action":"Reconnect the Calendar account and repeat discovery before selecting resources or reading events.","retry":"never"},{"code":"calendar.grant_disabled","status":403,"cause":"The Calendar grant or its application provider configuration is disabled for discovery and event reads.","action":"Ask the platform operator to enable Calendar access, then fetch grant status before retrying.","retry":"never"},{"code":"calendar.insufficient_scopes","status":403,"cause":"The Calendar token or application policy does not include every scope required by the requested capability.","action":"Reconnect with the required Calendar scopes, then repeat discovery before starting provider reads.","retry":"never"},{"code":"calendar.reconnect_required","status":409,"cause":"The Calendar connection or token lifecycle is not currently active enough to authorize provider discovery or reads.","action":"Reconnect the Calendar account, then fetch current grant status and repeat discovery before retrying.","retry":"never"},{"code":"calendar.generation_changed","status":409,"cause":"The persisted Calendar callback or token generation no longer matches the generation captured by this request or resource binding.","action":"Fetch current grant status, repeat discovery and create a new explicit selection or read request.","retry":"never"},{"code":"calendar.selection_changed","status":409,"cause":"A selection or detach retry no longer names the same resource binding, grant generation or idempotent intent.","action":"Fetch the current selected-resource list. Preserve the original idempotency key only for an exact retry; otherwise start a new explicit intent.","retry":"never"},{"code":"calendar.page_pending","status":409,"cause":"The scoped Calendar page exists, but its owning job has not completed and published the matching immutable result manifest.","action":"Poll the owning job until it completes, then fetch the page URL from that job's result manifest.","retry":"after-resign"},{"code":"calendar.page_expired","status":410,"cause":"The authenticated Calendar page traversal reached its fixed root expiry or its retained payload was pruned.","action":"Start a new root Calendar discovery or event read; page access cannot extend or revive the traversal.","retry":"never"},{"code":"calendar.discovery_expired","status":410,"cause":"The Calendar discovery page is older than the bounded selection-proof window and can no longer authorize a resource choice.","action":"Run Calendar discovery again and select the resource from the newly returned discovery page.","retry":"never"},{"code":"calendar.continuation_expired","status":410,"cause":"The Calendar continuation belongs to a root traversal whose fixed expiry elapsed or whose retained private provider state was pruned.","action":"Start a new root Calendar discovery or event read; the expired continuation cannot be renewed or replayed.","retry":"never"},{"code":"calendar.state_unavailable","status":503,"cause":"The runtime cannot safely interpret the persisted Calendar lifecycle or authorization state.","action":"Retry once with a freshly signed request, then escalate with the request id.","retry":"once-then-escalate"},{"code":"oauth.lifecycle_busy","status":409,"cause":"A callback exchange, token refresh, reconnect, provider execution or a different disconnect request currently owns this connection's durable OAuth lifecycle generation.","action":"Wait briefly, then re-sign and retry. For disconnect, retain the original idempotency key.","retry":"after-resign"},{"code":"oauth.disconnect_retryable","status":503,"cause":"Intuit revocation or the local disconnected projection did not complete. The encrypted retry material remains fenced from callback, refresh and provider execution paths.","action":"Re-sign and retry with the same disconnect idempotency key. Escalate if one retry still fails.","retry":"once-then-escalate"},{"code":"request.payload_too_large","status":413,"cause":"The raw request body exceeded the `/v1` body limit. Enforced before authentication, so it fires without the body ever being hashed.","action":"Send a smaller body. Chunking at the transport layer does not help — the limit is on the request, not on a single frame.","retry":"never"},{"code":"request.validation_failed","status":400,"cause":"The request did not match the operation's declared schema for its body, query, path, header or cookie parameters — a missing or empty required field, a wrong type, or a query parameter repeated where one value is expected. Raised by the shared validation hook before the handler runs, so no side effect has occurred. Properties the schema does not declare are IGNORED rather than rejected, so sending extra fields is not what caused this. A body that is not parseable JSON is rejected EARLIER, by the framework, and returns `400 text/plain` instead — see “Errors that are not problem+json”.","action":"Read the `errors` array: each entry names the offending `field`, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`). Fix the request and resend; on a signed route, re-sign it. Switch on the top-level `code` — `errors[].code` is a validator diagnostic label and is not stable. The list is capped at 20 entries, so a fixed request may surface further ones.","retry":"never"},{"code":"payload_too_large","status":400,"cause":"On `POST /v1/operations/execute`, the serialized `payload` field exceeded the maximum job-input size. Distinct from `request.payload_too_large`, which is the whole-body limit at a different layer.","action":"Reduce `payload`, or reference bulk data out of band.","retry":"never"},{"code":"idempotency_key_required","status":400,"cause":"The requested operation has `risk_level = financial` and no idempotency key was supplied in either `idempotency_key` or the `Idempotency-Key` header.","action":"Supply a stable key derived from the business event, not a fresh UUID per attempt. See “Idempotency keys”.","retry":"never"},{"code":"connect.return_url_not_allowed","status":400,"cause":"`return_url` is unparseable, is not `https`, or its scheme+host is not an exact match in the application's configured callback allowlist. Suffix matching is deliberately not used.","action":"Use an allowlisted return URL, or have the operator add yours to the allowlist.","retry":"never"},{"code":"connect.scopes_not_permitted","status":400,"cause":"The requested `scopes` are not a subset of the scopes configured for this provider.","action":"Request a narrower scope set.","retry":"never"},{"code":"quickbooks.consent_frozen","status":409,"cause":"New production QuickBooks consent is temporarily paused while the provider authorization boundary is reviewed. No connect session or authorization URL is created.","action":"Do not retry automatically. Wait for an operator notice before starting a new QuickBooks Connect or Reconnect ceremony.","retry":"never"},{"code":"connect.session_not_found","status":410,"cause":"The connect session does not exist, has expired, was already completed, or its token did not match. The four are collapsed into one response on purpose — distinguishing them would let a caller probe session state.","action":"Start a new connect session.","retry":"never"},{"code":"sync_profile.not_found","status":404,"cause":"No sync profile with that id is reachable for the authenticated application and tenant. A profile belonging to another tenant reads as not-found rather than forbidden.","action":"Check `sync_profile_id` and `tenant_id`.","retry":"never"},{"code":"mapping.unknown_object_type","status":400,"cause":"The dry-run could not shape a provider payload because the object type stored **on the sync profile** is not one the provider adapter knows. This is a stored-configuration problem, not a problem with the request body.","action":"Have the operator correct the sync profile. Changing the request will not help.","retry":"never"},{"code":"recipe_not_found","status":404,"cause":"No recipe is deployed for the `recipe_key` + `recipe_version` pair supplied.","action":"Use a deployed pair. Recipe versions are pinned deliberately so a runtime deploy cannot silently change the steps you asked for.","retry":"never"},{"code":"operation_admission_mismatch","status":400,"cause":"The requested provider or object type conflicts with the deployed recipe, or the recipe's operation is associated with a different provider in the catalog. No job is created or published.","action":"Use the exact provider and object type declared by the deployed recipe. If the catalog association is wrong, have an operator correct the runtime configuration rather than changing the request.","retry":"never"},{"code":"operation_app_not_permitted","status":403,"cause":"The authenticated runtime app is not permitted to admit this recipe. Recipe product labels are bound explicitly to routable runtime app ids, and shared recipes still require a routable app.","action":"Use a recipe permitted for the authenticated app. Do not retry under another tenant or alter the recipe version to bypass this application boundary.","retry":"never"},{"code":"recipe_version_retired","status":409,"cause":"The requested or replayed recipe version is still registered for historical inspection but is below the minimum safe version for that recipe.","action":"Submit a new job using the minimum version named in the response. Do not replay the retired job unchanged.","retry":"never"},{"code":"invoice_identity_unavailable","status":409,"cause":"Invoice admission cannot establish the destination QuickBooks company.","action":"Connect the intended company and resubmit. Historical writes without immutable identity require operator review; never re-key an uncertain write.","retry":"never"},{"code":"idempotency_key_conflict","status":409,"cause":"The idempotency key belongs to a job with a different payload, company, recipe version, operation, or object identity, or historical payload evidence is unavailable.","action":"Use the original payload and identity exactly. An existing internal invoice is immutable; do not change keys to bypass a conflict. Missing historical evidence requires operator review.","retry":"never"},{"code":"operation_not_enabled","status":403,"cause":"The provider operation exists in the catalog but is not enabled. Financial-write operations ship disabled and stay disabled until a documented operator sign-off has been filed.","action":"Ask your platform operator to enable the operation. This is a governance gate, not a transient condition.","retry":"never"},{"code":"operation_not_attestable","status":403,"cause":"The provider operation is disabled AND can never be enabled: no §6.5 attestation may cover it, so the governance gate that would enable it refuses too. Distinct from `operation_not_enabled`, which is a state an operator can change.","action":"Do not ask an operator to enable this operation and do not retry — both the enable endpoint and the sign-off route refuse the request. Treat it as permanently unavailable for this runtime version.","retry":"never"},{"code":"tenant_operation_not_enabled","status":403,"cause":"The provider operation is enabled in the catalog, but not for YOUR tenant. Financial-write operations are rolled out one tenant at a time, so an operation can be live for another tenant of the same application and still be closed for yours.","action":"Ask your platform operator to enable the operation for this tenant. Distinct from `operation_not_enabled`, which means the operation is disabled for everyone — this is a per-tenant governance gate, not a transient condition.","retry":"never"},{"code":"tenant_enablement_unavailable","status":503,"cause":"The runtime could not read the per-tenant enablement state for this operation, so it refused rather than guess. This is an infrastructure condition, not a permission decision.","action":"Retry with a FRESHLY SIGNED request (new `X-IR-Nonce` + `X-IR-Timestamp`) and ordinary backoff. Replaying the identical signed request fails `hmac.nonce_reused`: this refusal is raised inside the handler, after the auth middleware has already consumed the nonce. No `Retry-After` header is sent on this response. If it persists, escalate — the runtime is deliberately refusing financial writes while the rollout state is unknown.","retry":"after-resign"},{"code":"sync_job.not_found","status":404,"cause":"No sync job with that id exists for the authenticated application and tenant. Cross-tenant reads are reported as not-found.","action":"Check the job id and `tenant_id`.","retry":"never"},{"code":"calendar.replay_requires_new_read","status":409,"cause":"The source job belongs to a paginated Google Calendar read. Its continuation and authorization state are managed by the Calendar page flow, so generic sync-job replay is refused without creating a replacement job.","action":"Start a new root Calendar read with a newly signed request. Do not replay the stored page job or reuse its continuation as a generic sync-job input.","retry":"never"},{"code":"sync_job.not_replayable","status":409,"cause":"The job cannot be replayed, or publication of its replacement was deferred or refused. Causes include missing recipe/input, unavailable historical Invoice identity, a payload conflict, or an unconfirmed delivery outcome. The original dead-letter record stays open when replacement publication is not confirmed.","action":"If instance contains a replacement job polling URL, poll that job and inspect its failure code; do not repeatedly replay the original or create an Invoice with a new key. Escalate terminal or unconfirmed outcomes to your platform operator for reconciliation. A deferred replacement may recover through the publication sweep.","retry":"never"},{"code":"sync_job.replay_in_progress","status":409,"cause":"The source job is still queued or processing. Its step results are not stable yet, so creating a replay could run beside the original or skip a verifier whose latest run later fails.","action":"Wait until the source job reaches a terminal status, fetch it again, then submit a newly signed replay request. Do not create a second operation while the original is active.","retry":"after-resign"},{"code":"request.route_not_found","status":404,"cause":"No route matched the path + method, and every path-scoped middleware let the request through. The runtime does not emit `405`: a wrong method on a known path produces this code — but only after the path's middleware runs, because middleware is bound to the PATH, not the method. On a signed path, an UNSIGNED wrong-method request therefore surfaces as `401 hmac.signature_invalid` (and an over-limit one as `429`) before the router's not-found can render.","action":"Check the path and method against the spec. A resource that exists but is not yours produces a resource-specific 404 (`sync_job.not_found`, `sync_profile.not_found`) instead, so this code always means the URL or verb is wrong.","retry":"never"},{"code":"internal_error","status":500,"cause":"An unhandled fault in the runtime. This is also what a request carrying an unrecognised `app_id` currently produces on some paths.","action":"Retry once with backoff, then escalate quoting `request_id`. For non-idempotent operations, confirm whether the write landed before retrying — a 500 does not mean nothing happened. Do not loop: a persistent 500 is an incident, not congestion.","retry":"once-then-escalate"}]},"servers":[{"url":"https://staging.integrationengine.ca","description":"Provisioned staging"},{"url":"http://localhost:8787","description":"Local Wrangler dev"}],"security":[{"HmacSig":[]}],"tags":[{"name":"Health","description":"Liveness probe."},{"name":"Connections","description":"Tenant ↔ provider connection lifecycle."},{"name":"Mappings","description":"Tenant field mappings, draft/validate/publish lifecycle."},{"name":"Usage","description":"Retained job creation cohorts and current outcomes; provider HTTP attempts unavailable."},{"name":"QuickBooks","description":"Scoped company master-data reads and stable ERP mappings."},{"name":"SyncJobs","description":"Inspect sync job status and steps."}],"components":{"securitySchemes":{"HmacSig":{"type":"apiKey","in":"header","name":"X-IR-Signature","description":"base64url HMAC-SHA256 signature. Compute it server-side from the raw service secret; never send that secret as a header or expose it to a browser. See ADR-007 §4 for canonicalization."}},"schemas":{"CalendarPageManifest":{"type":"object","properties":{"kind":{"type":"string","enum":["calendar-page"]},"version":{"type":"number","enum":[1]},"page_id":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$"},"page_url":{"type":"string","minLength":1},"expires_at":{"type":"integer","minimum":0}},"required":["kind","version","page_id","page_url","expires_at"],"additionalProperties":false},"CalendarDiscoveryInitialPayload":{"type":"object","properties":{"grant_id":{"type":"string","minLength":1},"page_size":{"type":"integer","minimum":1,"maximum":10,"default":10}},"required":["grant_id"],"additionalProperties":false},"CalendarDiscoveryResumePayload":{"type":"object","properties":{"grant_id":{"type":"string","minLength":1},"continuation_handle":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$"}},"required":["grant_id","continuation_handle"],"additionalProperties":false},"CalendarEventsInitialPayload":{"type":"object","properties":{"grant_id":{"type":"string","minLength":1},"resource_id":{"type":"string","minLength":1},"representation":{"type":"string","enum":["series-and-exceptions"]},"time_min":{"type":"string","minLength":1},"time_max":{"type":"string","minLength":1},"page_size":{"type":"integer","minimum":1,"maximum":10,"default":10}},"required":["grant_id","resource_id","representation","time_min","time_max"],"additionalProperties":false},"CalendarEventsResumePayload":{"type":"object","properties":{"grant_id":{"type":"string","minLength":1},"resource_id":{"type":"string","minLength":1},"continuation_handle":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$"}},"required":["grant_id","resource_id","continuation_handle"],"additionalProperties":false},"CreateConnectSessionResponse":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"authorize_url":{"type":"string","format":"uri"},"expires_at":{"type":"integer"},"connection_id":{"type":["string","null"]}},"required":["session_id","authorize_url","expires_at","connection_id"]},"ProblemDetails":{"type":"object","properties":{"type":{"type":"string","description":"URI reference identifying the problem type."},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":["string","null"]},"code":{"type":"string"},"request_id":{"type":"string"},"correlation_id":{"type":["string","null"]},"instance":{"type":["string","null"]},"errors":{"type":"array","items":{"$ref":"#/components/schemas/FieldError"},"description":"Per-field breakdown, present when a problem is field-shaped — today that means request-schema validation failures on `/v1` (`400 request.validation_failed`) and nothing else. Omitted entirely — never `[]` — otherwise. Capped at 20 entries, so it may not list every failing field."}},"required":["type","title","status","detail","code","request_id","correlation_id","instance"]},"FieldError":{"type":"object","properties":{"field":{"type":"string","description":"The offending field, prefixed with where the value was read from: `body.`, `query.`, `path.`, `header.` or `cookie.` (e.g. `body.tenant_id`). Array elements are indexed (`body.lines[0].amount`). A bare prefix means the whole target was rejected."},"code":{"type":"string","description":"Diagnostic label from the validator's own issue vocabulary (e.g. `invalid_type`, `too_small`). Useful in a log; **not** the stable branch point and not covered by the API stability policy — switch on the top-level `code` instead."},"message":{"type":"string","description":"Human-facing description of what was wrong with this field. May be reworded in any release, and is truncated when long."}},"required":["field","code","message"]},"CreateConnectSessionRequest":{"type":"object","properties":{"tenant_id":{"type":"string","minLength":1},"provider_key":{"type":"string","enum":["quickbooks"]},"return_url":{"type":"string","format":"uri"},"scopes":{"type":["array","null"],"items":{"type":"string"}},"state":{"type":["string","null"],"description":"Opaque value echoed back on return."},"actor_user_id":{"type":["string","null"]},"actor_email":{"type":["string","null"],"format":"email"}},"required":["tenant_id","provider_key","return_url"]},"ConnectionsListResponse":{"type":"object","properties":{"connections":{"type":"array","items":{"$ref":"#/components/schemas/ConnectionPublicDTO"}},"next_cursor":{"type":["string","null"]}},"required":["connections","next_cursor"]},"ConnectionPublicDTO":{"type":"object","properties":{"id":{"type":"string"},"provider_key":{"type":"string"},"status":{"type":"string","enum":["connected","reconnect_required","disconnected","disabled"],"x-ir-enum-open":true,"description":"Authoritative runtime lifecycle state for this connection.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"connected_at":{"type":["integer","null"]},"created_at":{"type":"integer"},"updated_at":{"type":"integer"}},"required":["id","provider_key","status","connected_at","created_at","updated_at"]},"DisconnectConnectionRequest":{"type":"object","properties":{"tenant_id":{"type":"string","minLength":1},"provider_key":{"type":"string","enum":["quickbooks"]},"provider_account_id":{"type":"string","minLength":1},"idempotency_key":{"type":"string","minLength":1,"maxLength":128}},"required":["tenant_id","provider_key","provider_account_id","idempotency_key"]},"QuickBooksStatusResponse":{"type":"object","properties":{"app_id":{"type":"string"},"tenant_id":{"type":"string"},"transaction":{"$ref":"#/components/schemas/QuickBooksTransactionStatus"}},"required":["app_id","tenant_id","transaction"]},"QuickBooksTransactionStatus":{"type":"object","properties":{"entity_type":{"type":"string","enum":["Invoice","Payment"],"x-ir-enum-open":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"external_id":{"type":"string"},"sync_token":{"type":["string","null"]},"provider_updated_at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["open","partially_paid","paid","zero_value","active","voided","deleted"],"x-ir-enum-open":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"customer_ref":{"type":["string","null"]},"currency":{"type":["string","null"]},"total_minor":{"type":["integer","null"],"minimum":0,"maximum":9007199254740991},"balance_minor":{"type":["integer","null"],"minimum":0,"maximum":9007199254740991},"unapplied_minor":{"type":["integer","null"],"minimum":0,"maximum":9007199254740991},"payment_funding":{"type":["string","null"],"enum":["cash","credit_application","mixed",null],"x-ir-enum-open":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"cash_applied_minor":{"type":"integer","minimum":0,"maximum":9007199254740991},"credit_memo_allocations":{"type":"array","items":{"type":"object","properties":{"credit_memo_id":{"type":"string"},"amount_minor":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["credit_memo_id","amount_minor"]}},"payment_allocations":{"type":"array","items":{"type":"object","properties":{"payment_id":{"type":"string"},"amount_minor":{"type":"integer","minimum":0,"maximum":9007199254740991},"funding":{"type":["string","null"],"enum":["cash","credit_application","mixed",null],"x-ir-enum-open":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"cash_minor":{"type":"integer","minimum":0,"maximum":9007199254740991},"credit_minor":{"type":"integer","minimum":0,"maximum":9007199254740991},"credit_memo_ids":{"type":"array","items":{"type":"string"}}},"required":["payment_id","amount_minor"]}},"allocations":{"type":"array","items":{"type":"object","properties":{"invoice_id":{"type":"string"},"amount_minor":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["invoice_id","amount_minor"]}},"connection_id":{"type":"string"},"provider_key":{"type":"string","enum":["quickbooks"]},"provider_account_id":{"type":"string"},"internal_id":{"type":["string","null"]},"projection_revision":{"type":"integer","exclusiveMinimum":0},"observed_at":{"type":"string","format":"date-time"}},"required":["entity_type","external_id","sync_token","provider_updated_at","status","customer_ref","currency","total_minor","balance_minor","unapplied_minor","payment_allocations","allocations","connection_id","provider_key","provider_account_id","internal_id","projection_revision","observed_at"]},"QuickBooksMasterRecord":{"type":"object","properties":{"entity_type":{"type":"string","enum":["Customer","Item","TaxCode","TaxRate"],"x-ir-enum-open":true,"description":"Supported master entity; tax entities have no write operation.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either."},"external_id":{"type":"string","pattern":"^\\d{1,32}$"},"sync_token":{"type":["string","null"]},"active":{"type":"boolean"},"name":{"type":"string"},"provider_updated_at":{"type":["string","null"]},"customer_currency":{"type":["string","null"]},"email":{"type":["string","null"]},"item_type":{"type":["string","null"]},"income_account_id":{"type":["string","null"]},"unit_price_minor":{"type":["integer","null"]},"description":{"type":["string","null"]},"account_type":{"type":["string","null"]},"sales_tax_rates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^\\d{1,32}$"},"name":{"type":"string"},"rate_value":{"type":"number"}},"required":["id","name","rate_value"]}},"rate_value":{"type":["number","null"]},"app_id":{"type":"string"},"tenant_id":{"type":"string"},"connection_id":{"type":"string"},"provider_account_id":{"type":"string","pattern":"^\\d{1,32}$"},"observed_at":{"type":"string","format":"date-time"}},"required":["entity_type","external_id","sync_token","active","name","provider_updated_at","customer_currency","email","item_type","income_account_id","unit_price_minor","description","account_type","sales_tax_rates","rate_value","app_id","tenant_id","connection_id","provider_account_id","observed_at"]},"QuickBooksMasterMapping":{"type":"object","properties":{"app_id":{"type":"string"},"tenant_id":{"type":"string"},"connection_id":{"type":"string"},"provider_account_id":{"type":"string","pattern":"^\\d{1,32}$"},"erp_id":{"type":"string"},"state":{"type":"string","enum":["active","inactive","drifted","missing","pending"],"x-ir-enum-open":true,"description":"Stable mapping observation state; preserve an unrecognised state without claiming active verification.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"revision":{"type":"integer"},"pending_command_id":{"type":["string","null"]},"record":{"allOf":[{"$ref":"#/components/schemas/QuickBooksMasterRecord"},{"type":["object","null"]}]}},"required":["app_id","tenant_id","connection_id","provider_account_id","erp_id","state","revision","pending_command_id","record"]},"CalendarResourcesListResponse":{"type":"object","properties":{"resources":{"type":"array","items":{"$ref":"#/components/schemas/CalendarResource"}},"next_cursor":{"type":["string","null"]}},"required":["resources","next_cursor"],"additionalProperties":false},"CalendarResource":{"type":"object","properties":{"resource_id":{"type":"string","minLength":1},"grant_id":{"type":"string","minLength":1},"calendar_id":{"type":"string","minLength":1},"status":{"type":"string","enum":["selected","detached"],"x-ir-enum-open":true,"description":"Lifecycle state of this scoped Calendar resource binding.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"created_at":{"type":"integer"},"updated_at":{"type":"integer"}},"required":["resource_id","grant_id","calendar_id","status","created_at","updated_at"],"additionalProperties":false},"SelectCalendarResourceRequest":{"type":"object","properties":{"tenant_id":{"type":"string","minLength":1},"discovery_page_id":{"type":"string","minLength":1},"calendar_id":{"type":"string","minLength":1},"idempotency_key":{"type":"string","minLength":1,"maxLength":128}},"required":["tenant_id","discovery_page_id","calendar_id","idempotency_key"],"additionalProperties":false},"CalendarGrant":{"type":"object","properties":{"grant_id":{"type":"string","minLength":1},"connection_id":{"type":"string","minLength":1},"provider_key":{"type":"string","enum":["google-calendar"],"x-ir-enum-open":true,"description":"Provider key for this Calendar grant. This implementation emits Google Calendar only.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"principal_id":{"type":"string","minLength":1},"status":{"type":"string","enum":["connected","reconnect_required","disconnected","disabled"],"x-ir-enum-open":true,"description":"Authoritative runtime lifecycle state for this connection.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"capabilities":{"type":"object","properties":{"discover_calendars":{"type":"boolean"},"read_events":{"type":"boolean"}},"required":["discover_calendars","read_events"],"additionalProperties":false},"created_at":{"type":"integer"},"updated_at":{"type":"integer"}},"required":["grant_id","connection_id","provider_key","principal_id","status","capabilities","created_at","updated_at"],"additionalProperties":false},"CalendarRevokeRequest":{"type":"object","properties":{"tenant_id":{"type":"string","minLength":1},"idempotency_key":{"type":"string","minLength":1,"maxLength":128}},"required":["tenant_id","idempotency_key"],"additionalProperties":false},"CalendarDetachRequest":{"type":"object","properties":{"tenant_id":{"type":"string","minLength":1},"idempotency_key":{"type":"string","minLength":1,"maxLength":128}},"required":["tenant_id","idempotency_key"],"additionalProperties":false},"CalendarPublicPage":{"oneOf":[{"type":"object","properties":{"page_id":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$"},"grant_id":{"type":"string","minLength":1},"next_page_handle":{"type":["string","null"],"pattern":"^[A-Za-z0-9_-]{43}$"},"created_at":{"type":"integer","minimum":0},"expires_at":{"type":"integer","minimum":0},"resource_id":{"type":"null"},"representation":{"type":"string","enum":["calendar-list"]},"items":{"type":"array","items":{"type":"object","properties":{"identity":{"type":"object","properties":{"provider":{"type":"string","minLength":1},"appId":{"type":"string","minLength":1},"tenantId":{"type":"string","minLength":1},"grantId":{"type":"string","minLength":1},"principalId":{"type":"string","minLength":1},"calendarId":{"type":"string","minLength":1}},"required":["provider","appId","tenantId","grantId","principalId","calendarId"],"additionalProperties":false},"title":{"type":"string"},"timeZone":{"type":"string"},"accessRole":{"type":"string"}},"required":["identity"],"additionalProperties":false},"maxItems":10}},"required":["page_id","grant_id","next_page_handle","created_at","expires_at","resource_id","representation","items"],"additionalProperties":false},{"type":"object","properties":{"page_id":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$"},"grant_id":{"type":"string","minLength":1},"next_page_handle":{"type":["string","null"],"pattern":"^[A-Za-z0-9_-]{43}$"},"created_at":{"type":"integer","minimum":0},"expires_at":{"type":"integer","minimum":0},"resource_id":{"type":"string","minLength":1},"representation":{"type":"string","enum":["series-and-exceptions"]},"items":{"type":"array","items":{"type":"object","properties":{"identity":{"type":"object","properties":{"provider":{"type":"string","minLength":1},"appId":{"type":"string","minLength":1},"tenantId":{"type":"string","minLength":1},"grantId":{"type":"string","minLength":1},"principalId":{"type":"string","minLength":1},"calendarId":{"type":"string","minLength":1},"eventId":{"type":"string","minLength":1}},"required":["provider","appId","tenantId","grantId","principalId","calendarId","eventId"],"additionalProperties":false},"status":{"type":"string","enum":["confirmed","tentative","cancelled"],"x-ir-enum-open":true,"description":"Provider event lifecycle status.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"title":{"type":"string"},"start":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["date"]},"date":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","date"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["instant"]},"dateTime":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","dateTime"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["zoned-date-time"]},"dateTime":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","dateTime","timeZone"],"additionalProperties":false}]},"end":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["date"]},"date":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","date"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["instant"]},"dateTime":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","dateTime"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["zoned-date-time"]},"dateTime":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","dateTime","timeZone"],"additionalProperties":false}]},"endTimeUnspecified":{"type":"boolean"},"recurrence":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["single"]}},"required":["kind"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["series"]},"rules":{"type":"array","items":{"type":"string"}}},"required":["kind","rules"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["exception"]},"seriesId":{"type":"string","minLength":1},"originalStart":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["date"]},"date":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","date"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["instant"]},"dateTime":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","dateTime"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["zoned-date-time"]},"dateTime":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","dateTime","timeZone"],"additionalProperties":false}]}},"required":["kind","seriesId","originalStart"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["occurrence"]},"seriesId":{"type":"string","minLength":1},"originalStart":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","enum":["date"]},"date":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","date"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["instant"]},"dateTime":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","dateTime"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["zoned-date-time"]},"dateTime":{"type":"string","minLength":1},"timeZone":{"type":"string","minLength":1}},"required":["kind","dateTime","timeZone"],"additionalProperties":false}]}},"required":["kind","seriesId","originalStart"],"additionalProperties":false},{"type":"object","properties":{"kind":{"type":"string","enum":["unknown"]}},"required":["kind"],"additionalProperties":false}]}},"required":["identity","status","recurrence"],"additionalProperties":false},"maxItems":10}},"required":["page_id","grant_id","next_page_handle","created_at","expires_at","resource_id","representation","items"],"additionalProperties":false}]},"MappingDryRunResult":{"type":"object","properties":{"ok":{"type":"boolean"},"provider_payload":{"type":"object","additionalProperties":{}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/MappingWarning"}},"mapping_source":{"type":"string","enum":["active","request_override","none"],"x-ir-enum-open":true,"description":"`active` = used field_mappings row; `request_override` = used mapping_rules from body; `none` = no rules available, provider_payload is empty.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."}},"required":["ok","provider_payload","warnings","mapping_source"]},"MappingWarning":{"type":"object","properties":{"path":{"type":"string"},"code":{"type":"string","enum":["mapping.required_field_missing","mapping.transform_unknown","mapping.type_mismatch","quickbooks.required_key_missing"],"x-ir-enum-open":true,"description":"Machine-readable warning code. Switch on this, never on `message`.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"message":{"type":"string"},"transform":{"type":"string","description":"Set only on `mapping.transform_unknown` warnings — the offending transform name. Operators can read this to fix the rule without parsing `message`."}},"required":["path","code","message"]},"MappingDryRunRequest":{"type":"object","properties":{"sample_payload":{"type":"object","additionalProperties":{},"description":"Tenant-supplied sample to run through the active (or supplied) mapping rules."},"mapping_rules":{"type":"array","items":{"$ref":"#/components/schemas/MappingRule"},"description":"Optional override; when present, these rules are used instead of the sync_profile's active mapping. Useful for previewing an unpublished draft."}},"required":["sample_payload"]},"MappingRule":{"type":"object","properties":{"source":{"type":"string","minLength":1,"example":"invoice.customer.email"},"target":{"type":"string","minLength":1,"example":"BillEmail.Address"},"transform":{"type":["string","null"],"description":"Optional transform; v1 supports 'lowercase' and 'trim'. Unknown transforms emit a warning and pass the value through untransformed."},"warn_if_source_missing":{"type":"boolean","description":"When true and the source resolves to null/undefined, the dry-run emits a `mapping.required_field_missing` warning. Renamed from `required` (ADR-005 Addendum 3 A8, breaking): this flag is dry-run feedback only and is distinct from whether the provider requires the target field (a publish-time check)."}},"required":["source","target"]},"OperationsExecuteResult":{"type":"object","properties":{"job_id":{"type":"string"},"correlation_id":{"type":["string","null"]},"status":{"type":"string","enum":["queued"],"x-ir-enum-open":true,"description":"Acknowledgement state. The job is enqueued.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"polling_url":{"type":"string","description":"Relative path for polling this job, resolved against your runtime base URL. Self-contained and directly followable: it already carries the `tenant_id` query parameter that `GET /v1/sync-jobs/{id}` requires. Use it verbatim — the HMAC canonical string covers path **and** query, so re-deriving or re-ordering it breaks the signature. Poll it for progress.","example":"/v1/sync-jobs/01J0000000000000000000000?tenant_id=tenant_test_001"}},"required":["job_id","correlation_id","status","polling_url"]},"OperationsExecuteRequest":{"type":"object","properties":{"interactive":{"type":"boolean","default":false,"description":"Invoice-only scheduling preference: true requires a QuickBooks Invoice-create recipe. The first accepted job retains it. Both lanes share provider quota and Invoice serialization."},"tenant_id":{"type":"string","minLength":1},"provider_key":{"type":"string","minLength":1,"description":"Must match the deployed recipe's provider."},"connection_id":{"type":"string","minLength":1},"recipe_key":{"type":"string","minLength":1,"description":"Deployed recipe key to admit."},"recipe_version":{"type":"integer","exclusiveMinimum":0,"description":"Deployed recipe version to admit."},"trigger_type":{"type":"string","minLength":1},"object_type":{"type":"string","minLength":1,"description":"Must match the deployed recipe's object type."},"internal_object_id":{"type":"string","minLength":1},"idempotency_key":{"type":"string","minLength":1},"mapping_version":{"type":"integer","exclusiveMinimum":0},"payload":{"description":"Recipe-specific input. Google Calendar clients must use the published CalendarDiscoveryInitialPayload, CalendarDiscoveryResumePayload, CalendarEventsInitialPayload, or CalendarEventsResumePayload component matching the selected recipe and traversal mode."},"correlation_id":{"type":"string"},"request_id":{"type":"string"}},"required":["tenant_id","provider_key","connection_id","recipe_key","recipe_version","trigger_type","object_type","internal_object_id","payload"]},"UsageResponse":{"type":"object","properties":{"app_id":{"type":"string"},"tenant_id":{"type":"string"},"generated_at":{"type":"integer","minimum":0},"window":{"type":"object","properties":{"since":{"type":"integer","minimum":0},"until":{"type":"integer","minimum":0},"minutes":{"type":"integer","minimum":1,"maximum":10080}},"required":["since","until","minutes"]},"source":{"type":"string","enum":["sync_jobs"]},"basis":{"type":"string","enum":["created_cohort_current_state"]},"totals":{"$ref":"#/components/schemas/UsageCounts"},"breakdown":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/UsageCounts"},{"type":"object","properties":{"provider_key":{"type":"string"},"operation_key":{"type":"string"}},"required":["provider_key","operation_key"]}]},"maxItems":200},"provider_http_attempts":{"type":"object","properties":{"availability":{"type":"string","enum":["unavailable"]},"count":{"type":"null"}},"required":["availability","count"],"description":"No exact provider HTTP attempt ledger is available; job retry counts and sampled telemetry are not call counts."},"limits":{"type":"object","properties":{"max_jobs":{"type":"integer","exclusiveMinimum":0,"example":10000},"max_breakdown_groups":{"type":"integer","exclusiveMinimum":0,"example":200}},"required":["max_jobs","max_breakdown_groups"]}},"required":["app_id","tenant_id","generated_at","window","source","basis","totals","breakdown","provider_http_attempts","limits"]},"UsageCounts":{"type":"object","properties":{"jobs_created":{"type":"integer","minimum":0,"description":"Retained jobs created in [window.since, window.until), including automatic and replay jobs. Not API submissions or provider calls."},"jobs_completed":{"type":"integer","minimum":0,"description":"Jobs in this creation cohort currently in completed status. Not all completions during the window."},"by_status":{"type":"object","properties":{"queued":{"type":"integer","minimum":0},"processing":{"type":"integer","minimum":0},"completed":{"type":"integer","minimum":0},"failed":{"type":"integer","minimum":0},"dead_lettered":{"type":"integer","minimum":0},"cancelled":{"type":"integer","minimum":0},"manually_resolved":{"type":"integer","minimum":0}},"required":["queued","processing","completed","failed","dead_lettered","cancelled","manually_resolved"]}},"required":["jobs_created","jobs_completed","by_status"]},"SyncJobsListResponse":{"type":"object","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/SyncJobPublicDTO"}},"next_cursor":{"type":["string","null"]}},"required":["jobs","next_cursor"]},"SyncJobPublicDTO":{"type":"object","properties":{"id":{"type":"string"},"app_id":{"type":"string"},"tenant_id":{"type":"string"},"provider_key":{"type":"string"},"connection_id":{"type":"string"},"operation_key":{"type":"string"},"recipe_key":{"type":["string","null"]},"recipe_version":{"type":["integer","null"]},"object_type":{"type":["string","null"]},"internal_object_id":{"type":["string","null"]},"external_object_id":{"type":["string","null"]},"status":{"type":"string","enum":["queued","processing","completed","failed","dead_lettered","cancelled","manually_resolved"],"x-ir-enum-open":true,"description":"Lifecycle state of the job.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either."},"attempt_count":{"type":"integer","minimum":0},"last_error_code":{"$ref":"#/components/schemas/SyncJobFailureCode"},"created_at":{"type":"integer"},"completed_at":{"type":["integer","null"]}},"required":["id","app_id","tenant_id","provider_key","connection_id","operation_key","recipe_key","recipe_version","object_type","internal_object_id","external_object_id","status","attempt_count","last_error_code","created_at","completed_at"]},"SyncJobFailureCode":{"type":["string","null"],"description":"Machine-readable asynchronous failure code. Switch on this value, never on a human message.\n\nThis is an open string: other job failure codes may appear, and clients must tolerate values they do not recognise.\n\nFor the QuickBooks recipe-v2 tax outcomes below, `terminal` means the runtime does not automatically retry the failed delivery. `bounded-retryable` means the runtime retries automatically up to its attempt ceiling; exhaustion can still leave the job `dead_lettered`, so do not submit a duplicate operation while those retries are active.\n\n| Code | Disposition | Cause | Caller remedy |\n|---|---|---|---|\n| `invoice_runtime_marker_forbidden` | terminal | The exact Invoice document contains request-specific runtime marker evidence from a historical writer. | For fresh invalid input, restore the caller's marker-free business document. If a reservation already exists, preserve it unchanged and perform operator reconciliation before any replay. |\n| `invoice_payload_conflict` | terminal | The stored job payload was altered, or this internal invoice already has a reservation for different input. | Restore the immutable original input and investigate the conflict. Do not change the idempotency key to recreate an invoice that may already exist. |\n| `invoice_company_mismatch` | terminal | The admitted job or reservation company differs from the current authorized connection, or the authoritative admission pin is missing. | Restore the original company connection and inspect the job and reservation evidence before operator replay. Never repin or re-key an uncertain write to another company. |\n| `invoice_identity_unavailable` | terminal | A historical Invoice reservation lacks an immutable payload fingerprint or original provider deduplication token. | Operator reconciliation is required using the original company and provider evidence. Do not infer the original payload, mint a new request token, or reset the recovery clock. |\n| `ccdo.invoice_reservation_raced` | bounded-retryable | An existing reservation appeared during Invoice reservation opening; no provider create was sent by this attempt. | Allow bounded retry through the authoritative reservation recovery path; retain the original request token and first-call clock. |\n| `job.input_missing` | terminal | The immutable job input is still missing after the 15-minute admission recovery grace. Publication is refused. | Inspect the original job and reservation before retrying. A prior delivery or replay may have created an invoice; do not blindly submit with a new identity. Restore the original accepted payload or reconcile the provider outcome first. |\n| `job.input_reference_invalid` | terminal | The job input reference is outside its tenant namespace or is not a supported input object. Publication is refused. | Investigate stored job integrity and any prior provider outcome; never repair a reference by borrowing another tenant's payload. |\n| `job.payload_fingerprint_conflict` | terminal | The recovered input does not match the accepted Invoice fingerprint, or a legacy Invoice job lacks that fingerprint. Publication is refused. | Reconcile the accepted input and any existing provider invoice. Do not replace the immutable input, adopt a new identity, or automatically recreate the invoice. |\n| `job.recipe_missing` | terminal | The durable job is missing its executable recipe identity. Publication is refused. | Inspect job integrity and any prior provider outcome before an operator-authorized recovery. |\n| `job.delivery_status_unconfirmed` | terminal | Publication or delivery status exceeded its 24-hour recovery deadline without a fresh job or step heartbeat. An invoice MAY ALREADY EXIST; this is not proof that the provider write failed. | Reconcile the original provider request and reservation before replaying. Preserve its identity and original recovery clock; never automatically recreate with a new key. Pilot clients must surface this outcome as requiring operator attention. |\n| `cf_retries_exhausted` | terminal | Queue delivery exhausted its retry budget. The terminal job state and operator record are committed together. The provider outcome may already exist. | Inspect the job steps, original request identity, and provider reservation before an operator-authorized replay; do not assume that a dead-lettered job means no invoice was created. |\n| `financial_write_tax_disabled` | terminal | The live QuickBooks preference is exactly Preferences.TaxPrefs.UsingSalesTax=false. No Invoice or RefundReceipt create is sent. | Enable sales tax in QuickBooks, then ask an operator to replay the job. |\n| `financial_write_tax_preflight_failed` | terminal | The runtime cannot confirm the live QuickBooks sales-tax preference because the read is unauthorized, rate-limited, unavailable, malformed, missing, or the wrong type. No Invoice or RefundReceipt create is sent. | Restore QuickBooks access or availability, then ask an operator to replay the job. |\n| `financial_write_currency_unsupported` | terminal | The live QuickBooks Invoice profile has multicurrency enabled or a home currency other than CAD. No Invoice create is sent. | Use a qualified single-currency CAD company, then ask an operator to replay the job. |\n| `financial_write_tax_declaration_missing` | terminal | tax_declarations is absent or does not cover every provider line exactly once. No document create is sent. | Correct the declarations and submit a new job using the current supported recipe version with a new idempotency key; replaying the immutable invalid input cannot repair it. |\n| `financial_write_tax_payload_invalid` | terminal | The document or declarations are malformed, including missing native Invoice customer/item/tax references, Invoice update selectors or root customer/items inputs, duplicate or out-of-range indexes, unknown treatments, or a missing RefundReceipt deposit account. No document create is sent. | Correct the payload and submit a new job using the current supported recipe version with a new idempotency key; replaying the immutable invalid input cannot repair it. |\n| `financial_write_tax_path_unsupported` | terminal | The request uses an unsupported calculation mode, supplies TxnTaxDetail, or contains a Group, Discount, Subtotal, or other unsupported line shape. No document create is sent. | Rebuild the payload on the supported TaxExcluded top-level sales-line path and submit a new job using the current supported recipe version with a new idempotency key. |\n| `financial_write_tax_verification_mismatch` | terminal | A successful stored-document GET does not match the immutable calculation mode and line signatures. The linked reservation is retained and no replacement create is sent. | Do not recreate automatically. Compare the stored QuickBooks document with the source request and manually reconcile the discrepancy before submitting another job. |\n| `financial_write_tax_verification_unconfirmed` | bounded-retryable | The stored-document GET is missing, unavailable, malformed, or otherwise cannot confirm the durable object identity and tax content. The linked reservation is retained and no replacement create is sent. | Allow the runtime's bounded automatic retries to finish. If they are exhausted, restore provider availability, inspect the linked document, and ask an operator to replay the job. A 401 and 429 use their separate provider.401 and ccdo.rate_limited codes. |\n\nLinked Payment recipe v2 verifies stored allocation and authoritative remaining Invoice balance before publishing a public result.\n\n| Code | Disposition | Cause | Caller remedy |\n|---|---|---|---|\n| `payment.invalid_input` | terminal | The immutable positive CAD Payment envelope is invalid. | Correct input before first admission; preserve any existing request and do not re-key uncertainty. |\n| `payment.identity_unavailable` | terminal | Original durable Payment identity/company or verification coordinates are missing. | Reconcile historical provider evidence using the original company and request. |\n| `payment.parent_invoice_mismatch` | terminal | The Invoice is not linked under this exact app/tenant/native company, or the frozen company differs. | Restore the original native scope; do not repin an existing request after reconnect. |\n| `payment.invoice_snapshot_mismatch` | terminal | Current Invoice customer/currency/version or remaining balance differs from the approved snapshot. | Use operator GET-only reconciliation for uncertainty. Prepare new input only for a separate legitimate payment. |\n| `payment.reference_inactive` | terminal | The deposit account or payment method is inactive or unsupported. | Resolve reference drift and reconcile any prior provider write before a new request. |\n| `payment.verification_mismatch` | terminal | Stored Payment allocation/references or remaining Invoice balance differs from the original request. | A provider Payment may already exist. Compare native records; do not recreate or change its key. |\n| `payment.verification_unconfirmed` | bounded-retryable | Native provider reference or Payment/Invoice read-back is unusable. | Allow bounded retries of the original job, then restore reads and reconcile existing native evidence. |\n| `payment.status_refresh_unconfirmed` | bounded-retryable | The verified Payment's durable status projection could not be refreshed. | Retry the original job; its linked reservation prevents a replacement provider write. |\n\nFor Google Calendar read jobs, the same disposition terms apply to these grant, provider, persistence, and kill-switch outcomes.\n\n| Code | Disposition | Cause | Caller remedy |\n|---|---|---|---|\n| `operation_not_enabled` | terminal | The Calendar operation was disabled after admission and before execution. | Ask an operator to enable the operation, then submit a new root read. |\n| `oauth.lifecycle_busy` | bounded-retryable | A callback, refresh, or disconnect currently owns the grant lifecycle. | Allow bounded retry of the same job after the lifecycle owner completes. |\n| `recipe_result.invalid_json` | terminal | The Calendar page result could not be serialized as valid bounded JSON for publication. | Investigate the publication failure, then submit a new root read after correction. |\n| `recipe_result.too_large` | terminal | The valid Calendar page result exceeded the 32 KiB public job-result publication limit. | Submit a new root read with a smaller page_size so its public result stays bounded. |\n| `calendar.invalid_input` | terminal | The durable Calendar job input is invalid for its frozen recipe. | Correct the request before retrying. |\n| `calendar.unsupported_representation` | terminal | The event read requests an unsupported representation. | Use the series-and-exceptions representation. |\n| `calendar.grant_not_found` | terminal | The job's scoped Calendar grant no longer exists. | Treat the grant as unavailable. |\n| `calendar.resource_not_found` | terminal | The selected Calendar resource no longer exists. | Treat the resource as unavailable. |\n| `calendar.insufficient_scopes` | terminal | The current grant lacks the scope required by the operation. | Reconnect and grant the required scope. |\n| `calendar.resource_denied` | terminal | The job, grant, continuation, or resource binding no longer matches. | Use an authorized selected resource. |\n| `calendar.resource_detached` | terminal | The selected Calendar resource was detached before execution. | Select the resource again with a new intent. |\n| `calendar.grant_revoked` | terminal | The Calendar grant was revoked before execution. | Start a new consent ceremony. |\n| `calendar.grant_disabled` | terminal | Calendar admission is disabled for this app or registration. | Ask an operator to enable Calendar admission. |\n| `calendar.generation_changed` | terminal | The grant callback generation changed after the job was admitted. | Start a new root read on the current grant. |\n| `calendar.reconnect_required` | terminal | The current Calendar authorization requires reconnection. | Reconnect, rediscover, and reselect. |\n| `calendar.result_expired` | terminal | The root traversal expired before this job could publish its result. | Start a new root listing or read. |\n| `calendar.page_expired` | terminal | The bounded Calendar page expired before it could be staged. | Start a new root listing or read. |\n| `calendar.page_pending` | terminal | The referenced Calendar page is not yet in a completed state. | Poll the associated job before fetching the page. |\n| `calendar.state_unavailable` | bounded-retryable | Required Calendar state could not be read or persisted safely. | Retry the same signed intent with backoff. |\n| `calendar.invalid_continuation` | terminal | The continuation is malformed or no longer matches its parent page. | Start a new root listing or read. |\n| `calendar.continuation_expired` | terminal | The queued resume job references a continuation whose traversal window has expired. | Start a new root listing or read. |\n| `calendar.continuation_not_found` | terminal | The queued resume job no longer matches an available parent continuation. | Start a new root listing or read. |\n| `calendar.forbidden` | terminal | Google denied access to the requested Calendar resource. | Reconnect or choose a readable resource. |\n| `calendar.not_found` | terminal | Google reports that the requested Calendar resource does not exist. | Treat the provider resource as unavailable. |\n| `calendar.unexpected_status` | terminal | Google returned a response status outside the supported Calendar contract. | Start a new read after operator review. |\n| `calendar.response_too_large` | terminal | The bounded Google Calendar response exceeded the accepted size. | Use a smaller new root page size. |\n| `calendar.malformed_response` | terminal | Google returned a Calendar response that failed structural validation. | Start a new read after operator review. |\n| `calendar.rate_limited` | bounded-retryable | Calendar execution was refused by local quota or throttled by Google. | Retry with bounded backoff. |\n| `calendar.unavailable` | bounded-retryable | Calendar execution or Google Calendar is temporarily unavailable. | Retry with bounded backoff. |\n| `calendar.transport_error` | bounded-retryable | The Google Calendar request failed before a valid response arrived. | Retry with bounded backoff. |\n| `calendar.timeout` | bounded-retryable | The bounded Google Calendar request exceeded its timeout. | Retry with bounded backoff. |","x-ir-quickbooks-payment-job-errors":[{"code":"payment.invalid_input","disposition":"terminal","cause":"The immutable positive CAD Payment envelope is invalid.","remedy":"Correct input before first admission; preserve any existing request and do not re-key uncertainty."},{"code":"payment.identity_unavailable","disposition":"terminal","cause":"Original durable Payment identity/company or verification coordinates are missing.","remedy":"Reconcile historical provider evidence using the original company and request."},{"code":"payment.parent_invoice_mismatch","disposition":"terminal","cause":"The Invoice is not linked under this exact app/tenant/native company, or the frozen company differs.","remedy":"Restore the original native scope; do not repin an existing request after reconnect."},{"code":"payment.invoice_snapshot_mismatch","disposition":"terminal","cause":"Current Invoice customer/currency/version or remaining balance differs from the approved snapshot.","remedy":"Use operator GET-only reconciliation for uncertainty. Prepare new input only for a separate legitimate payment."},{"code":"payment.reference_inactive","disposition":"terminal","cause":"The deposit account or payment method is inactive or unsupported.","remedy":"Resolve reference drift and reconcile any prior provider write before a new request."},{"code":"payment.verification_mismatch","disposition":"terminal","cause":"Stored Payment allocation/references or remaining Invoice balance differs from the original request.","remedy":"A provider Payment may already exist. Compare native records; do not recreate or change its key."},{"code":"payment.verification_unconfirmed","disposition":"bounded-retryable","cause":"Native provider reference or Payment/Invoice read-back is unusable.","remedy":"Allow bounded retries of the original job, then restore reads and reconcile existing native evidence."},{"code":"payment.status_refresh_unconfirmed","disposition":"bounded-retryable","cause":"The verified Payment's durable status projection could not be refreshed.","remedy":"Retry the original job; its linked reservation prevents a replacement provider write."}],"x-ir-quickbooks-tax-job-errors":[{"code":"invoice_runtime_marker_forbidden","disposition":"terminal","cause":"The exact Invoice document contains request-specific runtime marker evidence from a historical writer.","remedy":"For fresh invalid input, restore the caller's marker-free business document. If a reservation already exists, preserve it unchanged and perform operator reconciliation before any replay."},{"code":"invoice_payload_conflict","disposition":"terminal","cause":"The stored job payload was altered, or this internal invoice already has a reservation for different input.","remedy":"Restore the immutable original input and investigate the conflict. Do not change the idempotency key to recreate an invoice that may already exist."},{"code":"invoice_company_mismatch","disposition":"terminal","cause":"The admitted job or reservation company differs from the current authorized connection, or the authoritative admission pin is missing.","remedy":"Restore the original company connection and inspect the job and reservation evidence before operator replay. Never repin or re-key an uncertain write to another company."},{"code":"invoice_identity_unavailable","disposition":"terminal","cause":"A historical Invoice reservation lacks an immutable payload fingerprint or original provider deduplication token.","remedy":"Operator reconciliation is required using the original company and provider evidence. Do not infer the original payload, mint a new request token, or reset the recovery clock."},{"code":"ccdo.invoice_reservation_raced","disposition":"bounded-retryable","cause":"An existing reservation appeared during Invoice reservation opening; no provider create was sent by this attempt.","remedy":"Allow bounded retry through the authoritative reservation recovery path; retain the original request token and first-call clock."},{"code":"job.input_missing","disposition":"terminal","cause":"The immutable job input is still missing after the 15-minute admission recovery grace. Publication is refused.","remedy":"Inspect the original job and reservation before retrying. A prior delivery or replay may have created an invoice; do not blindly submit with a new identity. Restore the original accepted payload or reconcile the provider outcome first."},{"code":"job.input_reference_invalid","disposition":"terminal","cause":"The job input reference is outside its tenant namespace or is not a supported input object. Publication is refused.","remedy":"Investigate stored job integrity and any prior provider outcome; never repair a reference by borrowing another tenant's payload."},{"code":"job.payload_fingerprint_conflict","disposition":"terminal","cause":"The recovered input does not match the accepted Invoice fingerprint, or a legacy Invoice job lacks that fingerprint. Publication is refused.","remedy":"Reconcile the accepted input and any existing provider invoice. Do not replace the immutable input, adopt a new identity, or automatically recreate the invoice."},{"code":"job.recipe_missing","disposition":"terminal","cause":"The durable job is missing its executable recipe identity. Publication is refused.","remedy":"Inspect job integrity and any prior provider outcome before an operator-authorized recovery."},{"code":"job.delivery_status_unconfirmed","disposition":"terminal","cause":"Publication or delivery status exceeded its 24-hour recovery deadline without a fresh job or step heartbeat. An invoice MAY ALREADY EXIST; this is not proof that the provider write failed.","remedy":"Reconcile the original provider request and reservation before replaying. Preserve its identity and original recovery clock; never automatically recreate with a new key. Pilot clients must surface this outcome as requiring operator attention."},{"code":"cf_retries_exhausted","disposition":"terminal","cause":"Queue delivery exhausted its retry budget. The terminal job state and operator record are committed together. The provider outcome may already exist.","remedy":"Inspect the job steps, original request identity, and provider reservation before an operator-authorized replay; do not assume that a dead-lettered job means no invoice was created."},{"code":"financial_write_tax_disabled","disposition":"terminal","cause":"The live QuickBooks preference is exactly Preferences.TaxPrefs.UsingSalesTax=false. No Invoice or RefundReceipt create is sent.","remedy":"Enable sales tax in QuickBooks, then ask an operator to replay the job."},{"code":"financial_write_tax_preflight_failed","disposition":"terminal","cause":"The runtime cannot confirm the live QuickBooks sales-tax preference because the read is unauthorized, rate-limited, unavailable, malformed, missing, or the wrong type. No Invoice or RefundReceipt create is sent.","remedy":"Restore QuickBooks access or availability, then ask an operator to replay the job."},{"code":"financial_write_currency_unsupported","disposition":"terminal","cause":"The live QuickBooks Invoice profile has multicurrency enabled or a home currency other than CAD. No Invoice create is sent.","remedy":"Use a qualified single-currency CAD company, then ask an operator to replay the job."},{"code":"financial_write_tax_declaration_missing","disposition":"terminal","cause":"tax_declarations is absent or does not cover every provider line exactly once. No document create is sent.","remedy":"Correct the declarations and submit a new job using the current supported recipe version with a new idempotency key; replaying the immutable invalid input cannot repair it."},{"code":"financial_write_tax_payload_invalid","disposition":"terminal","cause":"The document or declarations are malformed, including missing native Invoice customer/item/tax references, Invoice update selectors or root customer/items inputs, duplicate or out-of-range indexes, unknown treatments, or a missing RefundReceipt deposit account. No document create is sent.","remedy":"Correct the payload and submit a new job using the current supported recipe version with a new idempotency key; replaying the immutable invalid input cannot repair it."},{"code":"financial_write_tax_path_unsupported","disposition":"terminal","cause":"The request uses an unsupported calculation mode, supplies TxnTaxDetail, or contains a Group, Discount, Subtotal, or other unsupported line shape. No document create is sent.","remedy":"Rebuild the payload on the supported TaxExcluded top-level sales-line path and submit a new job using the current supported recipe version with a new idempotency key."},{"code":"financial_write_tax_verification_mismatch","disposition":"terminal","cause":"A successful stored-document GET does not match the immutable calculation mode and line signatures. The linked reservation is retained and no replacement create is sent.","remedy":"Do not recreate automatically. Compare the stored QuickBooks document with the source request and manually reconcile the discrepancy before submitting another job."},{"code":"financial_write_tax_verification_unconfirmed","disposition":"bounded-retryable","cause":"The stored-document GET is missing, unavailable, malformed, or otherwise cannot confirm the durable object identity and tax content. The linked reservation is retained and no replacement create is sent.","remedy":"Allow the runtime's bounded automatic retries to finish. If they are exhausted, restore provider availability, inspect the linked document, and ask an operator to replay the job. A 401 and 429 use their separate provider.401 and ccdo.rate_limited codes."}],"x-ir-calendar-job-errors":[{"code":"operation_not_enabled","disposition":"terminal","cause":"The Calendar operation was disabled after admission and before execution.","remedy":"Ask an operator to enable the operation, then submit a new root read."},{"code":"oauth.lifecycle_busy","disposition":"bounded-retryable","cause":"A callback, refresh, or disconnect currently owns the grant lifecycle.","remedy":"Allow bounded retry of the same job after the lifecycle owner completes."},{"code":"recipe_result.invalid_json","disposition":"terminal","cause":"The Calendar page result could not be serialized as valid bounded JSON for publication.","remedy":"Investigate the publication failure, then submit a new root read after correction."},{"code":"recipe_result.too_large","disposition":"terminal","cause":"The valid Calendar page result exceeded the 32 KiB public job-result publication limit.","remedy":"Submit a new root read with a smaller page_size so its public result stays bounded."},{"code":"calendar.invalid_input","disposition":"terminal","cause":"The durable Calendar job input is invalid for its frozen recipe.","remedy":"Correct the request before retrying."},{"code":"calendar.unsupported_representation","disposition":"terminal","cause":"The event read requests an unsupported representation.","remedy":"Use the series-and-exceptions representation."},{"code":"calendar.grant_not_found","disposition":"terminal","cause":"The job's scoped Calendar grant no longer exists.","remedy":"Treat the grant as unavailable."},{"code":"calendar.resource_not_found","disposition":"terminal","cause":"The selected Calendar resource no longer exists.","remedy":"Treat the resource as unavailable."},{"code":"calendar.insufficient_scopes","disposition":"terminal","cause":"The current grant lacks the scope required by the operation.","remedy":"Reconnect and grant the required scope."},{"code":"calendar.resource_denied","disposition":"terminal","cause":"The job, grant, continuation, or resource binding no longer matches.","remedy":"Use an authorized selected resource."},{"code":"calendar.resource_detached","disposition":"terminal","cause":"The selected Calendar resource was detached before execution.","remedy":"Select the resource again with a new intent."},{"code":"calendar.grant_revoked","disposition":"terminal","cause":"The Calendar grant was revoked before execution.","remedy":"Start a new consent ceremony."},{"code":"calendar.grant_disabled","disposition":"terminal","cause":"Calendar admission is disabled for this app or registration.","remedy":"Ask an operator to enable Calendar admission."},{"code":"calendar.generation_changed","disposition":"terminal","cause":"The grant callback generation changed after the job was admitted.","remedy":"Start a new root read on the current grant."},{"code":"calendar.reconnect_required","disposition":"terminal","cause":"The current Calendar authorization requires reconnection.","remedy":"Reconnect, rediscover, and reselect."},{"code":"calendar.result_expired","disposition":"terminal","cause":"The root traversal expired before this job could publish its result.","remedy":"Start a new root listing or read."},{"code":"calendar.page_expired","disposition":"terminal","cause":"The bounded Calendar page expired before it could be staged.","remedy":"Start a new root listing or read."},{"code":"calendar.page_pending","disposition":"terminal","cause":"The referenced Calendar page is not yet in a completed state.","remedy":"Poll the associated job before fetching the page."},{"code":"calendar.state_unavailable","disposition":"bounded-retryable","cause":"Required Calendar state could not be read or persisted safely.","remedy":"Retry the same signed intent with backoff."},{"code":"calendar.invalid_continuation","disposition":"terminal","cause":"The continuation is malformed or no longer matches its parent page.","remedy":"Start a new root listing or read."},{"code":"calendar.continuation_expired","disposition":"terminal","cause":"The queued resume job references a continuation whose traversal window has expired.","remedy":"Start a new root listing or read."},{"code":"calendar.continuation_not_found","disposition":"terminal","cause":"The queued resume job no longer matches an available parent continuation.","remedy":"Start a new root listing or read."},{"code":"calendar.forbidden","disposition":"terminal","cause":"Google denied access to the requested Calendar resource.","remedy":"Reconnect or choose a readable resource."},{"code":"calendar.not_found","disposition":"terminal","cause":"Google reports that the requested Calendar resource does not exist.","remedy":"Treat the provider resource as unavailable."},{"code":"calendar.unexpected_status","disposition":"terminal","cause":"Google returned a response status outside the supported Calendar contract.","remedy":"Start a new read after operator review."},{"code":"calendar.response_too_large","disposition":"terminal","cause":"The bounded Google Calendar response exceeded the accepted size.","remedy":"Use a smaller new root page size."},{"code":"calendar.malformed_response","disposition":"terminal","cause":"Google returned a Calendar response that failed structural validation.","remedy":"Start a new read after operator review."},{"code":"calendar.rate_limited","disposition":"bounded-retryable","cause":"Calendar execution was refused by local quota or throttled by Google.","remedy":"Retry with bounded backoff."},{"code":"calendar.unavailable","disposition":"bounded-retryable","cause":"Calendar execution or Google Calendar is temporarily unavailable.","remedy":"Retry with bounded backoff."},{"code":"calendar.transport_error","disposition":"bounded-retryable","cause":"The Google Calendar request failed before a valid response arrived.","remedy":"Retry with bounded backoff."},{"code":"calendar.timeout","disposition":"bounded-retryable","cause":"The bounded Google Calendar request exceeded its timeout.","remedy":"Retry with bounded backoff."}]},"SyncJobWithStepsDTO":{"allOf":[{"$ref":"#/components/schemas/SyncJobPublicDTO"},{"type":"object","properties":{"result_status":{"type":"string","enum":["none","available"],"x-ir-enum-open":true,"description":"Whether this job has an explicitly published public result. Results share the job row lifecycle and do not expire independently.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"result":{"$ref":"#/components/schemas/SyncJobResult"},"steps":{"type":"array","items":{"$ref":"#/components/schemas/SyncJobStepPublicDTO"}}},"required":["result_status","result","steps"]}]},"SyncJobResult":{"type":["object","null"],"additionalProperties":{},"description":"Recipe-declared, non-secret JSON result. The UTF-8 JSON representation is limited to 32 KiB. This is never an automatic projection of step output, credentials, or a raw provider response."},"SyncJobStepPublicDTO":{"type":"object","properties":{"id":{"type":"string"},"step_key":{"type":"string"},"status":{"type":"string","enum":["pending","processing","completed","failed","skipped"],"x-ir-enum-open":true,"description":"Lifecycle state of the individual recipe step.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"attempt_count":{"type":"integer","minimum":0},"error_code":{"$ref":"#/components/schemas/SyncJobFailureCode"},"provider_status_code":{"type":["integer","null"]},"provider_request_id":{"type":["string","null"]},"started_at":{"type":["integer","null"]},"completed_at":{"type":["integer","null"]}},"required":["id","step_key","status","attempt_count","error_code","provider_status_code","provider_request_id","started_at","completed_at"]},"SyncJobReplayResult":{"type":"object","properties":{"job_id":{"type":"string"},"status":{"type":"string","enum":["queued"],"x-ir-enum-open":true,"description":"Publication acknowledgement for the replay request, not the current job status; poll the job for execution status.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."}},"required":["job_id","status"]},"SyncJobReplayRequest":{"type":"object","properties":{"reason":{"type":"string","minLength":1,"maxLength":500}}}},"parameters":{}},"paths":{"/v1/health":{"get":{"tags":["Health"],"summary":"Liveness probe.","operationId":"getHealth","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"],"example":"ok","x-ir-enum-open":true,"description":"Liveness state.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"version":{"type":"string","example":"0.1.0"}},"required":["status","version"]}}}}}}},"/v1/connections/connect-session":{"post":{"tags":["Connections"],"summary":"Begin a tenant connect flow for a provider.","operationId":"createConnectSession","parameters":[{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConnectSessionRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateConnectSessionResponse"}}}},"400":{"description":"Bad request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Provider not enabled or tenant mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Connection OAuth lifecycle is busy or QuickBooks consent is frozen","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/connections":{"get":{"tags":["Connections"],"summary":"List connections for the requesting tenant.","description":"Returns a deterministic, bounded page of authoritative connection lifecycle state derived from the administrative app state, confirmed native projection generation and scoped token-vault state. The public response is an explicit allowlist and never includes credentials, OAuth state, granted scopes, vault metadata or operator-only fields.","operationId":"listConnections","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":2048},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Tenant-scoped connection page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionsListResponse"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/connections/{id}/disconnect":{"post":{"tags":["Connections"],"summary":"Disconnect one native QuickBooks company connection.","operationId":"disconnectConnection","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DisconnectConnectionRequest"}}}},"responses":{"204":{"description":"Disconnected, or the same request already completed"},"400":{"description":"Request validation failed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Connection or company identity not found in the authenticated scope","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"A different OAuth lifecycle operation owns the connection","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Provider revocation or local projection must be retried","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/connections/oauth/callback":{"get":{"tags":["Connections"],"summary":"Provider → runtime callback for the native OAuth flow.","operationId":"oauthCallback","security":[],"parameters":[{"schema":{"type":"string"},"required":false,"name":"code","in":"query"},{"schema":{"type":"string"},"required":false,"name":"state","in":"query"},{"schema":{"type":"string"},"required":false,"name":"realmId","in":"query"},{"schema":{"type":"string"},"required":false,"name":"error","in":"query"}],"responses":{"302":{"description":"Redirect to product app return_url."},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"410":{"description":"Unknown / expired / completed session (uniform, no enumeration).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/connections/{id}":{"get":{"tags":["Connections"],"summary":"Fetch authoritative status for one connection.","description":"Use this response, rather than an OAuth callback query hint, as the authoritative connection state. A stale connected app projection is demoted when its native callback generation is unconfirmed or the scoped token vault is terminal or missing. Unknown and out-of-scope connection ids return the same non-enumerating response.","operationId":"getConnection","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Tenant-scoped connection state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectionPublicDTO"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Connection not found in this app and tenant scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/connections/{id}/quickbooks/status/{entity_type}/{external_id}":{"get":{"tags":["QuickBooks"],"operationId":"getQuickBooksTransactionStatus","summary":"Read scoped QuickBooks transaction status","description":"Reads the last authoritative projection. refresh=1 performs quota-controlled provider GET reads and may update local status/outbound notifications. This endpoint never performs a financial write or grants financial operation admission. Balances use two-decimal minor units; unsupported currencies fail closed.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["Invoice","Payment"],"x-ir-enum-open":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either."},"required":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either.","name":"entity_type","in":"path"},{"schema":{"type":"string","pattern":"^[0-9]{1,32}$"},"required":true,"name":"external_id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","enum":["0","1"]},"required":false,"name":"refresh","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Provider/company-bound status projection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuickBooksStatusResponse"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Scoped connection or status not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Connection or status refresh unconfirmed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"502":{"description":"Authoritative read unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/connections/{id}/quickbooks/recovery":{"get":{"tags":["QuickBooks"],"operationId":"getQuickBooksRecovery","summary":"Inspect scoped QuickBooks recovery work","description":"Reads persisted current-company/current-generation status and pending work. Delivery states are a sample for the latest100 scoped outbox events. This endpoint performs no provider call and does not claim fanout is delivered.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Confirmed native binding and durable work","content":{"application/json":{"schema":{"type":"object","properties":{"app_id":{"type":"string"},"tenant_id":{"type":"string"},"recovery":{"type":"object","properties":{"binding":{"type":"object","properties":{"app_id":{"type":"string"},"tenant_id":{"type":"string"},"connection_id":{"type":"string"},"provider_account_id":{"type":"string"},"callback_epoch":{"type":"integer","minimum":0}},"required":["app_id","tenant_id","connection_id","provider_account_id","callback_epoch"]},"summary":{"type":"object","properties":{"known_transactions":{"type":"integer","minimum":0},"pending_refreshes":{"type":"integer","minimum":0},"pending_outbox":{"type":"integer","minimum":0},"delivery_states":{"type":"object","additionalProperties":{"type":"integer","minimum":0}},"delivery_sample_events":{"type":"integer","minimum":0},"recent_failures":{"type":"array","items":{"type":"object","properties":{"entity_type":{"type":"string"},"external_id":{"type":"string"},"reason":{"type":"string"},"occurred_at":{"type":"number"}},"required":["entity_type","external_id","reason","occurred_at"]}}},"required":["known_transactions","pending_refreshes","pending_outbox","delivery_states","delivery_sample_events","recent_failures"]}},"required":["binding","summary"]}},"required":["app_id","tenant_id","recovery"]}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Native company, generation or cursor unconfirmed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}},"post":{"tags":["QuickBooks"],"operationId":"recoverQuickBooksStatus","summary":"Explicitly refresh a bounded page of persisted QuickBooks identities","description":"Performs controlled accounting GETs only: up to20 transaction targets and64 accounting GET attempts (default32). OAuth maintenance is separate. Dependencies are durable; a failed target leaves cursor progress unchanged. The authenticated cursor binds app, tenant, company and connection generation. This operation never replays a financial POST, changes a financial request key or marks a webhook delivered. Terminal deleted projections are retained without inventing a new deletion from404.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tenant_id":{"type":"string","minLength":1},"expected_provider_account_id":{"type":"string","pattern":"^\\d{1,32}$"},"expected_callback_epoch":{"type":"integer","minimum":0},"max_transactions":{"type":"integer","minimum":1,"maximum":20},"max_provider_gets":{"type":"integer","minimum":1,"maximum":64},"cursor":{"type":"string","minLength":1,"maxLength":4096}},"required":["tenant_id","expected_provider_account_id","expected_callback_epoch"],"additionalProperties":false}}}},"responses":{"200":{"description":"Actual applied projections, accounting GET count, fanout inserts and durable remaining work","content":{"application/json":{"schema":{"type":"object","properties":{"app_id":{"type":"string"},"tenant_id":{"type":"string"},"recovery":{"type":"object","properties":{"binding":{"type":"object","properties":{"app_id":{"type":"string"},"tenant_id":{"type":"string"},"connection_id":{"type":"string"},"provider_account_id":{"type":"string"},"callback_epoch":{"type":"integer","minimum":0}},"required":["app_id","tenant_id","connection_id","provider_account_id","callback_epoch"]},"outcome":{"type":"string","enum":["complete","more","blocked","get_budget_exhausted"],"x-ir-enum-open":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"applied":{"type":"array","items":{"type":"object","properties":{"entity_type":{"type":"string","enum":["Invoice","Payment"],"x-ir-enum-open":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"external_id":{"type":"string"},"projection_revision":{"type":"integer","exclusiveMinimum":0},"changed":{"type":"boolean"}},"required":["entity_type","external_id","projection_revision","changed"]}},"provider_get_attempts":{"type":"integer","minimum":0},"provider_get_count_confirmed":{"type":"boolean"},"fanout_deliveries_created":{"type":"integer","minimum":0},"cursor":{"type":"string"},"failure":{"type":["object","null"],"properties":{"code":{"type":"string"},"target":{"type":["object","null"],"properties":{"entity_type":{"type":"string","enum":["Invoice","Payment"],"x-ir-enum-open":true,"description":"**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description."},"external_id":{"type":"string"}},"required":["entity_type","external_id"]}},"required":["code","target"]},"summary":{"type":"object","properties":{"known_transactions":{"type":"integer","minimum":0},"pending_refreshes":{"type":"integer","minimum":0},"pending_outbox":{"type":"integer","minimum":0},"delivery_states":{"type":"object","additionalProperties":{"type":"integer","minimum":0}},"delivery_sample_events":{"type":"integer","minimum":0},"recent_failures":{"type":"array","items":{"type":"object","properties":{"entity_type":{"type":"string"},"external_id":{"type":"string"},"reason":{"type":"string"},"occurred_at":{"type":"number"}},"required":["entity_type","external_id","reason","occurred_at"]}}},"required":["known_transactions","pending_refreshes","pending_outbox","delivery_states","delivery_sample_events","recent_failures"]}},"required":["binding","outcome","applied","provider_get_attempts","provider_get_count_confirmed","fanout_deliveries_created","cursor","failure","summary"]}},"required":["app_id","tenant_id","recovery"]}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Native company, generation or cursor unconfirmed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/connections/{id}/quickbooks/master/{entity}":{"get":{"tags":["QuickBooks"],"operationId":"lookupQuickBooksMasterData","summary":"Read exact current master data, including inactive state","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["Customer","Item","TaxCode","TaxRate"],"x-ir-enum-open":true,"description":"Supported master entity; tax entities have no write operation.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either."},"required":true,"description":"Supported master entity; tax entities have no write operation.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either.","name":"entity","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","pattern":"^\\d{1,32}$"},"required":false,"name":"external_id","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":100},"required":false,"name":"name","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Authoritative company-bound record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuickBooksMasterRecord"}}}},"400":{"description":"Invalid master request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Scoped mapping/record not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Master binding unconfirmed or conflicting","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Provider or app quota exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"502":{"description":"Provider read unconfirmed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/connections/{id}/quickbooks/master/mappings/{entity}/{erp_id}":{"get":{"tags":["QuickBooks"],"operationId":"getQuickBooksMasterMapping","summary":"Read or refresh stable ERP master mapping","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["Customer","Item","TaxCode","TaxRate"],"x-ir-enum-open":true,"description":"Supported master entity; tax entities have no write operation.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either."},"required":true,"description":"Supported master entity; tax entities have no write operation.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either.","name":"entity","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":128},"required":true,"name":"erp_id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","enum":["0","1"]},"required":false,"name":"refresh","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Stable mapping and drift/inactive state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuickBooksMasterMapping"}}}},"400":{"description":"Invalid master request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Scoped mapping/record not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Master binding unconfirmed or conflicting","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Provider or app quota exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"502":{"description":"Provider read unconfirmed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}},"post":{"tags":["QuickBooks"],"operationId":"bindQuickBooksMasterMapping","summary":"Explicitly bind verified provider master or reconcile an uncertain command","description":"Performs controlled GET reads and local mapping updates only. The chosen provider id must match current company and any pending command's frozen fields. TaxCode/TaxRate are read-only; no reactivation or tax-rate invention.","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","enum":["Customer","Item","TaxCode","TaxRate"],"x-ir-enum-open":true,"description":"Supported master entity; tax entities have no write operation.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either."},"required":true,"description":"Supported master entity; tax entities have no write operation.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either.","name":"entity","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":128},"required":true,"name":"erp_id","in":"path"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"tenant_id":{"type":"string","minLength":1},"expected_company_id":{"type":"string","pattern":"^\\d{1,32}$"},"external_id":{"type":"string","pattern":"^\\d{1,32}$"},"reconcile_command_id":{"type":"string","minLength":1,"maxLength":128}},"required":["tenant_id","expected_company_id","external_id"],"additionalProperties":false}}}},"responses":{"200":{"description":"Provider-verified local binding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuickBooksMasterRecord"}}}},"400":{"description":"Invalid master request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Scoped mapping/record not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Master binding unconfirmed or conflicting","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Provider or app quota exhausted","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"502":{"description":"Provider read unconfirmed","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/calendar/grants/{grant_id}/resources":{"get":{"tags":["Calendar"],"summary":"List selected Calendar resources.","description":"Returns only local selected-resource metadata for the grant's current callback epoch. This route performs no provider request and remains available when read admission is disabled.","operationId":"listCalendarResources","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"grant_id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":2048},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Current tenant-scoped selected Calendar resources.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarResourcesListResponse"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant or persisted grant binding denied.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Calendar grant not found in this scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Calendar state is unavailable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}},"post":{"tags":["Calendar"],"summary":"Select one discovered Calendar resource.","description":"Creates a local selected-resource binding from a recent completed discovery page. This endpoint performs no provider request or token refresh.","operationId":"selectCalendarResource","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"grant_id","in":"path"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelectCalendarResourceRequest"}}}},"responses":{"200":{"description":"Selected Calendar resource, including an exact idempotent retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarResource"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Provider, event-read scope or discovery proof denied.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Calendar grant or discovery page not found.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Selection identity, lifecycle or discovery publication is not current.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"410":{"description":"The discovery proof expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Calendar state is unavailable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/calendar/grants/{grant_id}":{"get":{"tags":["Calendar"],"summary":"Fetch scoped Calendar grant status.","description":"Returns persisted grant identity, lifecycle status and current read capability without decrypting credentials or requiring Calendar admission to be enabled.","operationId":"getCalendarGrant","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"grant_id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Tenant-scoped Calendar grant status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarGrant"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant or persisted grant binding denied.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Calendar grant not found in this scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Calendar state is unavailable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/calendar/grants/{grant_id}/revoke":{"post":{"tags":["Calendar"],"summary":"Revoke one Calendar grant.","description":"Establishes local denial and runs the grant-scoped native OAuth revocation journal. It remains reachable when Calendar read admission is disabled and never refreshes a token to offboard.","operationId":"revokeCalendarGrant","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"grant_id","in":"path"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarRevokeRequest"}}}},"responses":{"204":{"description":"Grant revoked, or the same revoke intent already completed."},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant scope denied.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Calendar grant not found in this scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"A different OAuth lifecycle operation owns the grant.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Provider revocation or local projection must be retried.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/calendar/resources/{resource_id}/detach":{"post":{"tags":["Calendar"],"summary":"Detach one selected Calendar resource.","description":"Tombstones only the scoped local resource binding. It does not refresh credentials, call Google, revoke the grant or affect sibling resources, and remains available when read admission is disabled.","operationId":"detachCalendarResource","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"resource_id","in":"path"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarDetachRequest"}}}},"responses":{"204":{"description":"Resource detached, or the same detach intent already completed."},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant or persisted resource binding denied.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Calendar resource not found in this scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"The resource generation or detach intent changed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Calendar state is unavailable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/calendar/pages/{page_id}":{"get":{"tags":["Calendar"],"summary":"Fetch one immutable Calendar result page.","description":"Returns the exact bounded page bytes while the originating grant/resource binding remains current and the root traversal is unexpired. This endpoint performs no provider request or token refresh.","operationId":"getCalendarPage","parameters":[{"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{43}$"},"required":true,"name":"page_id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Immutable Calendar page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CalendarPublicPage"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Current grant or selected-resource authorization denied.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Page not found in this app and tenant scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Grant generation changed or the page job is not completed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"410":{"description":"The scoped page expired.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"Calendar state is temporarily unavailable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/mappings/{sync_profile_id}/dry-run":{"post":{"tags":["Mappings"],"summary":"Preview the provider payload a sync_profile would build for a sample input.","operationId":"dryRunMapping","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"sync_profile_id","in":"path"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingDryRunRequest"}}}},"responses":{"200":{"description":"Dry-run result (no provider write, no D1 write).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingDryRunResult"}}}},"400":{"description":"Bad request (unknown object_type, malformed body, etc).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Provider not enabled or tenant mismatch.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"sync_profile not found in this scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/operations/execute":{"post":{"tags":["Operations"],"summary":"Enqueue a provider operation (sync recipe) for execution.","description":"QuickBooks Invoice CREATE uses erp.quickbooks.invoice.created v4 with existing native CustomerRef, ItemRef and TaxCodeRef values, TaxExcluded calculation and tax_declarations; historical v3 jobs remain replayable. CREATE rejects root customer/items and Invoice Id/SyncToken/sparse fields. Invoice UPDATE uses erp.quickbooks.invoice.updated v1, object_type invoice_update, an explicit immutable idempotency_key and ERP change ID, original_invoice company/Invoice/customer/CAD/tax evidence, and a sparse Invoice with the original Id and SyncToken. UPDATE retains every existing SalesItem line ID, ItemRef and TaxCodeRef exactly once and requires an open unpaid sale without payment or credit activity. For a taxable amount change on one existing line, the runtime derives a complete TxnTaxDetail from verified native rates and freezes the resulting provider body; callers still cannot send TxnTaxDetail. Native financial writes remain subject to policy qualification, global and tenant enablement, and emergency aborts. Google Calendar payloads use the standalone CalendarDiscoveryInitialPayload, CalendarDiscoveryResumePayload, CalendarEventsInitialPayload, and CalendarEventsResumePayload schemas published in components.schemas.","operationId":"executeOperation","parameters":[{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationsExecuteRequest"}}}},"responses":{"202":{"description":"Accepted; job enqueued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationsExecuteResult"}}}},"400":{"description":"Invalid request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Provider/tenant not authorized, or operation not enabled.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Recipe not found.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Recipe version retired, or idempotency identity conflict.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"410":{"description":"The Calendar continuation expired and a new root read is required.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"503":{"description":"tenant_enablement_unavailable — the per-tenant enablement read FAILED, so the runtime could not evaluate the rollout gate and refused rather than guess. Distinct from the 403: that one is a decision, this one is the absence of one. No `Retry-After` is sent, and the nonce for this request is already consumed — retry with a freshly signed request.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/usage":{"get":{"tags":["Usage"],"operationId":"getUsage","summary":"Read job usage for a tenant of the HMAC-authenticated app.","parameters":[{"schema":{"type":"string","minLength":1,"maxLength":200},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":10080,"default":1440},"required":false,"name":"window_minutes","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Current outcomes of retained jobs created in the bounded window. Not provider HTTP call counts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageResponse"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"Authentication missing or invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Caller lacks scope or tenant does not belong to the verified app.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"422":{"description":"usage.window_too_dense: choose a shorter window. No partial counts returned.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/sync-jobs":{"get":{"tags":["SyncJobs"],"summary":"List sync jobs for the requesting tenant.","operationId":"listSyncJobs","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"connection_id","in":"query"},{"schema":{"type":"string","enum":["queued","processing","completed","failed","dead_lettered","cancelled","manually_resolved"],"x-ir-enum-open":true,"description":"Lifecycle state of the job.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either."},"required":false,"description":"Lifecycle state of the job.\n\n**Extensible enum.** New members may be added in a MINOR release without a version bump. Clients MUST tolerate values they do not recognise — map an unknown value to an explicit \"unrecognised\" case rather than rejecting the response. See \"Enum stability\" in the API description.\n\nAs a request value, only the members listed here are accepted: the runtime validates this field and returns `400` on anything else. New members may become accepted in a MINOR release, so do not treat this list as permanently fixed — but do not send a value that is not on it either.","name":"status","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":200},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string","minLength":1},"required":false,"name":"cursor","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Sync jobs page.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncJobsListResponse"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/sync-jobs/{id}":{"get":{"tags":["SyncJobs"],"summary":"Fetch a single sync job with its step timeline.","operationId":"getSyncJob","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"responses":{"200":{"description":"Job with steps.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncJobWithStepsDTO"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Job not found in this scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}},"/v1/sync-jobs/{id}/replay":{"post":{"tags":["SyncJobs"],"summary":"Replay a failed/dead-lettered sync job (whole job; tenant-scoped).","operationId":"replaySyncJob","parameters":[{"schema":{"type":"string","minLength":1},"required":true,"name":"id","in":"path"},{"schema":{"type":"string","minLength":1},"required":true,"name":"tenant_id","in":"query"},{"schema":{"type":"string","description":"Application identifier supplied by the runtime operator."},"required":true,"description":"Application identifier supplied by the runtime operator.","name":"x-ir-app-id","in":"header"},{"schema":{"type":"string","description":"Service-key identifier within the application; this is not the secret."},"required":true,"description":"Service-key identifier within the application; this is not the secret.","name":"x-ir-key-id","in":"header"},{"schema":{"type":"string","description":"Request issuance time as canonical unix seconds, within the ±300s window."},"required":true,"description":"Request issuance time as canonical unix seconds, within the ±300s window.","name":"x-ir-timestamp","in":"header"},{"schema":{"type":"string","minLength":22,"maxLength":64,"description":"Single-use request nonce; replay is rejected."},"required":true,"description":"Single-use request nonce; replay is rejected.","name":"x-ir-nonce","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncJobReplayRequest"}}}},"responses":{"202":{"description":"Replay job enqueued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncJobReplayResult"}}}},"400":{"description":"Request validation failed — `request.validation_failed`. The body, query, path, header or cookie parameters did not match the declared schema. `errors[]` names each offending field, prefixed with where it was read from (`body.`, `query.`, `path.`, `header.`, `cookie.`).","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"401":{"description":"HMAC invalid.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"403":{"description":"Tenant mismatch.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"404":{"description":"Job not found in this scope.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"409":{"description":"Job not replayable.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}},"429":{"description":"Rate limited — `rate_limit.shield_shed` (pre-auth IP shield) or `rate_limit.quota_exceeded` (per-app quota). `Retry-After` carries the full window in seconds; re-sign every retry with a fresh `X-IR-Nonce` and `X-IR-Timestamp`.","headers":{"Retry-After":{"description":"Full rate-limit window in seconds — not the time remaining in it.","schema":{"type":"integer"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetails"}}}}}}}},"webhooks":{}}