{"openapi":"3.1.0","info":{"title":"Peak Commerce Agent API v1","description":"Action-oriented API for AI agents to manage accounts, subscriptions, orders, payments, invoices, and webhooks. Every response includes a `nextActions` array for HATEOAS-style navigation and a `requestId` for correlation.\n\n## Conventions\n\n### Request IDs\nEvery request is assigned a unique `X-Request-ID`. Supply your own (≤64 alphanumeric/dash/underscore chars) or the server will generate one. The same ID is returned in the response header and in every JSON body as `requestId`.\n\n### Pagination\nList endpoints accept `limit` (1–100, default 20), `offset` (default 0), `sort` (field name), and `sortDir` (`asc`|`desc`). Responses include a `pagination` envelope: `{ total, limit, offset, hasMore }`.\n\n### Idempotency\nAll `POST` and `PATCH` endpoints accept an `Idempotency-Key` header (≤255 chars). Replays with the same key+route within 24 hours return the original response without executing the operation again. The replayed response includes `X-Idempotency-Replayed: true`.\n\n### Errors\nAll errors use a stable envelope: `{ error: { code, message, details? }, requestId }`. `code` is a stable machine-readable string. `details` is present on validation errors and contains per-field information.\n\n### Versioning\nThe API version is embedded in the path (`/api/v1`). Deprecated endpoints will carry `Deprecation` and `Sunset` response headers before removal.\n\n## Webhook Signature Scheme\n\nEvery webhook POST request is signed using HMAC-SHA256. The signing secret is generated for you — you never choose it — and is returned once when the subscription is created (`POST /webhooks`) and once more each time you rotate it (`POST /webhooks/{id}/rotate-secret`). Store it securely.\n\n### Headers sent with each delivery\n\n| Header | Value |\n|---|---|\n| `X-Webhook-Signature` | one or more comma-separated `sha256=<hex-digest>` values |\n| `X-Webhook-Event` | event type name (e.g. `subscription.created`) |\n| `X-Webhook-Timestamp` | ISO 8601 timestamp of the event |\n\n### Verifying the signature\n\n1. Read the raw request body as a UTF-8 string — do **not** parse it first.\n2. Compute `HMAC-SHA256(secret, rawBody)` and hex-encode the digest.\n3. Split `X-Webhook-Signature` on commas and accept the delivery if **any** entry matches your computed `sha256=<hex>` value. Use a constant-time comparison to prevent timing attacks.\n\n**Always split on commas, even if you never rotate.** During a rotation the header carries a signature under each live secret, so a verifier that compares the whole header against a single digest rejects every delivery for the length of the overlap window.\n\nExample (Node.js):\n```js\nconst crypto = require('crypto');\nfunction verifyWebhook(secret, rawBody, signatureHeader) {\n  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');\n  const expectedBuf = Buffer.from(expected);\n  return signatureHeader.split(',').some((offered) => {\n    const offeredBuf = Buffer.from(offered.trim());\n    return offeredBuf.length === expectedBuf.length && crypto.timingSafeEqual(offeredBuf, expectedBuf);\n  });\n}\n```\n\n### Rotating the secret\n\n`POST /webhooks/{id}/rotate-secret` returns a new secret that signs deliveries immediately, while the previous secret keeps verifying for 24 hours. Deploy the new secret to your endpoint any time inside that window — deliveries carry a signature under both, so nothing is dropped during the cutover. After the window closes, only the new secret is used.\n\n### Retry policy and dead-lettering\n\nFailed deliveries are retried with exponential backoff (2^attempt seconds) up to 5 attempts. After the 5th failure the delivery enters `dead_letter` status. Dead-lettered deliveries are queryable via `GET /webhooks/{id}/deliveries?status=dead_letter` and can be re-enqueued at any time via `POST /webhooks/{id}/deliveries/{deliveryId}/replay`.","version":"1.0.0"},"servers":[{"url":"https://api.peakcommerce.app/api/v1","description":"Production"},{"url":"https://api.sandbox.peakcommerce.app/api/v1","description":"Sandbox"}],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Scoped **secret** API key — send an `sk_live_…` value (keys issued before the typed format carry a bare `pk_<hex>` prefix and are also secret; they still work). A **publishable** `pk_live_…`/`pk_test_…` key is browser-safe, carries no scopes, and is refused by every endpoint in this spec with `publishable_key_not_allowed`. Coarse scopes: read, commerce, admin (admin covers everything below). Narrow per-resource scopes: journeys|pages :read|write|publish|delete, productsets :read|write|publish|delete, components|rules :read|write|delete, context :read (read-only context tier), catalog :read|write-native. `publish` is never implied by `write`."},"BearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"Limit":{"name":"limit","in":"query","description":"Max items to return (1–100, default 20)","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},"Offset":{"name":"offset","in":"query","description":"Number of items to skip (default 0)","schema":{"type":"integer","minimum":0,"default":0}},"Sort":{"name":"sort","in":"query","description":"Field to sort by","schema":{"type":"string"}},"SortDir":{"name":"sortDir","in":"query","description":"Sort direction","schema":{"type":"string","enum":["asc","desc"],"default":"asc"}},"StatusFilter":{"name":"status","in":"query","description":"Filter by status","schema":{"type":"string"}}},"headers":{"X-Request-ID":{"description":"Unique request identifier for correlation","schema":{"type":"string"}},"Idempotency-Key":{"description":"Client-supplied idempotency key (≤255 chars). Replays within 24h return the original response.","schema":{"type":"string"}},"X-Idempotency-Replayed":{"description":"Present with value `true` when the response is a cached replay of a prior idempotent request","schema":{"type":"string","enum":["true"]}},"Sunset":{"description":"RFC 8594 date after which this endpoint will be removed","schema":{"type":"string"}},"Deprecation":{"description":"RFC 8594 deprecation date","schema":{"type":"string"}}},"schemas":{"ErrorDetail":{"type":"object","required":["field","message"],"properties":{"field":{"type":"string"},"message":{"type":"string"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine-readable error code (e.g. not_found, validation_failed, policy_violation, internal_error)"},"message":{"type":"string","description":"Human-readable error description"},"details":{"type":"array","items":{"$ref":"#/components/schemas/ErrorDetail"},"description":"Per-field validation details (present on validation_failed errors)"}}},"requestId":{"type":"string","description":"Unique request ID for correlation with server logs"}}},"Pagination":{"type":"object","required":["total","limit","offset","hasMore"],"properties":{"total":{"type":"integer","description":"Total number of items matching the query"},"limit":{"type":"integer"},"offset":{"type":"integer"},"hasMore":{"type":"boolean","description":"Whether more items exist beyond the current page"}}},"NextAction":{"type":"object","properties":{"rel":{"type":"string"},"method":{"type":"string"},"href":{"type":"string"}}},"Account":{"type":"object","properties":{"id":{"type":"string"},"accountNumber":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"balance":{"type":"number"},"currency":{"type":"string"},"billToContact":{"type":"object"},"soldToContact":{"type":"object"}}},"Subscription":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"priceCents":{"type":"integer"},"currency":{"type":"string"},"billingInterval":{"type":"string"}}},"Order":{"type":"object","properties":{"id":{"type":"string"},"orderNumber":{"type":"string"},"status":{"type":"string"},"totalCents":{"type":"integer"},"currency":{"type":"string"},"items":{"type":"array"}}},"CartItem":{"type":"object","description":"A single line item in a cart","properties":{"id":{"type":"string","format":"uuid"},"cartId":{"type":"string","format":"uuid"},"productId":{"type":"string","format":"uuid","nullable":true,"description":"Catalog product this line maps to (null for free-form lines)"},"offerId":{"type":"string","format":"uuid","nullable":true},"name":{"type":"string"},"description":{"type":"string","nullable":true},"quantity":{"type":"integer"},"unitPriceCents":{"type":"integer"},"totalPriceCents":{"type":"integer","description":"unitPriceCents × quantity"},"currency":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Cart":{"type":"object","description":"A cart with its line items and computed totals. Totals are recalculated server-side on every item change, including any applicable offer discount.","properties":{"id":{"type":"string","format":"uuid"},"sessionId":{"type":"string","description":"Caller-supplied session identifier the cart is keyed to"},"userId":{"type":"string","format":"uuid","nullable":true},"status":{"type":"string","example":"active"},"offerId":{"type":"string","format":"uuid","nullable":true,"description":"Offer currently applied to the cart (via promo code or auto-apply), null when none"},"subtotalCents":{"type":"integer","description":"Sum of line totals before discounts"},"discountCents":{"type":"integer","description":"Discount from the applied offer"},"totalCents":{"type":"integer","description":"subtotalCents − discountCents (never negative)"},"currency":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/CartItem"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Tag":{"type":"object","description":"A tenant-scoped taxonomy tag. Slugs are unique per tenant.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","maxLength":100},"slug":{"type":"string","maxLength":120,"description":"Lowercase alphanumeric with hyphens, unique within the tenant"},"description":{"type":"string","nullable":true,"maxLength":2000},"color":{"type":"string","nullable":true,"description":"Hex color in #RRGGBB form"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"Invoice":{"type":"object","properties":{"id":{"type":"string"},"invoiceNumber":{"type":"string"},"invoiceDate":{"type":"string"},"dueDate":{"type":"string"},"amount":{"type":"number"},"balance":{"type":"number"},"currency":{"type":"string"},"status":{"type":"string"}}},"PaymentSession":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"amountCents":{"type":"integer"},"currency":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"PaymentMethod":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"cardType":{"type":"string"},"lastFour":{"type":"string"},"expirationMonth":{"type":"integer"},"expirationYear":{"type":"integer"},"isDefault":{"type":"boolean"}}},"WebhookSubscription":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"WebhookDelivery":{"type":"object","description":"A single webhook delivery attempt record.","properties":{"id":{"type":"string","format":"uuid"},"webhookSubscriptionId":{"type":"string","format":"uuid"},"eventType":{"type":"string","description":"The event type that triggered this delivery."},"status":{"type":"string","enum":["pending","retrying","delivered","dead_letter"],"description":"Current delivery status. `dead_letter` means max retry attempts were exhausted."},"statusCode":{"type":"integer","nullable":true,"description":"HTTP status code returned by the consumer endpoint."},"responseBody":{"type":"string","nullable":true,"description":"First 1000 chars of the consumer response body."},"attempts":{"type":"integer","description":"Number of delivery attempts made so far."},"maxAttempts":{"type":"integer","description":"Maximum number of attempts before dead-lettering."},"lastAttemptAt":{"type":"string","format":"date-time","nullable":true},"nextRetryAt":{"type":"string","format":"date-time","nullable":true,"description":"When the next retry is scheduled (null if delivered or dead-lettered)."},"createdAt":{"type":"string","format":"date-time"}}},"WebhookEventType":{"type":"object","properties":{"name":{"type":"string","description":"Event type identifier to use in subscription `events` arrays."},"category":{"type":"string","description":"Logical grouping of the event."},"description":{"type":"string","description":"Human-readable description of when this event fires."}}},"Product":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"sku":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"productType":{"type":"string"},"priceCents":{"type":"integer"},"currency":{"type":"string"},"billingInterval":{"type":"string"},"isActive":{"type":"boolean"},"lifecycleRules":{"type":"object","description":"Applicable business rules governing lifecycle operations","properties":{"upgrade":{"type":"object","nullable":true,"description":"Rule for plan upgrades (timing, proration)"},"downgrade":{"type":"object","nullable":true,"description":"Rule for plan downgrades"},"cancellation":{"type":"object","nullable":true,"description":"Rule for cancellations"},"addOn":{"type":"object","nullable":true,"description":"Rule for add-on operations"}}},"pricingRules":{"type":"object","properties":{"hasDynamicPricing":{"type":"boolean","description":"Whether multiple pricing tiers exist"},"pricingTierCount":{"type":"integer","description":"Number of pricing tiers"}}}}},"CatalogResponse":{"type":"object","properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"},"description":"Enriched product list with lifecycle and pricing rules"},"paymentPages":{"type":"array","items":{"type":"object"},"description":"Payment page summaries"}}},"PreviewResponse":{"type":"object","description":"Preview of an operation showing effective date, financial impact, applicable business rule, and policy evaluation. Read-only — no state changes.","properties":{"subscriptionId":{"type":"string"},"currentProduct":{"type":"object","nullable":true},"targetProduct":{"type":"object","nullable":true},"direction":{"type":"string","enum":["upgrade","downgrade"]},"effectiveDate":{"type":"string","format":"date-time"},"financialImpact":{"type":"object","properties":{"currentPriceCents":{"type":"integer"},"newPriceCents":{"type":"integer"},"creditCents":{"type":"integer"},"chargeCents":{"type":"integer"},"netCents":{"type":"integer"},"currency":{"type":"string"}}},"businessRule":{"type":"object","nullable":true,"description":"The business rule governing this operation, or null if no rule applies"},"policyEvaluation":{"type":"object","properties":{"allowed":{"type":"boolean"},"evaluations":{"type":"array","items":{"type":"object"}}}},"resolvedPrice":{"type":"object","description":"Resolved pricing including tier information"}}},"PricingResolution":{"type":"object","description":"Resolved effective price after applying pricing tiers, quantity, and offer discounts","properties":{"productId":{"type":"string"},"productName":{"type":"string"},"basePriceCents":{"type":"integer","description":"Price before offer discounts"},"resolvedPriceCents":{"type":"integer","description":"Final price after all rules and discounts"},"currency":{"type":"string"},"quantity":{"type":"integer"},"context":{"type":"object","description":"Customer context used for resolution"},"rulesApplied":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string"},"type":{"type":"string"},"adjustment":{"type":"string"},"detail":{"type":"string"}}}},"availableOffers":{"type":"array","items":{"type":"object"}}}},"Manifest":{"type":"object","properties":{"operations":{"type":"array","items":{"type":"object"}},"paymentPages":{"type":"array","items":{"type":"object"}},"capabilities":{"type":"object"}}},"Address":{"type":"object","description":"Geographic address used for tax and shipping calculation.","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 or alpha-3 country code"},"state":{"type":"string"},"postalCode":{"type":"string"},"city":{"type":"string"},"line1":{"type":"string"}}},"TaxBreakdownLine":{"type":"object","properties":{"description":{"type":"string"},"amountCents":{"type":"integer"},"rate":{"type":"number"}}},"TaxResult":{"type":"object","properties":{"amountCents":{"type":"integer","description":"Total tax in cents"},"rate":{"type":"number","description":"Effective tax rate (0.0–1.0)"},"provider":{"type":"string","description":"Tax provider identifier (e.g. 'none', 'avalara')"},"breakdown":{"type":"array","items":{"$ref":"#/components/schemas/TaxBreakdownLine"}}}},"ShippingOption":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"carrier":{"type":"string"},"estimatedDays":{"type":"integer","nullable":true},"priceCents":{"type":"integer"},"currency":{"type":"string"},"description":{"type":"string"}}},"OrderTotalPreview":{"type":"object","description":"Breakdown of an order total including line items, tax, and shipping. Read-only — does not create an order.","properties":{"subtotalCents":{"type":"integer"},"taxAmountCents":{"type":"integer"},"shippingAmountCents":{"type":"integer"},"grandTotalCents":{"type":"integer"},"currency":{"type":"string"},"tax":{"$ref":"#/components/schemas/TaxResult"},"shipping":{"type":"object","properties":{"selectedOption":{"$ref":"#/components/schemas/ShippingOption","nullable":true},"availableOptions":{"type":"array","items":{"$ref":"#/components/schemas/ShippingOption"}}}},"pricingBreakdown":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"quantity":{"type":"integer"},"unitPriceCents":{"type":"integer"},"lineTotalCents":{"type":"integer"},"priceSource":{"type":"string","enum":["pricing_engine","caller_supplied"]}}}}}},"UsageSummary":{"type":"object","description":"Per-key API usage metrics aggregated by route and time bucket","properties":{"summary":{"type":"object","properties":{"totalRequests":{"type":"integer","description":"Total API requests in the window"},"totalErrors":{"type":"integer","description":"Requests that returned HTTP 4xx or 5xx"},"totalRateLimited":{"type":"integer","description":"Requests that were rate-limited (HTTP 429)"},"avgLatencyMs":{"type":"integer","description":"Average server latency in milliseconds"},"errorRate":{"type":"number","description":"Fraction of requests that were errors (0.0–1.0)"}}},"byRoute":{"type":"array","description":"Breakdown per route+method, sorted by request volume descending","items":{"type":"object","properties":{"route":{"type":"string","example":"/accounts/:id"},"method":{"type":"string","example":"GET"},"requests":{"type":"integer"},"errors":{"type":"integer"},"rateLimited":{"type":"integer"},"avgLatencyMs":{"type":"integer"}}}},"byBucket":{"type":"array","description":"Time series at 1-minute granularity, ordered chronologically","items":{"type":"object","properties":{"timeBucket":{"type":"string","format":"date-time"},"requests":{"type":"integer"},"errors":{"type":"integer"},"rateLimited":{"type":"integer"},"avgLatencyMs":{"type":"integer"}}}},"window":{"type":"object","properties":{"from":{"type":"string","format":"date-time"},"to":{"type":"string","format":"date-time"},"granularity":{"type":"string","example":"minute"}}},"apiKey":{"type":"object","nullable":true,"description":"Details of the API key the usage is scoped to (absent when viewing all keys)","properties":{"id":{"type":"string"},"prefix":{"type":"string"},"name":{"type":"string","nullable":true}}}}},"ComponentStatus":{"type":"object","required":["name","status"],"properties":{"name":{"type":"string","description":"Component identifier","example":"database"},"status":{"type":"string","enum":["operational","degraded","incident"],"description":"Current health of the component"},"latencyMs":{"type":"integer","nullable":true,"description":"Round-trip latency in ms (where applicable)"},"detail":{"type":"string","nullable":true,"description":"Human-readable detail about the component state"}}},"StatusResponse":{"type":"object","required":["status","components","cachedAt"],"properties":{"status":{"type":"string","enum":["operational","degraded","incident"],"description":"Worst-case status across all components"},"components":{"type":"array","items":{"$ref":"#/components/schemas/ComponentStatus"},"description":"Individual component health: database, billing_adapter, webhooks, cache"},"cachedAt":{"type":"string","format":"date-time","description":"When this status snapshot was computed (TTL: 30 s)"}}},"ErrorCodes":{"type":"object","description":"Catalog of all stable v1 API error codes. Every error response from /api/v1 includes a `code` field that matches one of these entries. Use `retryable` to decide whether to back off and retry.","properties":{"codes":{"type":"array","items":{"type":"object","required":["code","httpStatus","description","retryable"],"properties":{"code":{"type":"string","description":"Stable machine-readable error code","example":"rate_limit_exceeded"},"description":{"type":"string","description":"What the error means and how to resolve it"},"retryable":{"type":"boolean","description":"Whether the caller should retry (with back-off) after this error"}}}}}},"Refund":{"type":"object","description":"A refund issued against a successful payment session","properties":{"id":{"type":"string"},"paymentId":{"type":"string"},"invoiceId":{"type":"string","nullable":true},"amountCents":{"type":"integer"},"currency":{"type":"string"},"status":{"type":"string","enum":["pending","succeeded","failed"]},"isFullRefund":{"type":"boolean"},"reason":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"CreditApplication":{"type":"object","properties":{"paymentId":{"type":"string","nullable":true},"amountCents":{"type":"integer"},"appliedAt":{"type":"string","format":"date-time"}}},"Credit":{"type":"object","description":"A credit issued to an account that can be applied to reduce future payment amounts","properties":{"id":{"type":"string"},"accountId":{"type":"string"},"amountCents":{"type":"integer","description":"Original credit amount"},"remainingCents":{"type":"integer","description":"Remaining unapplied balance"},"currency":{"type":"string"},"status":{"type":"string","enum":["active","applied","expired","cancelled"]},"reason":{"type":"string","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"applications":{"type":"array","items":{"$ref":"#/components/schemas/CreditApplication"}}}},"CreditBalance":{"type":"object","description":"Aggregate credit balance for an account","properties":{"accountId":{"type":"string"},"balanceCents":{"type":"integer","description":"Total active, non-expired credit balance in cents"},"currency":{"type":"string"},"activeCredits":{"type":"integer","description":"Number of active non-expired credits"},"expiredCredits":{"type":"integer","description":"Number of credits that have expired"},"totalCredits":{"type":"integer","description":"Total credit records (all statuses)"}}},"AdminUser":{"type":"object","description":"A platform user (admin, customer, CSR, Sales, or partner)","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"portalType":{"type":"string","enum":["admin","customer","csr","sales","partner"]},"profileId":{"type":"string","format":"uuid"},"isActive":{"type":"boolean"},"externalAccountId":{"type":"string","nullable":true},"externalContactId":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Role":{"type":"object","description":"A user profile/role defining a named set of permissions","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"role":{"type":"string","enum":["admin","customer","csr","sales","partner"]},"isActive":{"type":"boolean"},"isSystem":{"type":"boolean"},"permissions":{"type":"object","description":"Structured permission set for this role"},"createdAt":{"type":"string","format":"date-time"}}},"AdminPage":{"type":"object","description":"A portal page, payment page, storefront page, or checkout page","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"type":{"type":"string"},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"CustomComponent":{"type":"object","description":"A custom UI component (code or template type) for the Puck editor","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"displayName":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"componentType":{"type":"string","enum":["code","template"]},"category":{"type":"string","nullable":true},"editorContexts":{"type":"array","items":{"type":"string"}},"audienceRoles":{"type":"array","items":{"type":"string"}},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"PageTemplate":{"type":"object","description":"A reusable page template for the editor","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"pageType":{"type":"string"},"thumbnail":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Partner":{"type":"object","description":"A partner user enriched with their partner profile","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"isActive":{"type":"boolean"},"partnerProfile":{"type":"object","nullable":true,"properties":{"companyName":{"type":"string","nullable":true},"contactName":{"type":"string","nullable":true},"contactEmail":{"type":"string","nullable":true},"tier":{"type":"string","nullable":true},"commissionStructureId":{"type":"string","nullable":true}}}}},"CommissionStructure":{"type":"object","description":"A commission calculation structure for partner earnings","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"commissionType":{"type":"string"},"rate":{"type":"number","nullable":true},"flatAmount":{"type":"number","nullable":true},"currency":{"type":"string","nullable":true},"isActive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"}}},"AuditLog":{"type":"object","description":"An immutable record of a platform action","properties":{"id":{"type":"string","format":"uuid"},"userId":{"type":"string","format":"uuid","nullable":true,"description":"The user who performed the action (null for API key actions)"},"action":{"type":"string","description":"Namespaced action identifier (e.g. api.user.create)"},"resourceType":{"type":"string","nullable":true},"resourceId":{"type":"string","nullable":true},"details":{"type":"object","description":"Action-specific metadata"},"ipAddress":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"ApiKeyRecord":{"type":"object","description":"An API key record (secret is never returned except at create/rotate time)","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"keyPrefix":{"type":"string","description":"First 10 characters of the key for identification"},"scopes":{"type":"array","items":{"type":"string","enum":["read","commerce","admin"]}},"rateLimitTier":{"type":"string","enum":["standard","premium","unlimited"]},"isActive":{"type":"boolean"},"revokedAt":{"type":"string","format":"date-time","nullable":true},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"ApiKeyCreateResponse":{"type":"object","description":"Response from creating or rotating an API key — includes the one-time plaintext secret","properties":{"data":{"allOf":[{"$ref":"#/components/schemas/ApiKeyRecord"},{"type":"object","properties":{"secret":{"type":"string","description":"Plaintext API key — shown only once, store securely"}},"required":["secret"]}]},"meta":{"type":"object","properties":{"secretNote":{"type":"string"},"replacedKeyId":{"type":"string","description":"ID of the key that was revoked (rotate only)"}}},"requestId":{"type":"string"}}},"ErrorCodesExample":{"type":"object","example":{"codes":[{"code":"api_key_required","httpStatus":401,"description":"No API key was supplied.","retryable":false},{"code":"rate_limit_exceeded","httpStatus":429,"description":"Rate limit exceeded. Check X-RateLimit-Reset.","retryable":true},{"code":"validation_failed","httpStatus":400,"description":"Request body failed schema validation. See details.","retryable":false},{"code":"not_found","httpStatus":404,"description":"The resource does not exist.","retryable":false},{"code":"internal_error","httpStatus":500,"description":"Unexpected server error. Include requestId in support tickets.","retryable":true}]}}}},"paths":{"/manifest":{"get":{"summary":"Get tenant-level capability manifest describing all available operations","operationId":"getManifest","tags":["Discovery"],"responses":{"200":{"description":"Tenant capability manifest","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Manifest"}}}}}}},"/catalog":{"get":{"summary":"List catalog products enriched with lifecycle rules and pricing indicators, plus payment pages","operationId":"getCatalog","tags":["Discovery"],"security":[{"ApiKeyAuth":["catalog:read"]}],"responses":{"200":{"description":"Enriched catalog with products and payment pages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogResponse"}}}}}}},"/catalog/products":{"get":{"summary":"List catalog products, flagged by whether the API may write them","description":"Each row carries `writable`: true for native `internal`/`goods` products, false for provider-`synced` rows and `bundle` parents — so an agent can tell what it may edit without attempting a write.","operationId":"listCatalogProducts","tags":["Discovery"],"security":[{"ApiKeyAuth":["catalog:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"productType","in":"query","description":"Filter by product type (synced, internal, bundle, goods)","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated products"}}},"post":{"summary":"Create a native catalog product","description":"`productType` is REQUIRED and must be `internal` or `goods`. Provider-owned (`synced`) products cannot be created, and `provider` / `externalId` / `sourceIntegrationId` cannot be set — those mark a row as sync-owned, and a later catalog sync could then match and overwrite it.","operationId":"createCatalogProduct","tags":["Discovery"],"security":[{"ApiKeyAuth":["catalog:write-native"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["productType","name"],"properties":{"productType":{"type":"string","enum":["internal","goods"]},"name":{"type":"string"},"sku":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"priceCents":{"type":"integer"},"currency":{"type":"string"},"imageUrl":{"type":"string"},"isActive":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Product created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/catalog/products/{id}":{"get":{"summary":"Get one catalog product","operationId":"getCatalogProduct","tags":["Discovery"],"security":[{"ApiKeyAuth":["catalog:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Product, with a `writable` flag"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a native catalog product","description":"Refused with 409 for provider-`synced` rows (`product_is_provider_owned`) and for `bundle` parents (`product_is_bundle`). `productType` cannot be changed.","operationId":"updateCatalogProduct","tags":["Discovery"],"security":[{"ApiKeyAuth":["catalog:write-native"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"priceCents":{"type":"integer"},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Product updated"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Product is provider-owned or a bundle","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/capabilities":{"get":{"summary":"Discover merchant capabilities including payment methods and supported currencies","operationId":"getCapabilities","tags":["Discovery"],"responses":{"200":{"description":"Merchant capabilities"}}}},"/accounts":{"post":{"summary":"Create a new account (organization)","operationId":"createAccount","tags":["Accounts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"externalId":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"website":{"type":"string"},"industry":{"type":"string"}}}}}},"responses":{"201":{"description":"Account created"},"400":{"description":"Validation error"}}}},"/accounts/{id}":{"get":{"summary":"Get account details by ID. For external billing accounts, fetches from the billing adapter.","operationId":"getAccount","tags":["Accounts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Account details"},"404":{"description":"Account not found"}}},"patch":{"summary":"Update account details","operationId":"updateAccount","tags":["Accounts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}}}}},"responses":{"200":{"description":"Account updated"},"404":{"description":"Not found"}}}},"/accounts/{id}/invoices":{"get":{"summary":"List all invoices for an account via the billing adapter","operationId":"getAccountInvoices","tags":["Invoices"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Sort"},{"$ref":"#/components/parameters/SortDir"}],"responses":{"200":{"description":"Paginated list of invoices","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No billing integration configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/accounts/{id}/subscriptions":{"get":{"summary":"List subscriptions for an account via the billing adapter","operationId":"getAccountSubscriptions","tags":["Subscriptions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Sort"},{"$ref":"#/components/parameters/SortDir"}],"responses":{"200":{"description":"Paginated list of subscriptions","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No billing integration configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/accounts/{id}/payment-methods":{"get":{"summary":"List payment methods for an account via the billing adapter","operationId":"getAccountPaymentMethods","tags":["PaymentMethods"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of payment methods","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No billing integration configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/contacts":{"get":{"summary":"List contacts for the tenant, optionally filtered by exact email address","description":"Resolve a person without an id — pass `email` to look up a contact directly. Scoped to the tenant that issued the API key; a match in another tenant is never returned.","operationId":"listContacts","tags":["Contacts"],"parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","format":"email"},"description":"Exact-match email filter. A miss returns an empty page, not a 404."},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of contacts","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope — requires 'read'","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/contacts/{id}":{"get":{"summary":"Get a contact by ID","description":"Resolves a contactId — for example the one carried on a subscription — into the contact record, including its email address. Visibility is tenant-wide: an API key is a tenant-level credential and is not subject to the per-user record scope that applies to a signed-in user.","operationId":"getContact","tags":["Contacts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Contact details","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope — requires 'read'","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/subscriptions":{"get":{"summary":"List all subscriptions for the tenant","operationId":"listSubscriptions","tags":["Subscriptions"],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Sort"},{"$ref":"#/components/parameters/SortDir"},{"$ref":"#/components/parameters/StatusFilter"}],"responses":{"200":{"description":"Paginated list of subscriptions","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a new subscription","operationId":"createSubscription","tags":["Subscriptions"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"Idempotency key to safely retry without creating duplicates","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["productId"],"properties":{"productId":{"type":"string"},"contactId":{"type":"string"},"organizationId":{"type":"string"},"priceCents":{"type":"integer"},"currency":{"type":"string"},"billingInterval":{"type":"string"},"externalId":{"type":"string"}}}}}},"responses":{"201":{"description":"Subscription created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/subscriptions/{id}":{"get":{"summary":"Get subscription details","operationId":"getSubscription","tags":["Subscriptions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription details"}}}},"/entitlements/subscription/{subscriptionId}":{"get":{"summary":"Resolve the entitlement set granted to one subscription","description":"Returns the final, resolved entitlement values for a subscription — the plan defaults merged with any active per-customer override. Overrides are never revealed as such; the response is the answer, not its derivation. `resolvedFrom` names the product the values came from: add-on products do not contribute in v1.","operationId":"resolveSubscriptionEntitlements","tags":["Entitlements"],"parameters":[{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Resolved entitlement set"},"404":{"description":"Subscription not found in this tenant"}}}},"/entitlements/contact/{contactId}":{"get":{"summary":"Resolve entitlements for every subscription a contact holds","description":"Returns one resolved entitlement set per subscription, keyed by subscription id. Deliberately NOT merged into a single object: a contact may hold several subscriptions whose values disagree, and there is no defensible merge rule for conflicting config values.","operationId":"resolveContactEntitlements","tags":["Entitlements"],"parameters":[{"name":"contactId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Resolved entitlement sets, one per subscription"}}}},"/subscriptions/{id}/change-plan":{"post":{"summary":"Change the plan for a subscription (update product, price, interval)","operationId":"changeSubscriptionPlan","tags":["Subscriptions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"productId":{"type":"string"},"priceCents":{"type":"integer"},"billingInterval":{"type":"string"}}}}}},"responses":{"200":{"description":"Plan changed"}}}},"/subscriptions/{id}/change-quantity":{"post":{"summary":"Change the quantity of a subscription charge. Routes through the configured billing integration and applies any plan-dependency rules for the charge.","operationId":"changeSubscriptionQuantity","tags":["Subscriptions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Subscription ID"},{"name":"Idempotency-Key","in":"header","description":"Idempotency key to safely retry without applying the change twice","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["accountId","chargeId","newQuantity"],"properties":{"accountId":{"type":"string","description":"Billing account the subscription belongs to — verified against the subscription before the change is applied"},"chargeId":{"type":"string","description":"The subscription charge whose quantity is being changed"},"newQuantity":{"type":"integer","minimum":0},"editedField":{"type":"string","enum":["quantity","includedUnits"],"description":"Which field the new value applies to (defaults to quantity)"},"catalogChargeId":{"type":"string","description":"Catalog charge ID, used to resolve plan-dependency rules"},"integrationId":{"type":"string","description":"Billing integration to use. Defaults to the tenant's active integration."}}}}}},"responses":{"200":{"description":"Quantity changed — returns the billing integration's change result"},"400":{"description":"Validation error, no billing integration configured, or the integration does not support self-service changes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Subscription does not belong to the supplied account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/subscriptions/{id}/cancel":{"post":{"summary":"Cancel a subscription","operationId":"cancelSubscription","tags":["Subscriptions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription cancelled"}}}},"/subscriptions/{id}/renew":{"post":{"summary":"Renew a subscription","operationId":"renewSubscription","tags":["Subscriptions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription renewed"}}}},"/orders":{"get":{"summary":"List all orders for the tenant","operationId":"listOrders","tags":["Orders"],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Sort"},{"$ref":"#/components/parameters/SortDir"},{"$ref":"#/components/parameters/StatusFilter"}],"responses":{"200":{"description":"Paginated list of orders","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a new order","operationId":"createOrder","tags":["Orders"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"Idempotency key to safely retry without creating duplicates","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"cartId":{"type":"string","description":"When set, the order is built from this cart and its offer discount is enforced against the tenant's discount_cap policy"},"contactId":{"type":"string"},"organizationId":{"type":"string"},"items":{"type":"array"},"totalCents":{"type":"integer"},"currency":{"type":"string"}}}}}},"responses":{"201":{"description":"Order created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Policy violation (e.g. discount exceeds the tenant cap)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cart not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/orders/{id}":{"get":{"summary":"Get order details","operationId":"getOrder","tags":["Orders"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Order details"}}},"patch":{"summary":"Update order status or details","operationId":"updateOrder","tags":["Orders"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"items":{"type":"array"},"totalCents":{"type":"integer"}}}}}},"responses":{"200":{"description":"Order updated"}}}},"/carts":{"post":{"summary":"Create a cart for a session. If an existing cart is already keyed to the session, it is returned instead of creating a duplicate.","operationId":"createCart","tags":["Carts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sessionId"],"properties":{"sessionId":{"type":"string","description":"Caller-chosen session identifier to key the cart to"}}}}}},"responses":{"200":{"description":"Existing cart for this session returned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"201":{"description":"Cart created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/carts/{id}":{"get":{"summary":"Get a cart with its items and totals","operationId":"getCart","tags":["Carts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Cart ID"}],"responses":{"200":{"description":"Cart details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"404":{"description":"Cart not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/carts/{id}/items":{"post":{"summary":"Add an item to a cart. Cart totals are recalculated and any auto-apply offer that improves the discount is applied.","operationId":"addCartItem","tags":["Carts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Cart ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","unitPriceCents"],"properties":{"productId":{"type":"string","format":"uuid","description":"Catalog product this line maps to"},"offerId":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1},"description":{"type":"string"},"quantity":{"type":"integer","minimum":1,"default":1},"unitPriceCents":{"type":"integer","minimum":0}}}}}},"responses":{"201":{"description":"Item added — returns the updated cart with recalculated totals","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cart not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/carts/{id}/items/{itemId}":{"delete":{"summary":"Remove an item from a cart. Cart totals and offer discounts are recalculated.","operationId":"removeCartItem","tags":["Carts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Cart ID"},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"},"description":"Cart item ID"}],"responses":{"200":{"description":"Item removed — returns the updated cart with recalculated totals","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"404":{"description":"Cart not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/carts/{id}/promo":{"post":{"summary":"Apply an offer (promo) code to a cart. The offer is validated against the cart contents before it is applied.","operationId":"applyCartPromo","tags":["Carts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Cart ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Offer code to apply"}}}}}},"responses":{"200":{"description":"Offer applied — returns the updated cart plus an `offer` summary with the discount amount","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"400":{"description":"Offer is not valid for this cart (offer_invalid) or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cart not found, or no offer matches the code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Remove the applied offer (promo) from a cart and recalculate totals","operationId":"removeCartPromo","tags":["Carts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Cart ID"}],"responses":{"200":{"description":"Offer removed — returns the updated cart","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cart"}}}},"404":{"description":"Cart not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invoices/{id}/pay":{"post":{"summary":"Initiate payment for a specific invoice","operationId":"payInvoice","tags":["Invoices"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["accountId","amountCents","currency"],"properties":{"accountId":{"type":"string"},"amountCents":{"type":"integer"},"currency":{"type":"string"}}}}}},"responses":{"201":{"description":"Payment session created for invoice"}}}},"/payments":{"get":{"summary":"List all payment sessions for the tenant","operationId":"listPaymentSessions","tags":["Payments"],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Sort"},{"$ref":"#/components/parameters/SortDir"},{"$ref":"#/components/parameters/StatusFilter"}],"responses":{"200":{"description":"Paginated list of payment sessions","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a payment session","operationId":"createPayment","tags":["Payments"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"Idempotency key to safely retry without creating duplicates","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["accountId","amountCents","currency"],"properties":{"accountId":{"type":"string"},"amountCents":{"type":"integer"},"currency":{"type":"string"},"invoiceId":{"type":"string"},"paymentPageSlug":{"type":"string"}}}}}},"responses":{"201":{"description":"Payment session created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/payments/{id}":{"get":{"summary":"Get payment session details","operationId":"getPaymentSession","tags":["Payments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Payment session details"}}}},"/payments/{id}/complete":{"post":{"summary":"Mark a payment session as completed","operationId":"completePaymentSession","tags":["Payments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Payment session completed"}}}},"/sessions":{"get":{"summary":"List journey sessions for the tenant","operationId":"listJourneySessions","tags":["Sessions"],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/Sort"},{"$ref":"#/components/parameters/SortDir"},{"$ref":"#/components/parameters/StatusFilter"},{"name":"journeyId","in":"query","description":"Filter to a specific journey","schema":{"type":"string"}},{"name":"outcome","in":"query","description":"Filter by outcome (pending, success, failure)","schema":{"type":"string","enum":["pending","success","failure"]}}],"responses":{"200":{"description":"Paginated list of journey sessions","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"400":{"description":"Invalid outcome filter value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sessions/{id}":{"get":{"summary":"Get a single journey session","operationId":"getJourneySession","tags":["Sessions"],"parameters":[{"name":"id","in":"path","required":true,"description":"Journey session UUID","schema":{"type":"string"}}],"responses":{"200":{"description":"Journey session details including outcome and outcomeReason"},"404":{"description":"Session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/seller/sessions":{"post":{"summary":"Open a sell session for a buyer agent","operationId":"createSellSession","tags":["Seller"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"counterparty":{"type":"string","description":"Label for the buyer agent's principal (audit)"},"sessionKey":{"type":"string"},"title":{"type":"string"}}}}}},"responses":{"201":{"description":"Sell session created"},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope (commerce required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List sell sessions for the tenant","operationId":"listSellSessions","tags":["Seller"],"responses":{"200":{"description":"List of sell sessions","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/seller/sessions/{id}/messages":{"post":{"summary":"Send a buyer message and run one seller turn","operationId":"createSellSessionMessage","tags":["Seller"],"parameters":[{"name":"id","in":"path","required":true,"description":"Sell session UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","description":"The buyer agent's message"},"merchantName":{"type":"string"},"catalogSummary":{"type":"string"},"requiredToClose":{"type":"string"},"allowCommit":{"type":"boolean","description":"When false, the seller quotes/builds but does not commit"}}}}}},"responses":{"200":{"description":"Seller reply with executed tool calls and token usage"},"404":{"description":"Sell session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Seller agent unavailable (no model key configured)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"Replay a sell session's turns","operationId":"listSellSessionMessages","tags":["Seller"],"parameters":[{"name":"id","in":"path","required":true,"description":"Sell session UUID","schema":{"type":"string"}}],"responses":{"200":{"description":"Ordered list of buyer and seller turns"},"404":{"description":"Sell session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/payment-methods":{"post":{"summary":"Add a payment method reference for an account","operationId":"addPaymentMethod","tags":["PaymentMethods"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["accountId"],"properties":{"accountId":{"type":"string"},"type":{"type":"string"},"lastFour":{"type":"string"}}}}}},"responses":{"201":{"description":"Payment method added (stub)"}}}},"/payment-methods/{id}/set-default":{"post":{"summary":"Set a payment method as the default for its account","operationId":"setDefaultPaymentMethod","tags":["PaymentMethods"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Default payment method set (stub)"}}}},"/webhooks/event-types":{"get":{"summary":"List all supported webhook event types that can be used in subscription `events` arrays","operationId":"listWebhookEventTypes","tags":["Webhooks"],"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"List of platform-supported event types","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEventType"}},"total":{"type":"integer"}}}}}}}}},"/webhooks":{"get":{"summary":"List webhook subscriptions","operationId":"listWebhooks","tags":["Webhooks"],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"isActive","in":"query","description":"Filter by active status","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Paginated list of webhook subscriptions","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Register a webhook subscription for event callbacks. Use `GET /webhooks/event-types` to discover available event type names. Subscribing to `*` receives all events.","operationId":"createWebhook","tags":["Webhooks"],"parameters":[{"name":"Idempotency-Key","in":"header","description":"Idempotency key to safely retry without creating duplicates","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"Public HTTPS endpoint that will receive POST requests."},"events":{"type":"array","items":{"type":"string"},"description":"Event type names to subscribe to. Use `*` for all events."}}}}}},"responses":{"201":{"description":"Webhook subscription created. The response includes the `secret` (shown once) used to verify payloads — see signature scheme in the description."},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{id}":{"get":{"summary":"Get webhook subscription details","operationId":"getWebhook","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Webhook subscription details"}}},"patch":{"summary":"Update a webhook subscription","operationId":"updateWebhook","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Webhook updated"}}},"delete":{"summary":"Delete a webhook subscription","operationId":"deleteWebhook","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Webhook deleted"}}}},"/webhooks/{id}/rotate-secret":{"post":{"summary":"Rotate the signing secret. The new secret signs deliveries immediately; the previous one keeps verifying for 24 hours so the endpoint can be redeployed without dropping events.","operationId":"rotateWebhookSecret","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Secret rotated. The response carries the new `secret` (shown once), `previousSecretExpiresAt`, and `overlapHours`.","headers":{"X-Request-ID":{"$ref":"#/components/headers/X-Request-ID"}}},"404":{"description":"Webhook subscription not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{id}/deliveries":{"get":{"summary":"List delivery history for a webhook subscription with optional status and time-range filtering","operationId":"listWebhookDeliveries","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook subscription ID"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"status","in":"query","description":"Filter by delivery status (pending | retrying | delivered | dead_letter)","schema":{"type":"string","enum":["pending","retrying","delivered","dead_letter"]}},{"name":"since","in":"query","description":"Return deliveries created at or after this ISO 8601 timestamp","schema":{"type":"string","format":"date-time"}},{"name":"until","in":"query","description":"Return deliveries created at or before this ISO 8601 timestamp","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Paginated delivery history","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"404":{"description":"Webhook subscription not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{id}/deliveries/{deliveryId}":{"get":{"summary":"Get a single delivery record including request payload, response body, and attempt history","operationId":"getWebhookDelivery","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook subscription ID"},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string"},"description":"Delivery ID"}],"responses":{"200":{"description":"Delivery record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{id}/deliveries/{deliveryId}/replay":{"post":{"summary":"Replay a delivery — re-enqueues the original payload for re-delivery. Works for any status including dead_letter.","operationId":"replayWebhookDelivery","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Webhook subscription ID"},{"name":"deliveryId","in":"path","required":true,"schema":{"type":"string"},"description":"Delivery ID"}],"responses":{"202":{"description":"Replay enqueued. The delivery will be re-attempted asynchronously and its status updated."},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/preview/plan-change":{"post":{"summary":"Preview a plan change: returns effective date, proration, applicable business rule, and resolved price without executing the change","operationId":"previewPlanChange","tags":["Preview"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subscriptionId","targetProductId"],"properties":{"subscriptionId":{"type":"string"},"targetProductId":{"type":"string"}}}}}},"responses":{"200":{"description":"Plan change preview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewResponse"}}}},"404":{"description":"Subscription or product not found"}}}},"/preview/add-on":{"post":{"summary":"Preview adding an add-on to a subscription: returns effective date, co-term behavior, prorated first charge, and applicable business rule","operationId":"previewAddOn","tags":["Preview"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subscriptionId","addOnProductId"],"properties":{"subscriptionId":{"type":"string"},"addOnProductId":{"type":"string"}}}}}},"responses":{"200":{"description":"Add-on preview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewResponse"}}}},"404":{"description":"Subscription or product not found"}}}},"/preview/cancellation":{"post":{"summary":"Preview cancelling a subscription: returns effective date, refund/credit info, retention requirements, and applicable business rule","operationId":"previewCancellation","tags":["Preview"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subscriptionId"],"properties":{"subscriptionId":{"type":"string"}}}}}},"responses":{"200":{"description":"Cancellation preview","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreviewResponse"}}}},"404":{"description":"Subscription not found"}}}},"/preview/order-total":{"post":{"summary":"Preview an order total: returns subtotal, tax, and shipping breakdown without creating an order. Calls the pluggable tax and shipping hooks configured for this tenant.","operationId":"previewOrderTotal","tags":["Preview"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","required":["quantity","unitPriceCents"],"properties":{"productId":{"type":"string"},"name":{"type":"string"},"quantity":{"type":"integer","default":1},"unitPriceCents":{"type":"integer","minimum":0}}}},"currency":{"type":"string","default":"USD"},"shippingOptionId":{"type":"string"},"shippingAddress":{"$ref":"#/components/schemas/Address"},"taxAddress":{"$ref":"#/components/schemas/Address"},"customerContext":{"type":"object"}}}}}},"responses":{"200":{"description":"Order total preview with subtotal, tax, and shipping breakdown","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderTotalPreview"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/shipping-options":{"get":{"summary":"List available shipping options for a destination address. Returns rates from the pluggable shipping provider configured for this tenant.","operationId":"listShippingOptions","tags":["Shipping"],"parameters":[{"name":"currency","in":"query","schema":{"type":"string","default":"USD"}},{"name":"country","in":"query","schema":{"type":"string"}},{"name":"state","in":"query","schema":{"type":"string"}},{"name":"postalCode","in":"query","schema":{"type":"string"}},{"name":"city","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Available shipping options","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ShippingOption"}}}}}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/payments/{id}/refunds":{"post":{"summary":"Issue a refund against a successful payment session. Supports full refunds (default) and partial refunds via amountCents.","operationId":"issuePaymentRefund","tags":["Refunds"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Payment session ID"},{"name":"Idempotency-Key","in":"header","description":"Per-refund idempotency key. A retry with the same key + same body returns the original refund; same key + different body returns 409.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amountCents":{"type":"integer","minimum":1,"description":"Partial refund amount in cents. Omit to refund the full refundable balance."},"reason":{"type":"string","maxLength":500},"refundAll":{"type":"boolean","default":false,"description":"Explicitly refund the full remaining balance."}}}}}},"responses":{"200":{"description":"Replayed prior refund for the same Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"}}}},"201":{"description":"Refund issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"}}}},"400":{"description":"Validation error or payment not refundable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Payment session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency-Key reused with a different request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List all refunds issued against a payment session","operationId":"listPaymentRefunds","tags":["Refunds"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Payment session ID"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of refunds"},"404":{"description":"Payment session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/invoices/{id}/refunds":{"post":{"summary":"Issue a refund against the payment for an invoice. Targets the most recent successful payment session linked to this invoice.","operationId":"issueInvoiceRefund","tags":["Refunds"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Invoice ID (externalInvoiceId on the payment session)"},{"name":"Idempotency-Key","in":"header","description":"Per-refund idempotency key. A retry with the same key + same body returns the original refund; same key + different body returns 409.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amountCents":{"type":"integer","minimum":1},"reason":{"type":"string","maxLength":500},"refundAll":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Replayed prior refund for the same Idempotency-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"}}}},"201":{"description":"Refund issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refund"}}}},"400":{"description":"Validation error or payment not refundable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No successful payment session found for this invoice","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency-Key reused with a different request body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List all refunds issued against payments for an invoice","operationId":"listInvoiceRefunds","tags":["Refunds"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Invoice ID"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of refunds"},"404":{"description":"No payment session in the caller's tenant references this invoice (anti-enumeration — see Task #1459)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/accounts/{id}/credits":{"post":{"summary":"Issue a credit to an account. Credits reduce the amount due on future payments when applied.","operationId":"issueCredit","tags":["Credits"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Account (organization) ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amountCents"],"properties":{"amountCents":{"type":"integer","minimum":1},"currency":{"type":"string","default":"USD","description":"3-letter ISO 4217 currency code"},"reason":{"type":"string","maxLength":500},"expiresAt":{"type":"string","format":"date-time","description":"ISO 8601 expiry timestamp. Omit for a non-expiring credit."}}}}}},"responses":{"201":{"description":"Credit issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credit"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List credits for an account","operationId":"listCredits","tags":["Credits"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Account ID"},{"name":"status","in":"query","schema":{"type":"string","enum":["active","applied","expired","cancelled"]}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of credits"},"404":{"description":"Account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/accounts/{id}/credit-balance":{"get":{"summary":"Get the aggregate credit balance for an account — sum of all active, non-expired credits","operationId":"getCreditBalance","tags":["Credits"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Account ID"}],"responses":{"200":{"description":"Credit balance summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditBalance"}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/accounts/{id}/credits/{creditId}/apply":{"post":{"summary":"Apply a credit (or a portion of it) to a payment session","operationId":"applyCredit","tags":["Credits"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Account ID"},{"name":"creditId","in":"path","required":true,"schema":{"type":"string"},"description":"Credit ID"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"paymentId":{"type":"string","description":"Payment session ID to apply the credit against. Omit to record an unallocated application."},"amountCents":{"type":"integer","minimum":1,"description":"Partial application amount. Omit to apply the full remaining balance."}}}}}},"responses":{"200":{"description":"Credit applied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credit"}}}},"400":{"description":"Credit not active, expired, or exhausted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Account, credit, or payment session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pricing/resolve":{"get":{"summary":"Resolve the effective price for a product given customer context (segment, locale, partner, quantity). Returns the resolved price with a breakdown of pricing rules applied.","operationId":"resolvePricing","tags":["Pricing"],"parameters":[{"name":"productId","in":"query","required":true,"schema":{"type":"string"}},{"name":"segment","in":"query","schema":{"type":"string"}},{"name":"locale","in":"query","schema":{"type":"string"}},{"name":"partner","in":"query","schema":{"type":"string"}},{"name":"quantity","in":"query","schema":{"type":"integer","default":1}}],"responses":{"200":{"description":"Resolved pricing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PricingResolution"}}}},"404":{"description":"Product not found"}}}},"/tags":{"get":{"summary":"List taxonomy tags for the tenant","operationId":"listTags","tags":["Tags"],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of tags","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Tag"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"requestId":{"type":"string"}}}}}},"401":{"description":"API key missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a tag. When `slug` is omitted it is derived from `name`.","operationId":"createTag","tags":["Tags"],"security":[{"ApiKeyAuth":["admin"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"slug":{"type":"string","maxLength":120,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","description":"Lowercase alphanumeric with hyphens. Derived from name when omitted."},"description":{"type":"string","maxLength":2000,"nullable":true},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","nullable":true,"description":"Hex color in #RRGGBB form"},"isActive":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Tag created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A tag with that slug already exists (slug_conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/tags/{id}":{"get":{"summary":"Get tag details","operationId":"getTag","tags":["Tags"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Tag ID"}],"responses":{"200":{"description":"Tag details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}},"404":{"description":"Tag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a tag (partial update). Changing the slug is rejected if it collides with another tag.","operationId":"updateTag","tags":["Tags"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Tag ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100},"slug":{"type":"string","maxLength":120,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"description":{"type":"string","maxLength":2000,"nullable":true},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$","nullable":true},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Tag updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tag"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Tag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A tag with that slug already exists (slug_conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a tag. Tags still assigned to items are protected — pass ?force=1 to delete anyway and drop the assignments.","operationId":"deleteTag","tags":["Tags"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Tag ID"},{"name":"force","in":"query","description":"Pass `1` to delete a tag that is still in use","schema":{"type":"string","enum":["1"]}}],"responses":{"204":{"description":"Tag deleted"},"404":{"description":"Tag not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Tag is still assigned to items (tag_in_use) — retry with ?force=1","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users":{"get":{"summary":"List all users for the tenant","operationId":"listUsers","tags":["Admin - Users"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"portalType","in":"query","description":"Filter by portal type (admin, customer, csr, partner)","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of users","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"401":{"$ref":"#/components/schemas/Error"},"403":{"$ref":"#/components/schemas/Error"}}},"post":{"summary":"Create a new user","operationId":"createUser","tags":["Admin - Users"],"security":[{"ApiKeyAuth":["admin"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","profileId"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":6,"description":"Required for password (database) sign-in tenants. Auth0-managed tenants must OMIT this — the user sets their own password via the email Auth0 sends; supplying one returns 400."},"profileId":{"type":"string","format":"uuid"},"portalType":{"type":"string"},"externalAccountId":{"type":"string"},"externalContactId":{"type":"string"},"isActive":{"type":"boolean"}}}}}},"responses":{"201":{"description":"User created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users/{id}":{"get":{"summary":"Get user details","operationId":"getUser","tags":["Admin - Users"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a user","operationId":"updateUser","tags":["Admin - Users"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"password":{"type":"string"},"profileId":{"type":"string"},"portalType":{"type":"string"},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"User updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUser"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a user","operationId":"deleteUser","tags":["Admin - Users"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"User deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/roles":{"get":{"summary":"List all roles (profiles) for the tenant","operationId":"listRoles","tags":["Admin - Roles"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"role","in":"query","description":"Filter by role type (admin, customer, csr, partner)","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of roles","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}}}},"post":{"summary":"Create a new role","operationId":"createRole","tags":["Admin - Roles"],"security":[{"ApiKeyAuth":["admin"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","role"],"properties":{"name":{"type":"string"},"role":{"type":"string"},"description":{"type":"string"},"isActive":{"type":"boolean"},"permissions":{"type":"object"}}}}}},"responses":{"201":{"description":"Role created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/roles/{id}":{"get":{"summary":"Get role details","operationId":"getRole","tags":["Admin - Roles"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Role details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a role","operationId":"updateRole","tags":["Admin - Roles"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"isActive":{"type":"boolean"},"permissions":{"type":"object"}}}}}},"responses":{"200":{"description":"Role updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a role (system roles cannot be deleted)","operationId":"deleteRole","tags":["Admin - Roles"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Role deleted"},"403":{"description":"System role — cannot delete","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/roles/{id}/permissions":{"get":{"summary":"Get the permission set for a role","operationId":"getRolePermissions","tags":["Admin - Roles"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Permission object for this role"},"404":{"description":"Role not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update the permission set for a role","operationId":"updateRolePermissions","tags":["Admin - Roles"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"resources":{"type":"object"},"navigationGroups":{"type":"object"},"catalogGrants":{"type":"object"}}}}}},"responses":{"200":{"description":"Permissions updated"},"403":{"description":"Forbidden (system Administrator role)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Role not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/context/personas":{"get":{"summary":"List persona configurations","operationId":"listContextPersonas","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["context:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated persona configurations"}}}},"/context/personas/{id}":{"get":{"summary":"Get a persona configuration","operationId":"getContextPersona","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["context:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Persona configuration"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/context/integrations":{"get":{"summary":"List integrations (identity and health only)","description":"Returns ONLY id, type, name, isActive and catalog-sync health. Credentials are never returned — not masked, not their key names — and `config` is not returned at all. This is an allowlist, so a column added to the integrations table in future stays invisible here until it is deliberately added.","operationId":"listContextIntegrations","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["context:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated integrations"}}}},"/context/integrations/{id}":{"get":{"summary":"Get an integration (identity and health only)","operationId":"getContextIntegration","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["context:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Integration"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/context/host-routes":{"get":{"summary":"List host routes — which hostname reaches which journey, step or page","description":"Entry-point routing. Filterable by `journeyId` or `pageId` to answer \"where do customers actually reach this?\". The internal Cloudflare custom-hostname id is deliberately omitted.","operationId":"listContextHostRoutes","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["context:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"journeyId","in":"query","description":"Only routes targeting this journey","schema":{"type":"string"}},{"name":"pageId","in":"query","description":"Only routes targeting this page","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated host routes"}}}},"/business-rules":{"get":{"summary":"List business rules","operationId":"listBusinessRules","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"direction","in":"query","description":"Filter by direction (upgrade, downgrade, cancellation, add_on, remove_add_on, retention_discount)","schema":{"type":"string"}},{"name":"isActive","in":"query","description":"Filter by enforcement state","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Paginated business rules"}}},"post":{"summary":"Create a business rule","description":"`isActive` is REQUIRED — a business rule has no draft state, so an active rule is enforced from the moment it is written and there is no safe default to choose on the caller's behalf. `isPlatformDefault` cannot be set: it is cleared on every boot, so writing it would look applied and silently revert.","operationId":"createBusinessRule","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","direction","changeTiming","isActive"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"direction":{"type":"string","enum":["upgrade","downgrade","cancellation","add_on","remove_add_on","retention_discount"]},"changeTiming":{"type":"string","enum":["immediate","end_of_term","custom_delay","grace_period","next_chargeable_period","paying_plan_activation"]},"timingValue":{"type":"integer"},"timingUnit":{"type":"string","enum":["days","weeks","months"]},"prorationType":{"type":"string","enum":["full","prorated","none"]},"subscriptionState":{"type":"string","enum":["any","active","trial"]},"appliesToSurface":{"type":"string","enum":["any","customer","csr"]},"blocksChange":{"type":"boolean"},"isActive":{"type":"boolean","description":"Required. True = enforced immediately."}}}}}},"responses":{"201":{"description":"Rule created; the response `enforcement` field states whether it is being enforced"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/business-rules/{id}":{"get":{"summary":"Get a business rule","operationId":"getBusinessRule","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Business rule"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a business rule","description":"The timing rule is checked against the MERGED result: patching `changeTiming` to `custom_delay` or `grace_period` without supplying a value and unit is refused, because the rule would otherwise have no defined timing when it is evaluated.","operationId":"updateBusinessRule","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"changeTiming":{"type":"string"},"timingValue":{"type":"integer"},"timingUnit":{"type":"string"},"blocksChange":{"type":"boolean"},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Rule updated"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a business rule","description":"Deleting a rule REMOVES a policy. When the rule was an active hard block, that removes a guardrail — the audit entry records the rule's direction, enforcement state, blocking flag and attached journeys, since after deletion that is the only remaining evidence.","operationId":"deleteBusinessRule","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:delete"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/business-rules/{id}/journeys":{"get":{"summary":"List the journeys a business rule is attached to","description":"The blast radius of a rule — read this before changing or deleting one.","operationId":"listBusinessRuleJourneys","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Journeys using this rule"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{journeyId}/business-rules":{"get":{"summary":"List the business rules attached to a journey","operationId":"listJourneyBusinessRules","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:read"]}],"parameters":[{"name":"journeyId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Attached rules"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Attach a business rule to a journey","description":"Scoped as a `rules:*` operation — the rule is what is being applied. Both the journey and the rule must belong to the caller's tenant. A duplicate attachment returns 409 `already_linked`.","operationId":"attachJourneyBusinessRule","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:write"]}],"parameters":[{"name":"journeyId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["businessRuleId"],"properties":{"businessRuleId":{"type":"string"}}}}}},"responses":{"201":{"description":"Attached"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Already attached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{journeyId}/business-rules/{linkId}":{"delete":{"summary":"Detach a business rule from a journey","operationId":"detachJourneyBusinessRule","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["rules:write"]}],"parameters":[{"name":"journeyId","in":"path","required":true,"schema":{"type":"string"}},{"name":"linkId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Detached"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets":{"get":{"summary":"List product sets","operationId":"listProductSets","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"status","in":"query","description":"Filter by status (draft, active, archived)","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated product sets"}}},"post":{"summary":"Create a product set","description":"Always created as a draft. Activate it with PATCH once its items are in place — activation runs the timing anchor rule and needs `productsets:publish`.","operationId":"createProductSet","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"carryQuantityOnBaseChange":{"type":"boolean"}}}}}},"responses":{"201":{"description":"Product set created (status draft)"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets/{id}":{"get":{"summary":"Get a product set","operationId":"getProductSet","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Product set"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a product set","description":"An ACTIVE set resolves for customers and has no version snapshot, so any edit to it — and every status transition in either direction — additionally requires `productsets:publish`. Activating runs the §1.5 anchor rule and returns `product_set_timing_invalid` if no line uses same-as-order timing.","operationId":"updateProductSet","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","enum":["draft","active","archived"],"description":"Requires `productsets:publish`."}}}}}},"responses":{"200":{"description":"Updated"},"400":{"description":"Timing/validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a product set","description":"Deleting an ACTIVE set withdraws it from customers and additionally requires `productsets:publish`.","operationId":"deleteProductSet","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:delete"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets/{id}/items":{"get":{"summary":"List a product set's items","description":"Each item carries a derived `orderGroup` (`default` or `own`), read from `metadata.orderGroup` with any unrecognised value reported as `default`, alongside its `effectiveDateTiming`.","operationId":"listProductSetItems","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated items"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Add an item to a product set","description":"`productId` must reference a product in the same tenant. On an ACTIVE set this requires `productsets:publish`.","operationId":"createProductSetItem","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["productId"],"properties":{"productId":{"type":"string"},"planId":{"type":"string"},"classification":{"type":"string","enum":["base","addon","optional"]},"groupName":{"type":"string"},"sortOrder":{"type":"integer"},"orderGroup":{"type":"string","enum":["default","own"],"description":"How the line is booked when a journey places the order. `own` books it as its own order action (an AddProduct) in the same order and subscription; `default` books it with the other lines. Honored only by journeys whose create_order action has `honorOrderGrouping` on. Stored in the item's `metadata.orderGroup` and merged there server-side, so setting it never replaces other metadata keys. Defaults to `default`."},"effectiveDateTiming":{"description":"When the line starts, counted from the order date. `same_as_order` (the default) starts it on the order date; `set_period` starts it `value` (1–120) `unit` later. Applied at checkout only when the create_order action has `honorActivationOffsets` on. On an ACTIVE set at least one line must stay `same_as_order` (the anchor rule); a write that breaks it returns 400 `product_set_timing_invalid`.","oneOf":[{"type":"object","required":["mode"],"additionalProperties":false,"properties":{"mode":{"type":"string","enum":["same_as_order"]}}},{"type":"object","required":["mode","value","unit"],"additionalProperties":false,"properties":{"mode":{"type":"string","enum":["set_period"]},"value":{"type":"integer","minimum":1,"maximum":120},"unit":{"type":"string","enum":["days","months","years"]}}}]}}}}}},"responses":{"201":{"description":"Item added. Items are returned with a derived `orderGroup`, and `configWarnings` when the set's lines resolve to more than two activation dates."},"400":{"description":"Validation error, or `product_set_timing_invalid` when the write would leave an active set with no same-as-order line","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Set is active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets/{id}/items/{itemId}":{"patch":{"summary":"Update a product set item","operationId":"updateProductSetItem","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"classification":{"type":"string"},"groupName":{"type":"string"},"sortOrder":{"type":"integer"},"orderGroup":{"type":"string","enum":["default","own"],"description":"How the line is booked when a journey places the order. `own` books it as its own order action (an AddProduct) in the same order and subscription; `default` books it with the other lines. Honored only by journeys whose create_order action has `honorOrderGrouping` on. Stored in the item's `metadata.orderGroup` and merged there server-side, so setting it never replaces other metadata keys. Defaults to `default`."},"effectiveDateTiming":{"description":"When the line starts, counted from the order date. `same_as_order` (the default) starts it on the order date; `set_period` starts it `value` (1–120) `unit` later. Applied at checkout only when the create_order action has `honorActivationOffsets` on. On an ACTIVE set at least one line must stay `same_as_order` (the anchor rule); a write that breaks it returns 400 `product_set_timing_invalid`.","oneOf":[{"type":"object","required":["mode"],"additionalProperties":false,"properties":{"mode":{"type":"string","enum":["same_as_order"]}}},{"type":"object","required":["mode","value","unit"],"additionalProperties":false,"properties":{"mode":{"type":"string","enum":["set_period"]},"value":{"type":"integer","minimum":1,"maximum":120},"unit":{"type":"string","enum":["days","months","years"]}}}]}}}}}},"responses":{"200":{"description":"Item updated. Returned with a derived `orderGroup`, and `configWarnings` when relevant."},"400":{"description":"Validation error, or `product_set_timing_invalid` when the change would leave an active set with no same-as-order line","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Set is active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Remove an item from a product set","operationId":"deleteProductSetItem","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"itemId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Item removed"},"403":{"description":"Set is active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets/{id}/items/reorder":{"post":{"summary":"Reorder a product set's items","description":"Sort order drives card order in every consumer, so `itemIds` must list every item exactly once.","operationId":"reorderProductSetItems","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["itemIds"],"properties":{"itemIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Reordered"},"400":{"description":"Invalid list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets/{id}/dependencies":{"get":{"summary":"List a product set's dependencies","operationId":"listProductSetDependencies","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Dependencies"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a dependency between two items in a set","description":"Both ends must be items of THIS set, and an item cannot depend on itself.","operationId":"createProductSetDependency","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sourceItemId","targetItemId","dependencyType"],"properties":{"sourceItemId":{"type":"string"},"targetItemId":{"type":"string"},"dependencyType":{"type":"string","enum":["requires","excluded_by","co_term_with"]}}}}}},"responses":{"201":{"description":"Dependency created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets/{id}/dependencies/{depId}":{"delete":{"summary":"Delete a dependency","operationId":"deleteProductSetDependency","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"depId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets/{id}/bundles":{"get":{"summary":"List a product set's bundles","operationId":"listProductSetBundles","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Bundles with their members"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a bundle in a product set","description":"A bundle is one FACE entry (the card the customer sees) plus one or more MEMBER entries added silently when the face is selected. The face may not be a member of its own bundle, and a bundle must have at least one member.","operationId":"createProductSetBundle","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","faceItemId","memberItemIds"],"properties":{"name":{"type":"string"},"faceItemId":{"type":"string"},"memberItemIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Bundle created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/product-sets/{id}/bundles/{bundleId}":{"patch":{"summary":"Update a bundle","operationId":"updateProductSetBundle","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"bundleId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"faceItemId":{"type":"string"},"memberItemIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Bundle updated"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a bundle","operationId":"deleteProductSetBundle","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["productsets:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"bundleId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys":{"get":{"summary":"List journeys for the tenant","operationId":"listJourneys","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"status","in":"query","description":"Filter by status (draft, active, archived)","schema":{"type":"string"}},{"name":"type","in":"query","description":"Filter by journey type","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of journeys"}}},"post":{"summary":"Create a journey","description":"A journey is always created as a draft. Publish it with POST /journeys/{id}/publish.","operationId":"createJourney","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string"},"description":{"type":"string"},"purpose":{"type":"string"},"audience":{"type":"string"}}}}}},"responses":{"201":{"description":"Journey created (status draft)"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}":{"get":{"summary":"Get journey details","operationId":"getJourney","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Journey details"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a journey","description":"Changing `status` changes what is live and additionally requires `journeys:publish` — in either direction, since taking a live journey down matters as much as putting one up. A transition to `active` runs the publish gates and records a version.","operationId":"updateJourney","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","enum":["draft","active","archived"],"description":"Requires `journeys:publish`."}}}}}},"responses":{"200":{"description":"Journey updated"},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a journey","description":"Deleting an ACTIVE journey takes it off the air and additionally requires `journeys:publish`.","operationId":"deleteJourney","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:delete"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Journey deleted"},"403":{"description":"Insufficient scope","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/publish":{"post":{"summary":"Publish a journey","description":"Runs the publish-time validation gates and records a journey version. A rejection returns the gate's own code — BROKEN_BRANCHING_TARGETS or UNSATISFIABLE_REQUIRED_ACTIONS — with its detail, so the problem can be fixed. Republishing an unchanged journey returns `unchanged: true` and mints no new version.","operationId":"publishJourney","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:publish"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Journey published (or unchanged)"},"400":{"description":"Graph validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A publish gate rejected the journey","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/versions":{"get":{"summary":"List a journey's versions","operationId":"listJourneyVersions","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of journey versions"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/versions/{versionId}":{"get":{"summary":"Get one journey version, including its snapshot","operationId":"getJourneyVersion","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"versionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Journey version"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/steps":{"get":{"summary":"List a journey's steps","operationId":"listJourneySteps","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of steps"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Add a step to a journey","description":"Draft-side: needs only `journeys:write`, and is invisible to live sessions until the journey is published. `stepOrder` is optional and appends when omitted.","operationId":"createJourneyStep","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["pageId"],"properties":{"pageId":{"type":"string","description":"A page in the same tenant."},"label":{"type":"string"},"stepOrder":{"type":"integer"},"branchingConfig":{"type":"object"}}}}}},"responses":{"201":{"description":"Step created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/steps/{stepId}":{"get":{"summary":"Get one journey step","operationId":"getJourneyStep","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Step"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a journey step","operationId":"updateJourneyStep","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string"},"pageId":{"type":"string"},"branchingConfig":{"type":"object"},"disabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Step updated"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a journey step","description":"Draft-side, so `journeys:write` — `journeys:delete` is for deleting the journey itself.","operationId":"deleteJourneyStep","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Step deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/steps/reorder":{"post":{"summary":"Reorder a journey's steps","description":"Must list every step exactly once. Rejected if the new order introduces a cycle or strands a step — non-retroactively, so a journey that already had a graph problem can still be rearranged toward a fix.","operationId":"reorderJourneySteps","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["stepIds"],"properties":{"stepIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Steps reordered"},"400":{"description":"Invalid list or graph would break","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/steps/{stepId}/actions":{"get":{"summary":"List a step's commerce actions","operationId":"listStepCommerceActions","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Commerce actions in execution order"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Add a commerce action to a step","operationId":"createStepCommerceAction","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["operationType"],"properties":{"operationType":{"type":"string"},"executionMode":{"type":"string","enum":["blocking","async"]},"integrationId":{"type":"string"},"fieldMapping":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"201":{"description":"Action created"},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/steps/{stepId}/actions/{actionId}":{"patch":{"summary":"Update a commerce action","operationId":"updateStepCommerceAction","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string"}},{"name":"actionId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"executionMode":{"type":"string"},"integrationId":{"type":"string"},"fieldMapping":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"200":{"description":"Action updated"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a commerce action","operationId":"deleteStepCommerceAction","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string"}},{"name":"actionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Action deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/journeys/{id}/steps/{stepId}/actions/reorder":{"post":{"summary":"Reorder a step's commerce actions","description":"Action order IS execution order, so the list must name every action on the step exactly once.","operationId":"reorderStepCommerceActions","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["journeys:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"stepId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["actionIds"],"properties":{"actionIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Actions reordered"},"400":{"description":"Invalid list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages":{"get":{"summary":"List all pages (portal, payment, storefront, checkout) for the tenant","operationId":"listAdminPages","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"type","in":"query","description":"Filter by page type","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of pages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPage"}}}}}},"post":{"summary":"Create a new page","operationId":"createAdminPage","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:write"]}],"description":"Setting `pageContent` publishes the page live and additionally requires the `pages:publish` scope; write `draftPageContent` and use POST /pages/{id}/publish instead.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string"},"slug":{"type":"string"},"draftPageContent":{"type":"object","description":"Working draft. Safe to write with `pages:write`."},"pageContent":{"type":"object","description":"LIVE content. Requires `pages:publish`."}}}}}},"responses":{"201":{"description":"Page created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPage"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages/{id}":{"get":{"summary":"Get page details","operationId":"getAdminPage","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Page details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPage"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a page","operationId":"updateAdminPage","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"description":"Setting `pageContent` publishes the page live and additionally requires the `pages:publish` scope; write `draftPageContent` and use POST /pages/{id}/publish instead.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"draftPageContent":{"type":"object","description":"Working draft. Safe to write with `pages:write`."},"pageContent":{"type":"object","description":"LIVE content. Requires `pages:publish`."}}}}}},"responses":{"200":{"description":"Page updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPage"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a page","operationId":"deleteAdminPage","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:delete"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Page deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages/{id}/publish":{"post":{"summary":"Publish a page — promote its draft content to live","description":"Copies `draftPageContent` onto `pageContent` and records a published version. The explicit form of what setting `pageContent` used to do implicitly.","operationId":"publishAdminPage","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:publish"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Page published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPage"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"No draft content to publish","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages/{id}/versions":{"get":{"summary":"List a page's saved versions","operationId":"listAdminPageVersions","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of versions"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages/{id}/versions/{versionId}":{"get":{"summary":"Get one saved version of a page","operationId":"getAdminPageVersion","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"versionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Version details"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages/{id}/versions/{versionId}/restore":{"post":{"summary":"Restore a saved version into the page's draft","description":"Restores into `draftPageContent`, never directly to live — so rolling back what is published stays a deliberate restore-then-publish. Requires `pages:write` only.","operationId":"restoreAdminPageVersion","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"versionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Draft restored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminPage"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/pages/{id}/manifest":{"get":{"summary":"Get a page's commerce manifest","description":"What this page sells — product set, persona config and resolved commerce manifest. Read-only context for an agent editing the page.","operationId":"getAdminPageManifest","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["pages:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Page commerce manifest"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/custom-components":{"get":{"summary":"List all custom components for the tenant","operationId":"listCustomComponents","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["components:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"context","in":"query","description":"Filter by editor context (payment, customer, portal, storefront, checkout, csr)","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of custom components","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomComponent"}}}}}},"post":{"summary":"Create a custom component","operationId":"createCustomComponent","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["components:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","componentType"],"properties":{"name":{"type":"string"},"displayName":{"type":"string"},"componentType":{"type":"string","enum":["code","template"]},"code":{"type":"string"},"category":{"type":"string"},"editorContexts":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Component created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomComponent"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/custom-components/{id}":{"get":{"summary":"Get custom component details","operationId":"getCustomComponent","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["components:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Component details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomComponent"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a custom component","operationId":"updateCustomComponent","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["components:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"displayName":{"type":"string"},"code":{"type":"string"},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Component updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomComponent"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a custom component","operationId":"deleteCustomComponent","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["components:delete"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Component deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/templates":{"get":{"summary":"List page templates","operationId":"listTemplates","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["context:read"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"pageType","in":"query","description":"Filter by page type","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of page templates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageTemplate"}}}}}},"post":{"summary":"Create a page template","operationId":"createTemplate","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["admin"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"pageType":{"type":"string"},"pageContent":{"type":"object"}}}}}},"responses":{"201":{"description":"Template created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageTemplate"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/templates/{id}":{"get":{"summary":"Get page template details","operationId":"getTemplate","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["context:read"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageTemplate"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a page template (system templates cannot be modified)","operationId":"updateTemplate","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"pageContent":{"type":"object"},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Template updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PageTemplate"}}}},"403":{"description":"System template — cannot modify","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a page template (system templates cannot be deleted)","operationId":"deleteTemplate","tags":["Admin - Content"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Template deleted"},"403":{"description":"System template — cannot delete","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/permissions":{"get":{"summary":"List the platform RBAC permission resources catalog — what resources can be granted, which role types they apply to, and the permission levels available","operationId":"listPermissions","tags":["Admin - Roles"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"role","in":"query","description":"Filter by role type (admin, customer, csr, partner) to see resources applicable to that role","schema":{"type":"string"}}],"responses":{"200":{"description":"Permission resource catalog","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"resource":{"type":"string","description":"Canonical permission resource id (e.g. users, pages, subscriptions)"},"roles":{"type":"array","items":{"type":"string"},"description":"Role types for which this resource is relevant"},"levels":{"type":"array","items":{"type":"string","enum":["none","view","create","edit","full"]}},"scopes":{"type":"array","items":{"type":"string","enum":["all","own","assigned"]}}}}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"description":{"type":"string"}}},"requestId":{"type":"string"}}}}}}}}},"/status":{"get":{"summary":"Component-level health status suitable for status-page consumption. No auth required. Cached for 30 seconds.","operationId":"getStatus","tags":["Observability"],"security":[],"responses":{"200":{"description":"Component health status (may be operational, degraded, or incident)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusResponse"}}}}}}},"/partners":{"get":{"summary":"List partner users with their profiles and commission structure","operationId":"listPartners","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of partner users","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Partner"}}}}}}},"/partners/{userId}":{"get":{"summary":"Get a partner user with their profile","operationId":"getPartner","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Partner details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Partner"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/partners/{userId}/profile":{"put":{"summary":"Create or update a partner's profile","operationId":"upsertPartnerProfile","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"companyName":{"type":"string"},"contactName":{"type":"string"},"contactEmail":{"type":"string"},"tier":{"type":"string"},"commissionStructureId":{"type":"string"}}}}}},"responses":{"200":{"description":"Partner profile updated"},"404":{"description":"Partner not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/partners/{userId}/commissions":{"get":{"summary":"List commission records for a partner (performance read)","operationId":"listPartnerCommissions","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"userId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of commission records"},"404":{"description":"Partner not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/partners/commission-structures":{"get":{"summary":"List all commission structures","operationId":"listCommissionStructures","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"}],"responses":{"200":{"description":"Paginated list of commission structures","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommissionStructure"}}}}}},"post":{"summary":"Create a commission structure","operationId":"createCommissionStructure","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","commissionType"],"properties":{"name":{"type":"string"},"commissionType":{"type":"string"},"rate":{"type":"number"},"flatAmount":{"type":"number"},"currency":{"type":"string"}}}}}},"responses":{"201":{"description":"Commission structure created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommissionStructure"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/partners/commission-structures/{id}":{"get":{"summary":"Get a commission structure","operationId":"getCommissionStructure","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Commission structure details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommissionStructure"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a commission structure","operationId":"updateCommissionStructure","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"commissionType":{"type":"string"},"rate":{"type":"number"},"isActive":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Commission structure updated"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a commission structure","operationId":"deleteCommissionStructure","tags":["Admin - Partners"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Commission structure deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/audit-logs":{"get":{"summary":"Query audit logs with optional filtering by actor, resource, time range, and event type","operationId":"listAuditLogs","tags":["Admin - Audit"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"$ref":"#/components/parameters/SortDir"},{"name":"actor","in":"query","description":"Filter by user ID (actor) who performed the action","schema":{"type":"string"}},{"name":"resourceType","in":"query","description":"Filter by resource type (e.g. user, page, apiKey)","schema":{"type":"string"}},{"name":"action","in":"query","description":"Filter by action substring (e.g. user.create, subscription)","schema":{"type":"string"}},{"name":"since","in":"query","description":"Return logs created at or after this ISO 8601 timestamp","schema":{"type":"string","format":"date-time"}},{"name":"until","in":"query","description":"Return logs created at or before this ISO 8601 timestamp","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Paginated list of audit log entries, newest first by default","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditLog"}}}},"401":{"description":"API key required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys":{"get":{"summary":"List API keys for the tenant (secrets are never returned in list responses)","operationId":"listApiKeys","tags":["Admin - API Keys"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Offset"},{"name":"includeRevoked","in":"query","description":"Include revoked keys in the response","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Paginated list of API key records (no secrets)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyRecord"}}}}}},"post":{"summary":"Create a new API key — the plaintext secret is returned only in this response","operationId":"createApiKey","tags":["Admin - API Keys"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"Idempotency-Key","in":"header","description":"Idempotency key to prevent duplicate key creation","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["read","commerce","admin"]},"default":["read"]},"rateLimitTier":{"type":"string","enum":["standard","premium","unlimited"],"default":"standard"},"expiresAt":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"API key created — includes one-time plaintext secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys/{id}/rotate":{"post":{"summary":"Rotate an API key — revokes the old key and creates a new one with the same name and scopes. The new plaintext secret is returned only in this response.","operationId":"rotateApiKey","tags":["Admin - API Keys"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"New API key issued — includes one-time plaintext secret","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreateResponse"}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Key is already revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api-keys/{id}":{"delete":{"summary":"Revoke an API key","operationId":"revokeApiKey","tags":["Admin - API Keys"],"security":[{"ApiKeyAuth":["admin"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"API key revoked"},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/error-codes":{"get":{"summary":"Stable error code catalog. Returns every error code the v1 API can return, its HTTP status, description, and whether the error is retryable.","operationId":"getErrorCodes","tags":["Observability"],"security":[],"responses":{"200":{"description":"Complete error code catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorCodes"}}}}}}},"/usage":{"get":{"summary":"Per-key API usage metrics (admin scope). Returns request counts, error counts, rate-limit hits, and latency broken down by route and 1-minute time bucket.","operationId":"getUsage","tags":["Observability"],"parameters":[{"name":"from","in":"query","description":"Start of the time window (ISO 8601). Defaults to 24 hours ago.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"End of the time window (ISO 8601). Defaults to now.","schema":{"type":"string","format":"date-time"}},{"name":"apiKeyId","in":"query","description":"API key ID to filter. Defaults to the calling key. Pass `*` to see all keys on the tenant.","schema":{"type":"string"}},{"name":"route","in":"query","description":"Filter to a specific route pattern (e.g. `/accounts/:id`).","schema":{"type":"string"}}],"responses":{"200":{"description":"Usage summary with per-route and per-bucket breakdowns","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageSummary"}}}},"401":{"description":"API key required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Insufficient scope (admin required)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}