Partner Integration OpenAPI JSON
Formatted OpenAPI contract for reading and diffing. Integrators should use the interactive reference or raw JSON URL in tooling.
{
"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"
]
}
}
}
}