{
  "openapi": "3.0.3",
  "info": {
    "title": "Executive Talents API Gateway — Control Plane",
    "version": "2.0.0",
    "description": "Application registration, scopes, cross-app grants, credentials, and signing keys (ADR-GW-1). See /developer-portal for every application registered here, and docs/gateway/CONTRACT.md in the repo for the full wire contract."
  },
  "servers": [{ "url": "/" }],
  "components": {
    "securitySchemes": {
      "adminAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Either the shared CONTROLPLANE_ADMIN_TOKEN (bootstrap/automation, e.g. et-console-staging), or a per-developer/console token minted via `controlplane token create` (see docs/gateway/CONTRACT.md §13) — not tied to any user session either way."
      },
      "internalAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "GATEWAY_INTERNAL_TOKEN — the same value the edge (cmd/gateway) sends; internal endpoints only."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "example": "CP1005" },
              "message": { "type": "string" }
            }
          }
        }
      },
      "Application": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "slug": { "type": "string", "example": "hr-service" },
          "name": { "type": "string" },
          "description": { "type": "string", "nullable": true },
          "baseUrl": { "type": "string", "format": "uri" },
          "manifestUrl": { "type": "string", "format": "uri" },
          "documentationUrl": { "type": "string", "format": "uri", "nullable": true },
          "status": { "type": "string", "enum": ["active", "suspended", "revoked"] },
          "ownerEmail": { "type": "string", "nullable": true },
          "contact": { "type": "string", "nullable": true },
          "createdBy": { "type": "string", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "Scope": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "ownerApplicationId": { "type": "string", "format": "uuid" },
          "key": { "type": "string", "example": "employee.read" },
          "description": { "type": "string", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "ScopeGrant": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "consumerApplicationId": { "type": "string", "format": "uuid" },
          "scopeId": { "type": "string", "format": "uuid" },
          "grantedBy": { "type": "string", "nullable": true },
          "grantedAt": { "type": "string", "format": "date-time" }
        }
      },
      "ClientCredential": {
        "type": "object",
        "description": "Never carries the secret or its hash — the raw secret is returned once, at mint time, in MintCredentialResponse only.",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "applicationId": { "type": "string", "format": "uuid" },
          "clientId": { "type": "string" },
          "secretPrefix": { "type": "string", "description": "First 8 hex chars of the raw secret, display-only." },
          "status": { "type": "string", "enum": ["active", "revoked"] },
          "createdBy": { "type": "string", "nullable": true },
          "createdAt": { "type": "string", "format": "date-time" },
          "lastUsedAt": { "type": "string", "format": "date-time", "nullable": true },
          "expiresAt": { "type": "string", "format": "date-time", "nullable": true },
          "revokedAt": { "type": "string", "format": "date-time", "nullable": true },
          "revokedReason": { "type": "string", "nullable": true }
        }
      },
      "RegisterApplicationRequest": {
        "type": "object",
        "required": ["manifestUrl"],
        "properties": {
          "manifestUrl": { "type": "string", "format": "uri", "description": "The application's own GET .well-known/gateway-manifest URL." },
          "actor": { "type": "string", "description": "Free-text label for who/what is acting — this control plane has no user system of its own." }
        }
      },
      "GrantRequest": {
        "type": "object",
        "required": ["providerAppSlug", "scopeKey"],
        "properties": {
          "providerAppSlug": { "type": "string", "example": "hr-service" },
          "scopeKey": { "type": "string", "example": "employee.read" },
          "actor": { "type": "string" }
        }
      },
      "MintCredentialResponse": {
        "type": "object",
        "properties": {
          "clientId": { "type": "string" },
          "clientSecret": { "type": "string", "description": "Shown exactly once — never retrievable again; only its sha256 hash is stored." },
          "sharedHeaderSecret": { "type": "string", "description": "Present only when this control plane is configured with GATEWAY_SHARED_HEADER_SECRET. The same gateway-wide value on every response (not generated per credential/application) — see README.md 'The shared header secret'." }
        }
      },
      "UsageEvent": {
        "type": "object",
        "properties": {
          "id": { "type": "integer", "format": "int64" },
          "requestId": { "type": "string", "format": "uuid" },
          "clientId": { "type": "string", "nullable": true, "description": "The calling application's id — machine-class requests only." },
          "tenantId": { "type": "string", "nullable": true },
          "routePrefix": { "type": "string" },
          "method": { "type": "string", "example": "GET" },
          "status": { "type": "integer", "example": 200 },
          "durationMs": { "type": "integer", "nullable": true },
          "routeClass": { "type": "string", "enum": ["machine", "passthrough", "blocked", "unknown"] },
          "service": { "type": "string", "nullable": true, "description": "The owning application's slug — null when no route matched at all (GW1006)." },
          "occurredAt": { "type": "string", "format": "date-time" }
        }
      },
      "UsageSummary": {
        "type": "object",
        "properties": {
          "service": { "type": "string", "example": "hr-service" },
          "total": { "type": "integer", "format": "int64" },
          "success2xx": { "type": "integer", "format": "int64" },
          "clientError4xx": { "type": "integer", "format": "int64" },
          "serverError5xx": { "type": "integer", "format": "int64" },
          "avgDurationMs": { "type": "number", "format": "double" }
        }
      },
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": { "type": "string" },
          "token_type": { "type": "string", "example": "Bearer" },
          "expires_in": { "type": "integer", "example": 900 }
        }
      }
    }
  },
  "paths": {
    "/healthz": {
      "get": {
        "summary": "Health check",
        "tags": ["Operations"],
        "responses": { "200": { "description": "OK" }, "503": { "description": "DB unreachable" } }
      }
    },
    "/.well-known/jwks.json": {
      "get": {
        "summary": "Public JWKS — active and retiring signing keys",
        "tags": ["Auth"],
        "responses": { "200": { "description": "OK" } }
      }
    },
    "/oauth/token": {
      "post": {
        "summary": "OAuth2 client-credentials token issuance",
        "tags": ["Auth"],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": ["grant_type", "client_id", "client_secret"],
                "properties": {
                  "grant_type": { "type": "string", "enum": ["client_credentials"] },
                  "client_id": { "type": "string" },
                  "client_secret": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Token issued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TokenResponse" } } } },
          "401": { "description": "Invalid client credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/apps": {
      "get": {
        "summary": "Developer portal (JSON) — every active registered application",
        "tags": ["Developer Portal"],
        "responses": {
          "200": {
            "description": "OK",
            "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Application" } } } }
          }
        }
      }
    },
    "/developer-portal": {
      "get": {
        "summary": "Developer portal (HTML) — browsable index of every active application and its docs",
        "tags": ["Developer Portal"],
        "responses": { "200": { "description": "OK", "content": { "text/html": {} } } }
      }
    },
    "/docs": {
      "get": {
        "summary": "Swagger UI for this control plane's own API (this page)",
        "tags": ["Developer Portal"],
        "responses": { "200": { "description": "OK", "content": { "text/html": {} } } }
      }
    },
    "/apps/{slug}/openapi.json": {
      "get": {
        "summary": "An application's own OpenAPI spec, fetched from its manifest's documentation URL and served through this gateway's domain",
        "tags": ["Developer Portal"],
        "parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" }, "example": "hr-service" }],
        "responses": {
          "200": { "description": "OK — content-type passed through from the application's own response" },
          "404": { "description": "Unknown slug, or the application declared no documentation URL" },
          "502": { "description": "The application's documentation URL is unreachable or returned a non-200" }
        }
      }
    },
    "/apps/{slug}/docs": {
      "get": {
        "summary": "Swagger UI for one registered application, pointed at this gateway's own proxied copy of its spec",
        "tags": ["Developer Portal"],
        "parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" }, "example": "hr-service" }],
        "responses": { "200": { "description": "OK", "content": { "text/html": {} } }, "404": { "description": "Unknown slug, or no documentation URL declared" } }
      }
    },
    "/applications": {
      "get": {
        "summary": "List all registered applications",
        "tags": ["Applications"],
        "security": [{ "adminAuth": [] }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Application" } } } } }
        }
      },
      "post": {
        "summary": "Register an application from its manifest",
        "tags": ["Applications"],
        "security": [{ "adminAuth": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RegisterApplicationRequest" } } } },
        "responses": {
          "201": { "description": "Registered", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Application" } } } },
          "409": { "description": "appId already registered", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "Manifest fetch/validation failed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/applications/{id}": {
      "get": {
        "summary": "Get a registered application",
        "tags": ["Applications"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Application" } } } },
          "404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/applications/{id}/refresh": {
      "post": {
        "summary": "Re-fetch the application's manifest and upsert its scopes/routes",
        "tags": ["Applications"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Application" } } } },
          "404": { "description": "Not found" },
          "422": { "description": "Manifest fetch/validation failed, or appId changed" }
        }
      }
    },
    "/applications/{id}/scopes": {
      "get": {
        "summary": "List scopes owned by an application",
        "tags": ["Scopes"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Scope" } } } } } }
      }
    },
    "/applications/{id}/requests": {
      "get": {
        "summary": "This application's request-log page (docs/gateway/CONTRACT.md §14)",
        "tags": ["Requests"],
        "security": [{ "adminAuth": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 100, "maximum": 100 } },
          { "name": "afterId", "in": "query", "description": "Resume after this row's id (keyset pagination) — pass the previous response's nextCursor.", "schema": { "type": "integer", "format": "int64" } },
          { "name": "status", "in": "query", "schema": { "type": "integer" } },
          { "name": "method", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "events": { "type": "array", "items": { "$ref": "#/components/schemas/UsageEvent" } }, "nextCursor": { "type": "integer", "format": "int64", "nullable": true } } } } } },
          "404": { "description": "Application not found" }
        }
      }
    },
    "/requests/summary": {
      "get": {
        "summary": "Request counts and latency grouped by app (docs/gateway/CONTRACT.md §14)",
        "tags": ["Requests"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "sinceMinutes", "in": "query", "description": "Trailing window in minutes.", "schema": { "type": "integer", "default": 1440 } }],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "object", "properties": { "sinceMinutes": { "type": "integer" }, "summary": { "type": "array", "items": { "$ref": "#/components/schemas/UsageSummary" } } } } } } }
        }
      }
    },
    "/applications/{id}/credentials": {
      "get": {
        "summary": "List an application's credentials, active and revoked alike",
        "tags": ["Credentials"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ClientCredential" } } } } } }
      },
      "post": {
        "summary": "Mint a client_id/client_secret pair for an application",
        "tags": ["Credentials"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "actor": { "type": "string" } } } } } },
        "responses": {
          "201": { "description": "Minted — clientSecret shown once", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MintCredentialResponse" } } } },
          "422": { "description": "Application not active" }
        }
      }
    },
    "/credentials/{id}/revoke": {
      "post": {
        "summary": "Revoke a client credential",
        "tags": ["Credentials"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string" } } } } } },
        "responses": { "204": { "description": "Revoked" }, "404": { "description": "Not found or already revoked" } }
      }
    },
    "/applications/{id}/grants": {
      "get": {
        "summary": "List an application's scope grants (as consumer)",
        "tags": ["Grants"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "200": { "description": "OK", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ScopeGrant" } } } } } }
      },
      "post": {
        "summary": "Grant this application (as consumer) a scope of another (provider)",
        "tags": ["Grants"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" }, "description": "The consumer application's id." }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/GrantRequest" } } } },
        "responses": {
          "201": { "description": "Granted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ScopeGrant" } } } },
          "409": { "description": "Grant already exists" },
          "422": { "description": "Self-grant, or provider/scope not found" }
        }
      }
    },
    "/grants/{id}": {
      "delete": {
        "summary": "Revoke a scope grant",
        "tags": ["Grants"],
        "security": [{ "adminAuth": [] }],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }],
        "responses": { "204": { "description": "Revoked" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/gateway/internal/config": {
      "get": {
        "summary": "Config snapshot the edge polls (routes, JWKS, rate limits)",
        "tags": ["Internal (edge only)"],
        "security": [{ "internalAuth": [] }],
        "responses": { "200": { "description": "OK" }, "304": { "description": "Not modified (If-None-Match)" } }
      }
    },
    "/api/v1/gateway/internal/usage": {
      "post": {
        "summary": "Usage event batch ingest from the edge",
        "tags": ["Internal (edge only)"],
        "security": [{ "internalAuth": [] }],
        "responses": { "202": { "description": "Accepted" } }
      }
    }
  }
}
