{"openapi":"3.1.0","info":{"title":"Marketplace External API","version":"1.0.0","description":"External HTTP API for Marketplace provider integrations and API-app access requests. Provider listing creation and provider access-request read/decision are real operations; API-app access-request intake remains deferred. External case intake (`POST /api/marketplace/api-apps/cases`) persists intake rows and may create user + case rows when valid. Provider webhooks persist idempotency, audit, and outbox records only. No tenant or legal-entity scoping and no URL API-version prefix are implemented. Route existence does not establish full satellite integration. See each operation description for current behavior."},"servers":[{"url":"/","description":"Same origin as the deployed Marketplace application"}],"paths":{"/api/marketplace/api-access/applications":{"post":{"operationId":"submitExternalApiApplication","summary":"Submit external API access application","description":"Public, unauthenticated signup intake for organizations requesting Marketplace API access. Client-controlled fields (application_id, status, reviewed_by, reviewed_at) are stripped server-side. On success the application is stored with status pending for admin review.","tags":["API access applications"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalApiApplicationCreate"}}}},"responses":{"202":{"description":"Application accepted for review","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExternalApiApplicationAccepted"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missingField":{"value":{"error":"Required field is missing.","code":"marketplace_external_api_application_missing_required_field"}},"invalidBody":{"value":{"error":"Invalid JSON body.","code":"marketplace_external_api_application_invalid_body"}},"consentRequired":{"value":{"error":"terms_accepted and privacy_consent must be true.","code":"marketplace_external_api_application_consent_required"}}}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/marketplace/api-apps/access-requests":{"post":{"operationId":"createApiAppAccessRequest","summary":"Submit API-app access request (deferred)","description":"Authenticated API-app access request intake. Deferred operation: validates authentication, permissions, idempotency, and quota where applicable. Returns an execution envelope and may persist audit/idempotency records only. Does not create or update listing rows, case rows, or access-request rows in business tables today. Returns execution_status deferred with artifact fields and an audit event reference.","tags":["API apps"],"security":[{"apiAppKey":[]},{"providerBearer":[]}],"parameters":[{"name":"x-api-app-id","in":"header","required":false,"schema":{"type":"string"},"description":"Optional API app identifier to disambiguate authentication."},{"name":"idempotency-key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional idempotency key. First request with a key stores the payload hash. Repeating the same key with the same payload is replay-safe. The same key with a different payload returns 409 (conflict)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiAppAccessRequestBody"}}}},"responses":{"202":{"description":"Deferred access request artifact accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiAppAccessRequestDeferred"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid API app authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"API app authentication is required.","code":"marketplace_api_app_auth_required","persistence_status":"deferred"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Idempotency key conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdempotencyConflict"},"example":{"error":"Idempotency key conflicts with a prior payload.","code":"marketplace_api_app_idempotency_conflict","execution_status":"blocked","persistence_status":"deferred"}}}},"429":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaBlocked"}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/api/marketplace/api-apps/cases":{"post":{"operationId":"submitApiAppExternalCaseIntake","summary":"Submit external case intake","description":"Authenticated API-app inbound case intake. Stores an external case intake row. When the payload passes strict validation, the server auto-creates a marketplace user account and case (status converted). When validation fails, the intake is stored with status held_for_review for administrator review and approval. Repeating the same api_app_id + external_case_id pair returns a replay of the existing intake (200) rather than creating a duplicate. No additional permission scope is enforced beyond API app authentication (same as access-requests intake). Arbitrary extra JSON keys are preserved in the stored payload for shared information.","tags":["API apps"],"security":[{"apiAppKey":[]},{"providerBearer":[]}],"parameters":[{"name":"x-api-app-id","in":"header","required":false,"schema":{"type":"string"},"description":"Optional API app identifier to disambiguate authentication."},{"name":"idempotency-key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional idempotency key. First request with a key stores the payload hash. Repeating the same key with the same payload is replay-safe. The same key with a different payload returns 409 (conflict)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiAppCaseIntakeBody"}}}},"responses":{"200":{"description":"Idempotent replay of an existing intake for the same api_app + external_case_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiAppCaseIntakeReplay"},"example":{"intake_id":"550e8400-e29b-41d4-a716-446655440000","status":"converted"}}}},"202":{"description":"New intake accepted (converted, held for review, or materialization failed)","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ApiAppCaseIntakeConverted"},{"$ref":"#/components/schemas/ApiAppCaseIntakeHeldForReview"},{"$ref":"#/components/schemas/ApiAppCaseIntakeFailed"}]},"examples":{"converted":{"value":{"intake_id":"550e8400-e29b-41d4-a716-446655440000","status":"converted","case_id":"660e8400-e29b-41d4-a716-446655440001"}},"heldForReview":{"value":{"intake_id":"550e8400-e29b-41d4-a716-446655440000","status":"held_for_review","validation_errors":["title_required","party_email_required"]}},"failed":{"value":{"intake_id":"550e8400-e29b-41d4-a716-446655440000","status":"failed","error":"External case intake materialization failed.","code":"marketplace_api_app_case_intake_materialization_failed"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Missing or invalid API app authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"API app authentication is required.","code":"marketplace_api_app_auth_required","persistence_status":"deferred"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"405":{"description":"Non-POST method not enabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"External case intake read API is not enabled.","code":"marketplace_api_app_case_intake_method_not_allowed","persistence_status":"deferred"}}}},"409":{"description":"Idempotency key conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdempotencyConflict"}}}},"429":{"description":"Quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaBlocked"}}}},"500":{"$ref":"#/components/responses/InternalError"}}},"get":{"operationId":"readApiAppExternalCaseIntake","summary":"Read external case intake (not enabled)","description":"Case intake read API is not enabled. Returns 405 without authentication.","tags":["API apps"],"security":[],"responses":{"405":{"description":"Read API not enabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"External case intake read API is not enabled.","code":"marketplace_api_app_case_intake_method_not_allowed","persistence_status":"deferred"}}}}}}},"/api/marketplace/providers/listings":{"post":{"operationId":"createProviderListing","summary":"Create provider draft listing","description":"Creates a real private draft listing. Requires provider_id in the JSON body matching the authenticated provider subject, Bearer token authentication, idempotency-key header, and the listings_write permission scope. The first request creates the listing (201); a same-payload idempotency replay returns that listing (200) without a second insert. Listing status and moderation_status are draft.","tags":["Providers"],"security":[{"providerBearer":[]}],"parameters":[{"name":"idempotency-key","in":"header","required":true,"schema":{"type":"string"},"description":"Optional idempotency key. First request with a key stores the payload hash. Repeating the same key with the same payload is replay-safe. The same key with a different payload returns 409 (conflict)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderListingWriteBody"}}}},"responses":{"200":{"description":"Idempotent replay of the created listing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderListingCreated"}}}},"201":{"description":"Private draft listing created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderListingCreated"}}}},"400":{"description":"Missing provider_id, invalid body, or missing idempotency-key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missingProvider":{"value":{"error":"provider_id is required.","code":"marketplace_provider_api_missing_provider_id","persistence_status":"deferred"}},"missingIdempotency":{"value":{"error":"idempotency-key header is required.","code":"marketplace_provider_api_missing_idempotency_key","persistence_status":"deferred"}}}}}},"401":{"$ref":"#/components/responses/ProviderUnauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"422":{"description":"Provider owner could not be resolved or listing creation was rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Provider quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuotaBlocked"}}}}}},"get":{"operationId":"listProviderListingsDeferred","summary":"List provider listings (not enabled)","description":"Listing read API is not enabled in the current phase. Returns 405 without authentication.","tags":["Providers"],"security":[],"responses":{"405":{"description":"Read API deferred / not enabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"Provider listing read API is not enabled in this phase.","code":"marketplace_provider_api_listing_read_deferred","persistence_status":"deferred"}}}}}}},"/api/marketplace/providers/access-requests":{"get":{"operationId":"listProviderAccessRequests","summary":"List access requests for a provider-owned listing","description":"Requires provider Bearer authentication plus provider_id and listing_id query parameters. Reads real marketplace_access_requests for the provider-owned listing and returns execution_status allowed.","tags":["Providers"],"security":[{"providerBearer":[]}],"parameters":[{"name":"provider_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"listing_id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Provider-owned listing access requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderAccessRequestsList"},"example":{"items":[],"execution_status":"allowed"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/ProviderUnauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Provider owner could not be resolved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/marketplace/providers/access-requests/{requestId}/decision":{"post":{"operationId":"decideProviderAccessRequest","summary":"Approve or reject provider access request","description":"Performs a real, idempotent access-request decision. Requires provider_id and decision in the body, Bearer token authentication, idempotency-key header, and the access_requests_decide permission scope. provider_id must match the authenticated provider. Approving ensures a grant. Returns 200 for both first-seen and replay requests.","tags":["Providers"],"security":[{"providerBearer":[]}],"parameters":[{"name":"requestId","in":"path","required":true,"schema":{"type":"string"},"description":"Access request identifier supplied by the caller."},{"name":"idempotency-key","in":"header","required":true,"schema":{"type":"string"},"description":"Optional idempotency key. First request with a key stores the payload hash. Repeating the same key with the same payload is replay-safe. The same key with a different payload returns 409 (conflict)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderAccessRequestDecisionBody"}}}},"responses":{"200":{"description":"Access request decided or idempotent replay returned","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderAccessRequestDecisionResult"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/ProviderUnauthorized"},"403":{"description":"Provider mismatch or permission denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"providerMismatch":{"value":{"error":"Provider does not match the authenticated subject.","code":"marketplace_provider_api_access_request_provider_mismatch","persistence_status":"deferred"}},"authProviderMismatch":{"value":{"error":"provider_id does not match authenticated provider.","code":"marketplace_provider_api_provider_mismatch","persistence_status":"deferred"}}}}}},"409":{"$ref":"#/components/responses/IdempotencyConflict"},"422":{"description":"Provider owner could not be resolved or decision was rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/marketplace/webhooks/provider":{"post":{"operationId":"receiveProviderWebhook","summary":"Receive provider inbound webhook","description":"Inbound provider webhook ingestion. Requires Bearer token authentication with the webhooks_receive permission scope, provider_id in the JSON body or x-provider-id header, and HMAC-SHA256 signature verification via x-webhook-signature (format: sha256=<hex-digest>). Idempotency is evaluated using the idempotency-key header or event_id in the body. On acceptance records audit and outbox rows only; does not mutate listings or cases. Returns 202 on first acceptance and 200 on replay.","tags":["Webhooks"],"security":[{"providerBearer":[]}],"parameters":[{"name":"x-provider-id","in":"header","required":false,"schema":{"type":"string"},"description":"Alternative provider identifier when omitted from the JSON body."},{"name":"x-webhook-signature","in":"header","required":true,"schema":{"type":"string"},"description":"HMAC-SHA256 signature of JSON.stringify of the parsed JSON body, not the raw request bytes. Format: sha256=<lowercase-hex-digest>."},{"name":"x-webhook-event-id","in":"header","required":false,"schema":{"type":"string"},"description":"Optional event identifier used when event_id is not present in the body."},{"name":"idempotency-key","in":"header","required":false,"schema":{"type":"string"},"description":"Optional idempotency key; defaults to event_id when omitted."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderWebhookBody"}}}},"responses":{"200":{"description":"Webhook replay (idempotent re-delivery)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderWebhookAccepted"}}}},"202":{"description":"Webhook accepted for processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderWebhookAccepted"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Authentication or signature verification failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"authRequired":{"value":{"error":"Provider API authentication is required.","code":"marketplace_provider_api_auth_required","persistence_status":"deferred"}},"invalidSignature":{"value":{"error":"Webhook signature verification failed.","code":"marketplace_provider_webhook_signature_invalid","persistence_status":"deferred"}},"signingNotConfigured":{"value":{"error":"Webhook signing configuration does not match the API app record.","code":"marketplace_provider_webhook_signing_not_configured","persistence_status":"deferred"}}}}}},"403":{"$ref":"#/components/responses/Forbidden"}}}}},"components":{"securitySchemes":{"providerBearer":{"type":"http","scheme":"bearer","description":"Provider API Bearer token. The authenticated provider subject must match provider_id in the request body where required. Permission scopes are enforced per operation: listings_write, access_requests_decide, webhooks_receive."},"apiAppKey":{"type":"apiKey","in":"header","name":"x-api-app-key","description":"API app key header. May be used instead of Authorization Bearer for API-app access request intake. Optional x-api-app-id header may accompany the key."}},"schemas":{"ErrorResponse":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string"},"code":{"type":"string"},"persistence_status":{"type":"string","enum":["deferred","persisted","partial"]},"execution_status":{"type":"string"}}},"ExternalApiApplicationCreate":{"type":"object","required":["company_legal_name","country","business_email","contact_person","requested_integration_type","terms_accepted","privacy_consent"],"properties":{"company_legal_name":{"type":"string"},"trading_name":{"type":"string","nullable":true},"country":{"type":"string"},"registration_number":{"type":"string","nullable":true},"vat_tax_id":{"type":"string","nullable":true},"website_domain":{"type":"string","nullable":true},"business_email":{"type":"string","format":"email"},"contact_person":{"type":"string"},"business_category":{"type":"string","nullable":true},"intended_use":{"type":"string","nullable":true},"expected_volume":{"type":"string","nullable":true},"requested_integration_type":{"type":"string","enum":["external_provider_listing","external_case_intake","satellite_cases","webhook","other"]},"terms_accepted":{"type":"boolean","const":true},"privacy_consent":{"type":"boolean","const":true},"technical_contact":{"type":"string","nullable":true},"webhook_url":{"type":"string","nullable":true},"allowed_ips":{"type":"array","items":{"type":"string"}},"metadata":{"type":"object","additionalProperties":true}}},"ExternalApiApplicationAccepted":{"type":"object","required":["application_id","status"],"properties":{"application_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending"]}}},"ApiAppAccessRequestBody":{"type":"object","properties":{"listing_id":{"type":"string","nullable":true},"case_id":{"type":"string","nullable":true},"invite_type":{"type":"string","nullable":true},"request_id":{"type":"string","nullable":true}},"additionalProperties":true},"ApiAppAccessRequestDeferred":{"type":"object","required":["execution_status"],"properties":{"execution_status":{"type":"string","enum":["deferred"]},"persistence_status":{"type":"string"},"audit_event_id":{"type":"string","nullable":true},"listing_id":{"type":"string","nullable":true},"case_id":{"type":"string","nullable":true},"api_app":{"type":"object","additionalProperties":true}},"additionalProperties":true},"ApiAppCaseIntakeBody":{"type":"object","properties":{"external_case_id":{"type":"string","nullable":true,"description":"Optional caller-supplied case identifier. When present, duplicate submissions for the same API app replay the existing intake instead of creating a new row."},"party_email":{"type":"string","nullable":true,"description":"Party email. Required for auto-convert eligibility."},"party_name":{"type":"string","nullable":true},"title":{"type":"string","nullable":true,"description":"Case title. Required for auto-convert eligibility."},"summary":{"type":"string","nullable":true}},"additionalProperties":true,"description":"Case intake fields plus arbitrary extra shared-information keys preserved in the stored payload."},"CaseIntakeValidationErrorCode":{"type":"string","enum":["title_required","title_too_long","party_email_required","party_email_invalid","party_email_too_long","summary_too_long","party_name_too_long","external_case_id_too_long"]},"ApiAppCaseIntakeConverted":{"type":"object","required":["intake_id","status","case_id"],"properties":{"intake_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["converted"]},"case_id":{"type":"string","format":"uuid"}}},"ApiAppCaseIntakeHeldForReview":{"type":"object","required":["intake_id","status","validation_errors"],"properties":{"intake_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["held_for_review"]},"validation_errors":{"type":"array","items":{"$ref":"#/components/schemas/CaseIntakeValidationErrorCode"}}}},"ApiAppCaseIntakeFailed":{"type":"object","required":["intake_id","status","error","code"],"properties":{"intake_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["failed"]},"error":{"type":"string"},"code":{"type":"string","example":"marketplace_api_app_case_intake_materialization_failed"}}},"ApiAppCaseIntakeReplay":{"type":"object","required":["intake_id","status"],"properties":{"intake_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["received","held_for_review","converted","rejected","failed"]}}},"ProviderListingWriteBody":{"type":"object","required":["provider_id"],"properties":{"provider_id":{"type":"string"}},"additionalProperties":true},"ProviderListingCreated":{"type":"object","required":["listing_id","status","moderation_status","idempotency_state"],"properties":{"listing_id":{"type":"string"},"status":{"type":"string","enum":["draft"]},"moderation_status":{"type":"string","enum":["draft"]},"idempotency_state":{"type":"string","enum":["first_seen","replay"]},"audit_event_id":{"type":"string","nullable":true}}},"ProviderAccessRequestsList":{"type":"object","required":["items","execution_status"],"properties":{"items":{"type":"array","items":{"type":"object","additionalProperties":true}},"execution_status":{"type":"string","enum":["allowed"]}}},"ProviderAccessRequestDecisionBody":{"type":"object","required":["provider_id","decision"],"properties":{"provider_id":{"type":"string"},"decision":{"type":"string"}},"additionalProperties":true},"ProviderAccessRequestDecisionResult":{"type":"object","required":["request_id","status","idempotency_state"],"properties":{"request_id":{"type":"string"},"status":{"type":"string"},"grant_id":{"type":"string","nullable":true},"idempotency_state":{"type":"string","enum":["first_seen","replay"]},"audit_event_id":{"type":"string","nullable":true}}},"ProviderWebhookBody":{"type":"object","required":["provider_id"],"properties":{"provider_id":{"type":"string"},"event_id":{"type":"string"},"event_type":{"type":"string"},"payload":{"type":"object","additionalProperties":true}},"additionalProperties":true},"ProviderWebhookAccepted":{"type":"object","properties":{"provider_subject_key":{"type":"string"},"webhook_event_id":{"type":"string","nullable":true},"verification_status":{"type":"string"},"processing_status":{"type":"string"},"idempotency_state":{"type":"string","nullable":true},"retry_status":{"type":"string","nullable":true},"outbox_event_key":{"type":"string","nullable":true},"audit_event_id":{"type":"string","nullable":true},"persistence_status":{"type":"string"}}},"IdempotencyConflict":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","example":"marketplace_api_app_idempotency_conflict"},"execution_status":{"type":"string","example":"blocked"},"persistence_status":{"type":"string"}}},"QuotaBlocked":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"},"execution_status":{"type":"string","example":"blocked"},"persistence_status":{"type":"string"}}}},"responses":{"BadRequest":{"description":"Invalid request body or parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Forbidden":{"description":"Permission denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ProviderUnauthorized":{"description":"Missing or invalid provider Bearer authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"Provider API authentication is required.","code":"marketplace_provider_api_auth_required","persistence_status":"deferred"}}}},"IdempotencyConflict":{"description":"Idempotency key conflicts with a prior payload","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IdempotencyConflict"}}}},"InternalError":{"description":"Unexpected server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}