{
  "openapi": "3.0.3",
  "info": {
    "title": "QueryTek Tapestry Partner Integration API (Subset)",
    "version": "1.1.0",
    "description": "Partner-facing subset of the Tapestry API contract for interactive launch, partner-switch, governed artifact courier (SPEC-042), and inbound partner callbacks.\n\n**Artifact corridors (Phase 6):** two independent one-way `partner_e2e` PII flows — (1) Textmetrics→Trusaic `compliance_pdf`; (2) Trusaic→Textmetrics `structured_report_json`. Both require profile `e2e-rsa-aes-gcm-v1-signed` (P6-034). Receivers register encryption keys on the target partner; sources register signing keys on the uploading partner. `courier_plaintext` is not implemented. Reservation is orchestrated during approved partner-switch; partners use grant-scoped upload/download routes documented under `partner-artifacts`."
  },
  "servers": [
    {
      "url": "https://tapestry.querytek.io",
      "description": "Main edge runtime"
    }
  ],
  "tags": [
    {
      "name": "partner-routing",
      "description": "Launch and partner-switch routes used by partner UI integrations."
    },
    {
      "name": "partner-callbacks",
      "description": "Partner-initiated callbacks to Tapestry."
    },
    {
      "name": "partner-artifacts",
      "description": "Governed inter-partner artifact courier (SPEC-042): E2E ciphertext upload and download on two allowlisted corridors. Tapestry stores ciphertext only; `courier_plaintext` is not a product mode."
    }
  ],
  "paths": {
    "/v1/launch": {
      "post": {
        "tags": [
          "partner-routing"
        ],
        "operationId": "postLaunchPartnerSubset",
        "summary": "Start a partner launch flow",
        "description": "Call from a partner UI when a user clicks a launch CTA (for example, \"Analyze in Textmetrics\"). When Tapestry session cookies (or `X-Session-Id` + `X-Tenant-Id`) are present, `tenant_id` in the body must match that session or be omitted; mismatch returns **403** `ROUTING_TENANT_SCOPE_INVALID`. Production-like environments require an authenticated session.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LaunchRequest"
              },
              "examples": {
                "launch_textmetrics": {
                  "summary": "Temporal recruiter launches into Textmetrics",
                  "value": {
                    "tenant_id": "tenant_primary",
                    "partner_key": "textmetrics",
                    "action_key": "launch",
                    "actor_role": "tenant_user",
                    "context_values": {
                      "requisition_id": "req-4482",
                      "locale": "en-US"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/RoutingSuccessResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequestResponse"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedResponse"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenResponse"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundResponse"
          },
          "500": {
            "$ref": "#/components/responses/InternalErrorResponse"
          },
          "502": {
            "$ref": "#/components/responses/BadGatewayResponse"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailableResponse"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeoutResponse"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://tapestry.querytek.io/v1/launch' \\\n  -H 'Content-Type: application/json' \\\n  -H 'X-Correlation-Id: demo-correlation-001' \\\n  -d '{\"tenant_id\":\"tenant_primary\",\"partner_key\":\"textmetrics\",\"action_key\":\"launch\",\"actor_role\":\"tenant_user\",\"context_values\":{\"requisition_id\":\"req-4482\",\"locale\":\"en-US\"}}'"
          },
          {
            "lang": "JavaScript",
            "label": "TypeScript",
            "source": "import { TapestryClient } from \"querytek-partner-sdk\";\n\nconst client = new TapestryClient(\"https://tapestry.querytek.io\");\nconst decision = await client.launch({\n  tenant_id: \"tenant_primary\",\n  partner_key: \"textmetrics\",\n  action_key: \"launch\",\n  actor_role: \"tenant_user\",\n  context_values: { requisition_id: \"req-4482\", locale: \"en-US\" },\n}, { correlationId: \"demo-correlation-001\" });"
          },
          {
            "lang": "Python",
            "source": "from tapestry_partner_sdk import TapestryClient\n\nclient = TapestryClient(\"https://tapestry.querytek.io\")\ndecision = client.launch(\n    {\n        \"tenant_id\": \"tenant_primary\",\n        \"partner_key\": \"textmetrics\",\n        \"action_key\": \"launch\",\n        \"actor_role\": \"tenant_user\",\n        \"context_values\": {\"requisition_id\": \"req-4482\", \"locale\": \"en-US\"},\n    },\n    correlation_id=\"demo-correlation-001\",\n)"
          }
        ]
      }
    },
    "/v1/partner-switch": {
      "post": {
        "tags": [
          "partner-routing"
        ],
        "operationId": "postPartnerSwitchPartnerSubset",
        "summary": "Switch active partner context",
        "description": "Call from a partner UI when a user switches from one integrated tool to another (for example, Textmetrics -> Trusaic PayParity). When Tapestry session cookies (or `X-Session-Id` + `X-Tenant-Id`) are present, `tenant_id` in the body must match that session or be omitted; mismatch returns **403** `ROUTING_TENANT_SCOPE_INVALID`. Requires `Idempotency-Key` header per SPEC-001.\n\nWhen the switch completes an **approved artifact corridor** (Textmetrics→Trusaic `compliance_pdf` or Trusaic→Textmetrics `structured_report_json`), the response may include an `artifact_transfer` extension with role-specific grants and `content_url`. Switches on flows without artifact policy omit `artifact_transfer`. Artifact bytes are never embedded in relay JSON — partners use `/v1/artifacts/*` with `partner_e2e` envelopes only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/CorrelationIdHeader"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerSwitchRequest"
              },
              "examples": {
                "switch_textmetrics_to_trusaic": {
                  "summary": "Switch from Textmetrics to Trusaic PayParity",
                  "value": {
                    "tenant_id": "tenant_primary",
                    "partner_key": "trusaic",
                    "source_partner_key": "textmetrics",
                    "actor_role": "tenant_user",
                    "context_values": {
                      "analysis_id": "pay-2026-001"
                    },
                    "reason": "handoff_after_analysis"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/RoutingSuccessResponse"
          },
          "400": {
            "$ref": "#/components/responses/BadRequestResponse"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedResponse"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenResponse"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundResponse"
          },
          "500": {
            "$ref": "#/components/responses/InternalErrorResponse"
          },
          "502": {
            "$ref": "#/components/responses/BadGatewayResponse"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailableResponse"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeoutResponse"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://tapestry.querytek.io/v1/partner-switch' \\\n  -H 'Content-Type: application/json' \\\n  -H 'X-Correlation-Id: demo-correlation-001' \\\n  -H 'Idempotency-Key: switch-demo-001' \\\n  -d '{\"tenant_id\":\"tenant_primary\",\"partner_key\":\"trusaic\",\"source_partner_key\":\"textmetrics\",\"actor_role\":\"tenant_user\",\"context_values\":{\"analysis_id\":\"pay-2026-001\"},\"reason\":\"handoff_after_analysis\"}'"
          },
          {
            "lang": "JavaScript",
            "label": "TypeScript",
            "source": "import { TapestryClient } from \"querytek-partner-sdk\";\n\nconst client = new TapestryClient(\"https://tapestry.querytek.io\");\nconst decision = await client.partnerSwitch({\n  tenant_id: \"tenant_primary\",\n  partner_key: \"trusaic\",\n  source_partner_key: \"textmetrics\",\n  actor_role: \"tenant_user\",\n  context_values: { analysis_id: \"pay-2026-001\" },\n  reason: \"handoff_after_analysis\",\n}, { correlationId: \"demo-correlation-001\", idempotencyKey: \"switch-demo-001\" });"
          },
          {
            "lang": "Python",
            "source": "from tapestry_partner_sdk import TapestryClient\n\nclient = TapestryClient(\"https://tapestry.querytek.io\")\ndecision = client.partner_switch(\n    {\n        \"tenant_id\": \"tenant_primary\",\n        \"partner_key\": \"trusaic\",\n        \"source_partner_key\": \"textmetrics\",\n        \"actor_role\": \"tenant_user\",\n        \"context_values\": {\"analysis_id\": \"pay-2026-001\"},\n        \"reason\": \"handoff_after_analysis\",\n    },\n    correlation_id=\"demo-correlation-001\",\n    idempotency_key=\"switch-demo-001\",\n)"
          }
        ]
      }
    },
    "/v1/artifacts/transfers/{transfer_id}/content": {
      "put": {
        "tags": [
          "partner-artifacts"
        ],
        "operationId": "putArtifactTransferContent",
        "summary": "Source upload — partner-E2E ciphertext envelope",
        "description": "Source partner uploads a `tapestry-artifact-envelope-v1` JSON body for an orchestrated transfer. **All shipped Phase 6 corridors use `partner_e2e` only** — plaintext uploads fail with `ARTIFACT_ENCRYPTION_REQUIRED`. **Both corridors require profile `e2e-rsa-aes-gcm-v1-signed`** (PII; P6-034). Requires source grant from `artifact_transfer` on partner-switch completion.",
        "parameters": [
          {
            "name": "transfer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/CorrelationIdHeader"
          },
          {
            "$ref": "#/components/parameters/ArtifactGrantHeader"
          },
          {
            "$ref": "#/components/parameters/TenantIdHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/vnd.tapestry.artifact+ciphertext": {
              "schema": {
                "$ref": "#/components/schemas/ArtifactEnvelopeV1"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload accepted; transfer status is upload_complete.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtifactUploadCompleteResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestResponse"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedResponse"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenResponse"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundResponse"
          },
          "409": {
            "description": "Conflicting upload attempt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalErrorResponse"
          }
        }
      },
      "get": {
        "tags": [
          "partner-artifacts"
        ],
        "operationId": "getArtifactTransferContent",
        "summary": "Target download — ciphertext envelope bytes",
        "description": "Target partner downloads the ephemeral ciphertext blob using a **hash-bound** target grant obtained from `GET /v1/artifacts/transfers/{transfer_id}` when `status` is `upload_complete`. First successful delivery consumes the blob (`ARTIFACT_TRANSFER_ALREADY_DELIVERED` on replay). Tapestry never decrypts.",
        "parameters": [
          {
            "name": "transfer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/CorrelationIdHeader"
          },
          {
            "$ref": "#/components/parameters/ArtifactGrantHeader"
          },
          {
            "$ref": "#/components/parameters/TenantIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Ciphertext envelope bytes (Content-Type per encryption mode).",
            "content": {
              "application/vnd.tapestry.artifact+ciphertext": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequestResponse"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedResponse"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenResponse"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundResponse"
          },
          "410": {
            "description": "Transfer expired or already delivered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalErrorResponse"
          }
        }
      }
    },
    "/v1/artifacts/transfers/{transfer_id}": {
      "get": {
        "tags": [
          "partner-artifacts"
        ],
        "operationId": "getArtifactTransferStatus",
        "summary": "Transfer metadata and download grant",
        "description": "Returns transfer metadata without body bytes. Target partners **must** read `download_grant` here when `status` is `upload_complete` before calling download — pre-upload target grants are rejected on content GET (`ARTIFACT_GRANT_INVALID`).",
        "parameters": [
          {
            "name": "transfer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/CorrelationIdHeader"
          },
          {
            "$ref": "#/components/parameters/ArtifactGrantHeader"
          },
          {
            "$ref": "#/components/parameters/TenantIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Transfer metadata (no artifact bytes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArtifactTransferStatusResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedResponse"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenResponse"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundResponse"
          },
          "500": {
            "$ref": "#/components/responses/InternalErrorResponse"
          }
        }
      }
    },
    "/v1/callbacks/partner/{partner_key}": {
      "post": {
        "tags": [
          "partner-callbacks"
        ],
        "operationId": "postPartnerCallback",
        "summary": "Inbound partner callback",
        "description": "Partner systems POST signed callback events to Tapestry. Tapestry validates signature and payload per registry configuration.",
        "parameters": [
          {
            "name": "partner_key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/CorrelationIdHeader"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PartnerCallbackPayload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Partner callback processed successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1SuccessEnvelope"
                }
              }
            }
          },
          "400": {
            "description": "Partner callback payload or signature is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "503": {
            "description": "Partner callback processing is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "JavaScript",
            "label": "TypeScript",
            "source": "import { TapestryClient, buildPartnerCallback } from \"querytek-partner-sdk\";\n\nconst client = new TapestryClient(\"https://tapestry.querytek.io\");\nconst envelope = buildPartnerCallback({\n  tenant_id: \"tenant_primary\",\n  partner_key: \"textmetrics\",\n  event: \"partner.report.ready\",\n  payload: { run_id: \"run-demo-001\", correlation_id: \"demo-correlation-001\" },\n  signature: \"REPLACE_ME\",\n  signature_placement: \"header\",\n});\nawait client.postCallback(envelope, { correlationId: \"demo-correlation-001\" });"
          },
          {
            "lang": "Python",
            "source": "from tapestry_partner_sdk import TapestryClient\nfrom tapestry_partner_sdk.auth import build_partner_callback\n\nclient = TapestryClient(\"https://tapestry.querytek.io\")\nenvelope = build_partner_callback(\n    tenant_id=\"tenant_primary\",\n    partner_key=\"textmetrics\",\n    event=\"partner.report.ready\",\n    payload={\"run_id\": \"run-demo-001\", \"correlation_id\": \"demo-correlation-001\"},\n    signature=\"REPLACE_ME\",\n    signature_placement=\"header\",\n)\nclient.post_callback(envelope, correlation_id=\"demo-correlation-001\")"
          },
          {
            "lang": "curl",
            "label": "cURL",
            "source": "curl -X POST 'https://tapestry.querytek.io/v1/callbacks/partner/textmetrics' \\\n  -H 'Content-Type: application/json' \\\n  -H 'X-Correlation-Id: demo-correlation-001' \\\n  -H 'X-Callback-Signature: REPLACE_ME' \\\n  -d '{\"tenant_id\":\"tenant_primary\",\"payload\":{\"event\":\"partner.report.ready\",\"run_id\":\"run-demo-001\",\"correlation_id\":\"demo-correlation-001\"}}'"
          }
        ]
      }
    }
  },
  "components": {
    "parameters": {
      "CorrelationIdHeader": {
        "name": "X-Correlation-Id",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Optional caller-provided correlation ID for traceability."
      },
      "IdempotencyKeyHeader": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 128
        },
        "description": "Recommended for launch; required for partner-initiated switch semantics per SPEC-001/SPEC-005."
      },
      "ArtifactGrantHeader": {
        "name": "X-Tapestry-Artifact-Grant",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Signed artifact grant JWT from `artifact_transfer` (source or hash-bound target grant after upload complete)."
      },
      "TenantIdHeader": {
        "name": "X-Tenant-Id",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Tenant scope for artifact upload, download, or status."
      }
    },
    "responses": {
      "RoutingSuccessResponse": {
        "description": "Routing decision resolved.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RoutingDecisionResponse"
            },
            "examples": {
              "launch_ready": {
                "value": {
                  "correlation_id": "demo-20260319-001",
                  "decision_result": "launch_ready",
                  "tenant_id": "tenant_primary",
                  "partner_key": "trusaic",
                  "action_key": "switch",
                  "redirect_url": "https://payparity.trusaic.example/sso/start",
                  "context": {
                    "analysis_id": "pay-2026-001"
                  },
                  "adapter_contract_version": "v1",
                  "source_partner_key": "textmetrics",
                  "failure_code": null
                }
              },
              "switch_with_artifact_corridor1": {
                "summary": "Corridor 1 — Textmetrics→Trusaic compliance_pdf",
                "value": {
                  "correlation_id": "demo-20260702-c1",
                  "decision_result": "launch_ready",
                  "tenant_id": "tenant_primary",
                  "partner_key": "trusaic",
                  "action_key": "switch",
                  "redirect_url": "https://payparity.trusaic.example/sso/start",
                  "context": {
                    "object_id": "d15f7f8b-ac8d-4935-9786-b6eb39bb4028"
                  },
                  "adapter_contract_version": "v1",
                  "source_partner_key": "textmetrics",
                  "failure_code": null,
                  "artifact_transfer": {
                    "transfer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
                    "artifact_class": "compliance_pdf",
                    "encryption_mode": "partner_e2e",
                    "grant": "<source-or-target-jwt>",
                    "content_url": "/v1/artifacts/transfers/7c9e6679-7425-40de-944b-e07fc1f90ae7/content",
                    "max_artifact_bytes": 5242880,
                    "transit_expires_at": "2026-07-02T12:15:00Z",
                    "object_id": "d15f7f8b-ac8d-4935-9786-b6eb39bb4028"
                  }
                }
              },
              "switch_with_artifact_corridor2": {
                "summary": "Corridor 2 — Trusaic→Textmetrics structured_report_json",
                "value": {
                  "correlation_id": "demo-20260702-c2",
                  "decision_result": "launch_ready",
                  "tenant_id": "tenant_primary",
                  "partner_key": "textmetrics",
                  "action_key": "switch",
                  "redirect_url": "https://textmetrics.example/sso/start",
                  "context": {
                    "object_id": "a28c4e11-9f3d-4b2e-8c1a-0d9e7f654321"
                  },
                  "adapter_contract_version": "v1",
                  "source_partner_key": "trusaic",
                  "failure_code": null,
                  "artifact_transfer": {
                    "transfer_id": "8d0f7780-8536-51ef-a55c-f18ed2a01bf8",
                    "artifact_class": "structured_report_json",
                    "encryption_mode": "partner_e2e",
                    "grant": "<source-or-target-jwt>",
                    "content_url": "/v1/artifacts/transfers/8d0f7780-8536-51ef-a55c-f18ed2a01bf8/content",
                    "max_artifact_bytes": 5242880,
                    "transit_expires_at": "2026-07-02T12:15:00Z",
                    "object_id": "a28c4e11-9f3d-4b2e-8c1a-0d9e7f654321"
                  }
                }
              }
            }
          }
        }
      },
      "BadRequestResponse": {
        "description": "Request is invalid or fails deterministic contract checks.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "action_not_allowed": {
                "value": {
                  "code": "ACTION_NOT_ALLOWED",
                  "message": "Launch request is missing required routing context.",
                  "correlation_id": "demo-20260319-002",
                  "status": 400,
                  "details": {
                    "field": "tenant_id"
                  }
                }
              },
              "context_invalid": {
                "value": {
                  "code": "CONTEXT_INVALID",
                  "message": "Context payload validation failed.",
                  "correlation_id": "demo-20260319-003",
                  "status": 400,
                  "details": {
                    "field": "locale",
                    "reason": "context values must be one of: string, integer, boolean"
                  }
                }
              }
            }
          }
        }
      },
      "UnauthorizedResponse": {
        "description": "Authentication/session context is invalid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "auth_invalid": {
                "value": {
                  "code": "AUTH_INVALID",
                  "message": "Routing authentication failed: missing tenant_id in request context.",
                  "correlation_id": "demo-20260319-004",
                  "status": 401
                }
              }
            }
          }
        }
      },
      "ForbiddenResponse": {
        "description": "Request is denied by tenant/partner/role policy.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "tenant_scope_invalid": {
                "value": {
                  "code": "TENANT_SCOPE_INVALID",
                  "message": "Tenant scope mismatch for partner registry operation.",
                  "correlation_id": "demo-20260319-005",
                  "status": 403
                }
              },
              "routing_tenant_scope_invalid": {
                "value": {
                  "code": "ROUTING_TENANT_SCOPE_INVALID",
                  "message": "Launch tenant scope does not match the authenticated session.",
                  "correlation_id": "demo-20260702-001",
                  "status": 403
                }
              },
              "partner_not_enabled": {
                "value": {
                  "code": "PARTNER_NOT_ENABLED",
                  "message": "Partner 'trusaic' is not launch-eligible: disabled.",
                  "correlation_id": "demo-20260319-006",
                  "status": 403
                }
              }
            }
          }
        }
      },
      "NotFoundResponse": {
        "description": "Target partner adapter cannot be resolved.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "adapter_not_found": {
                "value": {
                  "code": "PARTNER_ADAPTER_NOT_FOUND",
                  "message": "No adapter is registered for partner 'trusaic'.",
                  "correlation_id": "demo-20260319-007",
                  "status": 404
                }
              }
            }
          }
        }
      },
      "InternalErrorResponse": {
        "description": "Unhandled platform failure.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "internal_error": {
                "value": {
                  "code": "INTERNAL_ERROR",
                  "message": "Routing orchestration failed due to an unexpected internal error.",
                  "correlation_id": "demo-20260319-008",
                  "status": 500
                }
              }
            }
          }
        }
      },
      "BadGatewayResponse": {
        "description": "Downstream adapter or partner dependency failed.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "partner_downstream_error": {
                "value": {
                  "code": "PARTNER_DOWNSTREAM_ERROR",
                  "message": "Partner adapter 'trusaic' returned an unexpected error.",
                  "correlation_id": "demo-20260319-009",
                  "status": 502
                }
              }
            }
          }
        }
      },
      "ServiceUnavailableResponse": {
        "description": "Partner route temporarily unavailable due to resilience controls.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "circuit_breaker_open": {
                "value": {
                  "code": "CIRCUIT_BREAKER_OPEN",
                  "message": "Partner adapter 'trusaic' is temporarily unavailable due to repeated failures. Retry after cooldown.",
                  "correlation_id": "demo-20260319-010",
                  "status": 503,
                  "details": {
                    "partner_key": "trusaic",
                    "tenant_id": "tenant_primary",
                    "cooldown_seconds": 60
                  }
                }
              }
            }
          }
        }
      },
      "GatewayTimeoutResponse": {
        "description": "Partner timed out.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "partner_timeout": {
                "value": {
                  "code": "PARTNER_TIMEOUT",
                  "message": "Partner invocation timed out.",
                  "correlation_id": "demo-20260319-011",
                  "status": 504
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "LaunchRequest": {
        "type": "object",
        "required": [
          "tenant_id",
          "partner_key"
        ],
        "properties": {
          "tenant_id": {
            "type": "string"
          },
          "partner_key": {
            "type": "string"
          },
          "action_key": {
            "type": "string",
            "default": "launch"
          },
          "actor_role": {
            "type": "string"
          },
          "context": {
            "type": "object",
            "additionalProperties": true
          },
          "context_values": {
            "type": "object",
            "additionalProperties": true
          },
          "channel_envelope": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": false
      },
      "PartnerSwitchRequest": {
        "type": "object",
        "required": [
          "tenant_id",
          "partner_key"
        ],
        "properties": {
          "tenant_id": {
            "type": "string"
          },
          "partner_key": {
            "type": "string"
          },
          "source_partner_key": {
            "type": "string"
          },
          "actor_role": {
            "type": "string"
          },
          "context": {
            "type": "object",
            "additionalProperties": true
          },
          "context_values": {
            "type": "object",
            "additionalProperties": true
          },
          "channel_envelope": {
            "type": "object",
            "additionalProperties": true
          },
          "reason": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "RoutingDecisionResponse": {
        "type": "object",
        "required": [
          "correlation_id",
          "decision_result",
          "tenant_id",
          "partner_key",
          "action_key",
          "redirect_url",
          "context",
          "adapter_contract_version"
        ],
        "properties": {
          "correlation_id": {
            "type": "string"
          },
          "decision_result": {
            "type": "string",
            "enum": [
              "launch_ready",
              "launch_denied",
              "launch_failed"
            ]
          },
          "tenant_id": {
            "type": "string"
          },
          "partner_key": {
            "type": "string"
          },
          "action_key": {
            "type": "string",
            "enum": [
              "launch",
              "switch"
            ]
          },
          "redirect_url": {
            "type": "string",
            "nullable": true
          },
          "context": {
            "type": "object",
            "additionalProperties": true
          },
          "adapter_contract_version": {
            "type": "string"
          },
          "source_partner_key": {
            "type": "string",
            "nullable": true
          },
          "failure_code": {
            "type": "string",
            "nullable": true
          },
          "artifact_transfer": {
            "$ref": "#/components/schemas/ArtifactTransferExtension",
            "description": "Present when partner-switch completes an approved artifact corridor (corridor 1 or 2). Omitted on flows without artifact policy."
          }
        },
        "additionalProperties": false
      },
      "ArtifactTransferExtension": {
        "type": "object",
        "required": [
          "transfer_id",
          "artifact_class",
          "encryption_mode",
          "grant",
          "content_url",
          "max_artifact_bytes",
          "transit_expires_at"
        ],
        "properties": {
          "transfer_id": {
            "type": "string",
            "format": "uuid"
          },
          "artifact_class": {
            "type": "string",
            "enum": [
              "compliance_pdf",
              "structured_report_json"
            ],
            "description": "Corridor 1: compliance_pdf (PII). Corridor 2: structured_report_json (PII)."
          },
          "encryption_mode": {
            "type": "string",
            "enum": [
              "partner_e2e"
            ],
            "description": "Only mode shipped in Phase 6; courier_plaintext is not implemented."
          },
          "grant": {
            "type": "string",
            "description": "Role-specific JWT for upload (source) or initial target reservation."
          },
          "content_url": {
            "type": "string",
            "description": "Relative URL for PUT (source) or GET (target) content."
          },
          "max_artifact_bytes": {
            "type": "integer"
          },
          "transit_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "object_id": {
            "type": "string",
            "nullable": true,
            "description": "Relay correlation identifier when supplied by SPEC-018 channel envelope."
          }
        },
        "additionalProperties": false
      },
      "ArtifactEnvelopeV1": {
        "type": "object",
        "description": "Partner-E2E ciphertext envelope (tapestry-artifact-envelope-v1). Tapestry validates structure only; does not decrypt.",
        "required": [
          "envelope_version",
          "profile",
          "key_id",
          "ciphertext"
        ],
        "properties": {
          "envelope_version": {
            "type": "string",
            "enum": [
              "tapestry-artifact-envelope-v1"
            ]
          },
          "profile": {
            "type": "string",
            "enum": [
              "e2e-rsa-aes-gcm-v1",
              "e2e-rsa-aes-gcm-v1-signed"
            ],
            "description": "Both Phase 6 corridors (PII) require e2e-rsa-aes-gcm-v1-signed (P6-034)."
          },
          "key_id": {
            "type": "string"
          },
          "ciphertext": {
            "type": "string"
          },
          "logical_mime_type": {
            "type": "string",
            "description": "Optional informational hint; not validated by Tapestry."
          }
        },
        "additionalProperties": true
      },
      "ArtifactUploadCompleteResponse": {
        "type": "object",
        "required": [
          "transfer_id",
          "status",
          "sha256",
          "byte_count",
          "encryption_mode",
          "correlation_id"
        ],
        "properties": {
          "transfer_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "upload_complete"
            ]
          },
          "sha256": {
            "type": "string"
          },
          "byte_count": {
            "type": "integer"
          },
          "mime_type": {
            "type": "string"
          },
          "encryption_mode": {
            "type": "string",
            "enum": [
              "partner_e2e"
            ]
          },
          "encryption_profile": {
            "type": "string"
          },
          "logical_mime_type": {
            "type": "string",
            "nullable": true
          },
          "key_id": {
            "type": "string"
          },
          "correlation_id": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "ArtifactTransferStatusResponse": {
        "type": "object",
        "required": [
          "transfer_id",
          "status",
          "artifact_class",
          "encryption_mode",
          "correlation_id"
        ],
        "properties": {
          "transfer_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "reserved",
              "upload_complete",
              "delivered",
              "expired"
            ]
          },
          "artifact_class": {
            "type": "string"
          },
          "encryption_mode": {
            "type": "string",
            "enum": [
              "partner_e2e"
            ]
          },
          "mime_type": {
            "type": "string"
          },
          "logical_mime_type": {
            "type": "string",
            "nullable": true
          },
          "byte_count": {
            "type": "integer",
            "nullable": true
          },
          "sha256": {
            "type": "string",
            "nullable": true
          },
          "correlation_id": {
            "type": "string"
          },
          "source_partner_key": {
            "type": "string"
          },
          "target_partner_key": {
            "type": "string"
          },
          "transit_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "object_id": {
            "type": "string",
            "nullable": true
          },
          "download_grant": {
            "type": "string",
            "description": "Hash-bound target grant; present when status is upload_complete."
          }
        },
        "additionalProperties": false
      },
      "V1SuccessEnvelope": {
        "type": "object",
        "required": [
          "correlation_id"
        ],
        "properties": {
          "correlation_id": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "PartnerCallbackPayload": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string"
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "code",
          "message",
          "correlation_id",
          "status"
        ],
        "properties": {
          "code": {
            "$ref": "#/components/schemas/ErrorCode"
          },
          "message": {
            "type": "string"
          },
          "message_localized": {
            "type": "string",
            "description": "Optional locale-specific error summary (P4-019, ADR-044, SPEC-001). Included only when the response Content-Language is not English and the Tapestry catalog provides a translation for this error code without falling back to English; omitted otherwise. The `code` field remains the stable machine contract."
          },
          "correlation_id": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "details": {
            "type": "object",
            "additionalProperties": true,
            "nullable": true
          }
        },
        "additionalProperties": false
      },
      "ErrorCode": {
        "type": "string",
        "enum": [
          "AUTH_INVALID",
          "ACTION_NOT_ALLOWED",
          "TENANT_SCOPE_INVALID",
          "PARTNER_NOT_ENABLED",
          "CONTEXT_INVALID",
          "ROUTE_TARGET_INVALID",
          "PARTNER_ADAPTER_NOT_FOUND",
          "PARTNER_ACTION_UNSUPPORTED",
          "PARTNER_ASSERTION_FAILED",
          "PARTNER_DOWNSTREAM_ERROR",
          "PARTNER_TIMEOUT",
          "CIRCUIT_BREAKER_OPEN",
          "INTERNAL_ERROR",
          "ARTIFACT_ENCRYPTION_REQUIRED",
          "ARTIFACT_ENVELOPE_INVALID",
          "ARTIFACT_GRANT_INVALID",
          "ARTIFACT_GRANT_HASH_MISMATCH",
          "ARTIFACT_TRANSFER_ALREADY_DELIVERED",
          "ARTIFACT_TRANSFER_NOT_FOUND",
          "ARTIFACT_FLOW_NOT_ALLOWED",
          "ARTIFACT_SOURCE_SIGNATURE_REQUIRED"
        ]
      }
    }
  }
}
