{
	"openapi": "3.0.3",
	"info": {
		"title": "Purple Engine API",
		"version": "1.0.0",
		"description": "Unified fintech API for Purple brands (Axiory, Purple Trading, Fintokei) and third-party applications.\n\nEach business unit is served by its own gateway host; on production, Axiory's is purple-engine.axiory.com. The host selects the gateway and the services behind it; the brand in the access token does not.\n\nThe gateway refuses a call before any service sees it with 401 or 403 and an `error.code`, among them `TOKEN_EXPIRED` (renew or sign in again), `SCOPE_INSUFFICIENT` (the app lacks the permission, or its sign-in predates it), `ROUTE_NOT_CONFIGURED` (the operation is turned off for the app), `PARAM_NOT_PERMITTED` (an argument outside the app's filter), `APP_NOT_OWNER` (a personal app signed in with another brand account than its owner) and `APP_INACTIVE` (the app was revoked)."
	},
	"servers": [
		{
			"url": "https://purple-engine-axi-plt-1609.jmedek.personal.purple-lab.dev",
			"description": "Purple Engine API"
		}
	],
	"paths": {
		"/health": {
			"get": {
				"summary": "Health check",
				"operationId": "getHealth",
				"tags": [
					"system"
				],
				"security": [],
				"responses": {
					"200": {
						"description": "Service is healthy",
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"properties": {
										"status": {
											"type": "string",
											"enum": [
												"ok"
											]
										}
									}
								}
							}
						}
					}
				}
			}
		},
		"/v1/app-info": {
			"get": {
				"summary": "App metadata for OIDC consent screen",
				"description": "Public endpoint (no auth, no API key). Returns the third-party app's name, description, and capabilities so the brand's consent screen can render the approval flow. Anti-enumeration: malformed, unknown, and fully-inactive `app_id` values all return the same 200 shape with `name: ''` and `capabilities: []`. Pair with the WAF rule `AppInfoRateLimit` (100 req/min/IP).",
				"operationId": "getAppInfo",
				"tags": [
					"public"
				],
				"security": [],
				"parameters": [
					{
						"name": "app_id",
						"in": "query",
						"required": true,
						"description": "Application identifier from the App Registry.",
						"schema": {
							"type": "string",
							"pattern": "^[a-zA-Z0-9._-]+$"
						}
					}
				],
				"responses": {
					"200": {
						"description": "App metadata. `capabilities.length === 0` means unknown / invalid / deactivated — consent screen MUST NOT render the approve button.",
						"headers": {
							"Cache-Control": {
								"schema": {
									"type": "string"
								},
								"description": "public, max-age=300"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/AppInfo"
								}
							}
						}
					},
					"401": {
						"description": "Unauthorized",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					},
					"403": {
						"description": "Forbidden",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					},
					"500": {
						"description": "Internal error (DynamoDB / engine-internal failure). Real outages are NOT suppressed into the empty-200 shape.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					}
				}
			}
		},
		"/v1/requests/{requestId}": {
			"get": {
				"summary": "Read the status of an asynchronous request",
				"description": "Returns the status of an asynchronous request that the calling app created for the calling user, on either environment. Needs no scope: a request enters an app's namespace only through a scoped create call, and a request outside the caller's namespace answers the same 404 as an unknown id. Every `202` from an asynchronous endpoint names this URL in its `Location` header.",
				"operationId": "getRequestStatus",
				"tags": [
					"requests"
				],
				"parameters": [
					{
						"name": "requestId",
						"in": "path",
						"required": true,
						"description": "The `requestId` an asynchronous endpoint returned in its `202` body.",
						"schema": {
							"type": "string"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The request's current status.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/RequestStatusResponse"
								}
							}
						}
					},
					"401": {
						"description": "Unauthorized",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					},
					"403": {
						"description": "Forbidden",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					},
					"404": {
						"description": "Unknown request, or one the caller did not create.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					},
					"500": {
						"description": "Internal Server Error",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					}
				}
			}
		}
	},
	"components": {
		"securitySchemes": {
			"apiKey": {
				"type": "apiKey",
				"in": "header",
				"name": "x-api-key"
			},
			"bearerAuth": {
				"type": "http",
				"scheme": "bearer",
				"bearerFormat": "JWT"
			}
		},
		"schemas": {
			"ErrorResponse": {
				"type": "object",
				"required": [
					"error"
				],
				"properties": {
					"error": {
						"type": "object",
						"required": [
							"code",
							"message"
						],
						"properties": {
							"code": {
								"type": "string"
							},
							"message": {
								"type": "string"
							},
							"requestId": {
								"type": "string"
							}
						}
					}
				}
			},
			"AppInfo": {
				"type": "object",
				"required": [
					"appId",
					"name",
					"capabilities"
				],
				"properties": {
					"appId": {
						"type": "string",
						"description": "Echoed `app_id` for known apps. Empty string when the request was malformed."
					},
					"name": {
						"type": "string",
						"description": "Display name for the consent screen. Empty string for unknown / invalid / deactivated apps (sentinel)."
					},
					"description": {
						"type": "string"
					},
					"capabilities": {
						"type": "array",
						"description": "Per-scope consent disclosures. Empty array for unknown / invalid / deactivated apps.",
						"items": {
							"type": "object",
							"required": [
								"scope",
								"label",
								"riskLevel"
							],
							"properties": {
								"scope": {
									"type": "string"
								},
								"label": {
									"type": "string"
								},
								"riskLevel": {
									"type": "string",
									"enum": [
										"standard",
										"high"
									]
								},
								"riskWarning": {
									"type": "string"
								}
							}
						}
					}
				}
			}
		}
	},
	"security": [
		{
			"apiKey": []
		},
		{
			"bearerAuth": []
		}
	]
}