diff --git a/docs/rest-apis/gateway/certificate-management.md b/docs/rest-apis/gateway/certificate-management.md index e001645928..ebc46de136 100644 --- a/docs/rest-apis/gateway/certificate-management.md +++ b/docs/rest-apis/gateway/certificate-management.md @@ -95,7 +95,7 @@ Upload a new TLS certificate (PEM format) to the Gateway. The certificate is loa diff --git a/docs/rest-apis/platform-api/README.md b/docs/rest-apis/platform-api/README.md new file mode 100644 index 0000000000..7c63c4011b --- /dev/null +++ b/docs/rest-apis/platform-api/README.md @@ -0,0 +1,213 @@ + +

WSO2 API Platform - Platform API v0.9.0

+ +This document specifies a **RESTful API** for WSO2 **API Platform** - **Platform API**. + +## Authentication + +Most API endpoints require a Bearer JWT token in the `Authorization` header. Tokens are +validated against a configured Identity Provider (IDP) using JWKS-based signature +verification. + +**Required claim**: `organization` — UUID of the caller's organization. All operations are +automatically scoped to this organization. + +**Supported signing algorithms**: RS256, RS384, RS512 (RSA) and ES256, ES384, ES512 (ECDSA). +Works with any standards-compliant IDP (Keycloak, Azure AD, Okta, Auth0, WSO2 IS, etc.). + +## Scope-Based Access Control + +Authorization is enforced via OAuth2 scopes carried in the JWT. Both read (GET) and write +operations require specific scopes declared in the `security` field of each operation. + +Base URLs: +* https://localhost:9243/api/v0.9 +* https://api.platform.com/api/v0.9 + +License: Apache 2.0 + +## Table of Contents + +### [Authentication](authentication.md) + +- [Overview](authentication.md#overview) +- [Authentication Modes](authentication.md#authentication-modes) +- [Required Claims](authentication.md#required-claims) +- [Authorization](authentication.md#authorization) +- [Error Responses](authentication.md#error-responses) +- [Troubleshooting](authentication.md#troubleshooting) +- [Scope Reference](authentication.md#scope-reference) + +### [Organizations](organizations.md) + +- [Register a new organization](organizations.md#register-a-new-organization) +- [List organizations](organizations.md#list-organizations) +- [Get organization by ID](organizations.md#get-organization-by-id) +- [Check if organization exists](organizations.md#check-if-organization-exists) + +### [Projects](projects.md) + +- [Create a new project](projects.md#create-a-new-project) +- [Get all projects for current user's organization](projects.md#get-all-projects-for-current-users-organization) +- [Get project by ID](projects.md#get-project-by-id) +- [Update project](projects.md#update-project) +- [Delete project](projects.md#delete-project) + +### [Applications](applications.md) + +- [Create a new application](applications.md#create-a-new-application) +- [Get applications for current user's organization](applications.md#get-applications-for-current-users-organization) +- [Get application by handle](applications.md#get-application-by-handle) +- [Update application](applications.md#update-application) +- [Delete application](applications.md#delete-application) +- [List application API key mappings](applications.md#list-application-api-key-mappings) +- [Add application API key mappings](applications.md#add-application-api-key-mappings) +- [Remove application API key mapping](applications.md#remove-application-api-key-mapping) +- [List application associations](applications.md#list-application-associations) +- [Add application associations](applications.md#add-application-associations) +- [Remove application association](applications.md#remove-application-association) +- [List application API key mappings for an association](applications.md#list-application-api-key-mappings-for-an-association) + +### [REST APIs](rest-apis.md) + +- [Get all REST APIs for an organization](rest-apis.md#get-all-rest-apis-for-an-organization) +- [Create a new REST API](rest-apis.md#create-a-new-rest-api) +- [Get REST API by ID](rest-apis.md#get-rest-api-by-id) +- [Update REST API](rest-apis.md#update-rest-api) +- [Delete REST API](rest-apis.md#delete-rest-api) +- [Get gateways for REST API](rest-apis.md#get-gateways-for-rest-api) +- [Add gateways for REST API](rest-apis.md#add-gateways-for-rest-api) +- [Create API key](rest-apis.md#create-api-key) +- [Update API key](rest-apis.md#update-api-key) +- [Revoke API key](rest-apis.md#revoke-api-key) + +### [REST API Deployments](rest-api-deployments.md) + +- [Create and deploy a new deployment](rest-api-deployments.md#create-and-deploy-a-new-deployment) +- [Get deployments for a REST API](rest-api-deployments.md#get-deployments-for-a-rest-api) +- [Get deployment by ID](rest-api-deployments.md#get-deployment-by-id) +- [Delete deployment](rest-api-deployments.md#delete-deployment) +- [Undeploy deployment from gateway](rest-api-deployments.md#undeploy-deployment-from-gateway) +- [Restore a previous deployment](rest-api-deployments.md#restore-a-previous-deployment) + +### [Gateways](gateways.md) + +- [Register a new gateway](gateways.md#register-a-new-gateway) +- [List all gateways](gateways.md#list-all-gateways) +- [Get gateway by ID](gateways.md#get-gateway-by-id) +- [Update gateway](gateways.md#update-gateway) +- [Delete gateway](gateways.md#delete-gateway) +- [Get gateway policy manifest](gateways.md#get-gateway-policy-manifest) + +### [Gateway Tokens](gateway-tokens.md) + +- [List active gateway tokens](gateway-tokens.md#list-active-gateway-tokens) +- [Rotate gateway token](gateway-tokens.md#rotate-gateway-token) +- [Revoke gateway token](gateway-tokens.md#revoke-gateway-token) + +### [Gateway Policies](gateway-policies.md) + +- [Get synced custom policies for the current organization](gateway-policies.md#get-synced-custom-policies-for-the-current-organization) +- [Sync a custom policy from the gateway manifest](gateway-policies.md#sync-a-custom-policy-from-the-gateway-manifest) +- [Get a specific custom policy version](gateway-policies.md#get-a-specific-custom-policy-version) +- [Delete a specific custom policy version](gateway-policies.md#delete-a-specific-custom-policy-version) + +### [LLM Providers](llm-providers.md) + +- [Create a new LLM provider](llm-providers.md#create-a-new-llm-provider) +- [List all LLM providers](llm-providers.md#list-all-llm-providers) +- [Get LLM provider by identifier](llm-providers.md#get-llm-provider-by-identifier) +- [Update an existing LLM provider](llm-providers.md#update-an-existing-llm-provider) +- [Delete an LLM provider](llm-providers.md#delete-an-llm-provider) +- [List LLM proxies by provider](llm-providers.md#list-llm-proxies-by-provider) +- [Create a new API key for an LLM provider](llm-providers.md#create-a-new-api-key-for-an-llm-provider) +- [List API keys for an LLM provider](llm-providers.md#list-api-keys-for-an-llm-provider) +- [Delete an API key for an LLM provider](llm-providers.md#delete-an-api-key-for-an-llm-provider) + +### [LLM Proxies](llm-proxies.md) + +- [Create a new LLM proxy](llm-proxies.md#create-a-new-llm-proxy) +- [List all LLM proxies](llm-proxies.md#list-all-llm-proxies) +- [Get LLM proxy by unique identifier](llm-proxies.md#get-llm-proxy-by-unique-identifier) +- [Update an existing LLM proxy](llm-proxies.md#update-an-existing-llm-proxy) +- [Delete an LLM proxy](llm-proxies.md#delete-an-llm-proxy) +- [Create a new API key for an LLM proxy](llm-proxies.md#create-a-new-api-key-for-an-llm-proxy) +- [List API keys for an LLM proxy](llm-proxies.md#list-api-keys-for-an-llm-proxy) +- [Delete an API key for an LLM proxy](llm-proxies.md#delete-an-api-key-for-an-llm-proxy) + +### [LLM Provider Templates](llm-provider-templates.md) + +- [Create a new LLM provider template family](llm-provider-templates.md#create-a-new-llm-provider-template-family) +- [List templates, list a family's versions, or get a single version](llm-provider-templates.md#list-templates-list-a-familys-versions-or-get-a-single-version) +- [Create a new version by copying an existing one](llm-provider-templates.md#create-a-new-version-by-copying-an-existing-one) +- [Get LLM provider template by id](llm-provider-templates.md#get-llm-provider-template-by-id) +- [Update an existing LLM provider template](llm-provider-templates.md#update-an-existing-llm-provider-template) +- [Enable or disable a template version](llm-provider-templates.md#enable-or-disable-a-template-version) +- [Delete a template version](llm-provider-templates.md#delete-a-template-version) + +### [LLM Provider Deployments](llm-provider-deployments.md) + +- [Create and deploy a new LLM provider deployment](llm-provider-deployments.md#create-and-deploy-a-new-llm-provider-deployment) +- [Get deployments for an LLM provider](llm-provider-deployments.md#get-deployments-for-an-llm-provider) +- [Get LLM provider deployment by ID](llm-provider-deployments.md#get-llm-provider-deployment-by-id) +- [Delete LLM provider deployment](llm-provider-deployments.md#delete-llm-provider-deployment) +- [Undeploy LLM provider deployment from gateway](llm-provider-deployments.md#undeploy-llm-provider-deployment-from-gateway) +- [Restore a previous LLM provider deployment](llm-provider-deployments.md#restore-a-previous-llm-provider-deployment) + +### [LLM Proxy Deployments](llm-proxy-deployments.md) + +- [Create and deploy a new LLM proxy deployment](llm-proxy-deployments.md#create-and-deploy-a-new-llm-proxy-deployment) +- [Get deployments for an LLM proxy](llm-proxy-deployments.md#get-deployments-for-an-llm-proxy) +- [Get LLM proxy deployment by ID](llm-proxy-deployments.md#get-llm-proxy-deployment-by-id) +- [Delete LLM proxy deployment](llm-proxy-deployments.md#delete-llm-proxy-deployment) +- [Undeploy LLM proxy deployment from gateway](llm-proxy-deployments.md#undeploy-llm-proxy-deployment-from-gateway) +- [Restore a previous LLM proxy deployment](llm-proxy-deployments.md#restore-a-previous-llm-proxy-deployment) + +### [API Keys](api-keys.md) + +- [List API keys for the current user](api-keys.md#list-api-keys-for-the-current-user) + +### [MCP Proxies](mcp-proxies.md) + +- [Create a new MCP proxy](mcp-proxies.md#create-a-new-mcp-proxy) +- [List all MCP proxies](mcp-proxies.md#list-all-mcp-proxies) +- [Get MCP proxy by unique identifier](mcp-proxies.md#get-mcp-proxy-by-unique-identifier) +- [Update an existing MCP proxy](mcp-proxies.md#update-an-existing-mcp-proxy) +- [Delete an MCP proxy](mcp-proxies.md#delete-an-mcp-proxy) +- [Fetch server info from MCP proxy backend services](mcp-proxies.md#fetch-server-info-from-mcp-proxy-backend-services) + +### [MCP Proxy Deployments](mcp-proxy-deployments.md) + +- [Create and deploy a new deployment for MCP proxy](mcp-proxy-deployments.md#create-and-deploy-a-new-deployment-for-mcp-proxy) +- [Get deployments for an MCP proxy](mcp-proxy-deployments.md#get-deployments-for-an-mcp-proxy) +- [Get deployment by ID](mcp-proxy-deployments.md#get-deployment-by-id) +- [Delete deployment](mcp-proxy-deployments.md#delete-deployment) +- [Undeploy deployment from gateway](mcp-proxy-deployments.md#undeploy-deployment-from-gateway) +- [Restore a previous deployment](mcp-proxy-deployments.md#restore-a-previous-deployment) + +### [SubscriptionPlans](subscriptionplans.md) + +- [Create subscription plan](subscriptionplans.md#create-subscription-plan) +- [List subscription plans](subscriptionplans.md#list-subscription-plans) +- [Get subscription plan by ID](subscriptionplans.md#get-subscription-plan-by-id) +- [Update subscription plan](subscriptionplans.md#update-subscription-plan) +- [Delete subscription plan](subscriptionplans.md#delete-subscription-plan) + +### [Subscriptions](subscriptions.md) + +- [Create subscription](subscriptions.md#create-subscription) +- [List subscriptions](subscriptions.md#list-subscriptions) +- [Get subscription by ID](subscriptions.md#get-subscription-by-id) +- [Update subscription](subscriptions.md#update-subscription) +- [Delete subscription](subscriptions.md#delete-subscription) + +### [Secrets](secrets.md) + +- [Create a secret](secrets.md#create-a-secret) +- [List secrets](secrets.md#list-secrets) +- [Get a secret by handle](secrets.md#get-a-secret-by-handle) +- [Rotate a secret value](secrets.md#rotate-a-secret-value) +- [Delete a secret](secrets.md#delete-a-secret) + +### [Schemas](schemas.md) + diff --git a/docs/rest-apis/platform-api/api-keys.md b/docs/rest-apis/platform-api/api-keys.md new file mode 100644 index 0000000000..f2aca1aacf --- /dev/null +++ b/docs/rest-apis/platform-api/api-keys.md @@ -0,0 +1,113 @@ +

API Keys

+ +API key management operations for REST APIs and LLM Providers + +## List API keys for the current user + + + +`GET /me/api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/me/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns API keys created by the caller within the organization. +Optionally filter by one or more artifact types using a comma-separated `type` query parameter. +The plain key value is never returned. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|type|query|array[string]|false|Comma-separated list of artifact types to filter by.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**type**: Comma-separated list of artifact types to filter by. +If omitted, all types are returned. + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|type|RestApi| +|type|LlmProvider| +|type|LlmProxy| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "maskedApiKey": "string", + "status": "active", + "createdAt": "2019-08-24T14:15:22Z", + "createdBy": "john.doe", + "updatedAt": "2019-08-24T14:15:22Z", + "expiresAt": "2019-08-24T14:15:22Z", + "issuer": "api-platform-devportal", + "allowedTargets": "string", + "artifactId": "wso2-openai-provider", + "artifactType": "RestApi" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API keys retrieved successfully|[UserAPIKeyListResponse](schemas.md#schemauserapikeylistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/applications.md b/docs/rest-apis/platform-api/applications.md new file mode 100644 index 0000000000..2b3a8b6987 --- /dev/null +++ b/docs/rest-apis/platform-api/applications.md @@ -0,0 +1,1548 @@ +

Applications

+ +Application management operations + +## Create a new application + + + +`POST /applications` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/applications \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a new application within the organization specified in the JWT token. + +> Payload + +```json +{ + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[CreateApplicationRequest](schemas.md#schemacreateapplicationrequest)|true|none| + +> Example responses +> +> 201 Response + +```json +{ + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Application created successfully|[Application](schemas.md#schemaapplication)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Get applications for current user's organization + + + +`GET /applications` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/applications?projectId=default-project \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves applications belonging to the organization specified in the JWT token. +Filters by project using the required `projectId` query parameter. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|projectId|query|string|true|**Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| +|sortBy|query|string|false|Field to sort the collection by. An unrecognized value falls back to the default sort (createdAt).| +|sortOrder|query|string|false|Sort direction applied to `sortBy`.| +|query|query|string|false|Case-insensitive substring filter matched against the resource id (handle).| + +#### Detailed descriptions + +**projectId**: **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|sortBy|name| +|sortBy|createdAt| +|sortOrder|asc| +|sortOrder|desc| + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Applications retrieved successfully|[ApplicationListResponse](schemas.md#schemaapplicationlistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get application by handle + + + +`GET /applications/{applicationId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/applications/{applicationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a specific application by handle. Access is validated against +the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +> Example responses +> +> 200 Response + +```json +{ + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Application retrieved successfully|[Application](schemas.md#schemaapplication)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update application + + + +`PUT /applications/{applicationId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/applications/{applicationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an existing application by handle. + +> Payload + +```json +{ + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| +|body|body|[Application](schemas.md#schemaapplication)|true|none| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +> Example responses +> +> 200 Response + +```json +{ + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Application updated successfully|[Application](schemas.md#schemaapplication)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete application + + + +`DELETE /applications/{applicationId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/applications/{applicationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes an existing application by handle. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Application deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## List application API key mappings + + + +`GET /applications/{applicationId}/api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/applications/{applicationId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists all API keys mapped to the specified application. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "keyId": "client-key-1", + "associatedEntity": { + "id": "pizza-api", + "kind": "RestApi" + }, + "status": "ACTIVE", + "userId": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z", + "expiresAt": "2026-11-15T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Mapped API keys retrieved successfully|[MappedAPIKeyListResponse](schemas.md#schemamappedapikeylistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Add application API key mappings + + + +`POST /applications/{applicationId}/api-keys` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/applications/{applicationId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Adds API key mappings to the specified application. + +> Payload + +```json +{ + "apiKeys": [ + { + "keyId": "client-key-1", + "associatedEntity": { + "id": "pizza-api" + } + } + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| +|body|body|[AddApplicationAPIKeysRequest](schemas.md#schemaaddapplicationapikeysrequest)|true|none| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "keyId": "client-key-1", + "associatedEntity": { + "id": "pizza-api", + "kind": "RestApi" + }, + "status": "ACTIVE", + "userId": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z", + "expiresAt": "2026-11-15T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API key mappings added successfully|[MappedAPIKeyListResponse](schemas.md#schemamappedapikeylistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Remove application API key mapping + + + +`DELETE /applications/{applicationId}/api-keys/{apiKeyId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/applications/{applicationId}/api-keys/{apiKeyId}?entityID=my-rest-api-handle \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Removes a mapped API key from the specified application. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| +|apiKeyId|path|string|true|**API Key ID** consisting of the **name** of the API key.| +|entityID|query|string|true|**Entity ID** of the artifact associated with the API key mapping.| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +**apiKeyId**: **API Key ID** consisting of the **name** of the API key. + +**entityID**: **Entity ID** of the artifact associated with the API key mapping. + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|API key mapping removed successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## List application associations + + + +`GET /applications/{applicationId}/associations` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/applications/{applicationId}/associations \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists association targets mapped to the specified application. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "provider-handle", + "displayName": "OpenAI Provider", + "version": "v1.0", + "kind": "LlmProvider", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Application associations retrieved successfully|[ApplicationAssociationListResponse](schemas.md#schemaapplicationassociationlistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Add application associations + + + +`POST /applications/{applicationId}/associations` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/applications/{applicationId}/associations \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Adds association targets to the specified application. + +> Payload + +```json +{ + "associations": [ + { + "id": "provider-handle", + "kind": "LlmProvider" + } + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| +|body|body|[AddApplicationAssociationsRequest](schemas.md#schemaaddapplicationassociationsrequest)|true|none| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "provider-handle", + "displayName": "OpenAI Provider", + "version": "v1.0", + "kind": "LlmProvider", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Application associations added successfully|[ApplicationAssociationListResponse](schemas.md#schemaapplicationassociationlistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Remove application association + + + +`DELETE /applications/{applicationId}/associations/{associationId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/applications/{applicationId}/associations/{associationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Removes an association target from the specified application. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| +|associationId|path|string|true|**Association ID** consisting of the **handle** or **UUID** of the association target.| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +**associationId**: **Association ID** consisting of the **handle** or **UUID** of the association target. + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Application association removed successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## List application API key mappings for an association + + + +`GET /applications/{applicationId}/associations/{associationId}/api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/applications/{applicationId}/associations/{associationId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Lists API keys mapped to the specified application for the given associated target. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|applicationId|path|string|true|**Application ID** consisting of the **handle** of the application.| +|associationId|path|string|true|**Association ID** consisting of the **handle** or **UUID** of the association target.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**applicationId**: **Application ID** consisting of the **handle** of the application. + +**associationId**: **Association ID** consisting of the **handle** or **UUID** of the association target. + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "keyId": "client-key-1", + "associatedEntity": { + "id": "pizza-api", + "kind": "RestApi" + }, + "status": "ACTIVE", + "userId": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z", + "expiresAt": "2026-11-15T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Mapped API keys retrieved successfully|[MappedAPIKeyListResponse](schemas.md#schemamappedapikeylistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/authentication.md b/docs/rest-apis/platform-api/authentication.md new file mode 100644 index 0000000000..e44ccea978 --- /dev/null +++ b/docs/rest-apis/platform-api/authentication.md @@ -0,0 +1,364 @@ +# Authentication + +## Overview + +Every Platform API request is authenticated with a **Bearer JWT** in the `Authorization` header and +authorized against the **OAuth2 scopes** carried in that token. There is no Basic Auth path and no +API-key path for this API — the access token is the only credential. + +``` +Authorization: Bearer +``` + +Two things must be true for a request to succeed: + +1. **Authentication** — the token's signature, expiry and issuer validate, and it carries an + `organization` claim identifying the caller's organization. +2. **Authorization** — the token's `scope` claim (or, in role mode, its roles expanded to scopes) + satisfies the scope declared on that specific operation. Every operation page in this + documentation lists its required scopes under an **Authentication** heading. + +All operations are automatically scoped to the organization in the token. An organization ID is +never read from a path, query or body parameter, so a token for one organization can never reach +another organization's resources. + +## Authentication Modes + +The mode is selected once at startup with `[platform_api.auth] mode`. Only the selected mode's +configuration section is read; the others are ignored. + +| Mode | How tokens are obtained | Signature verified with | Typical use | +|---|---|---|---| +| `file` | The built-in login endpoint issues RS256 JWTs for locally configured users | `auth.jwt.public_key_file` | Quickstart, air-gapped setup | +| `external_token` | Tokens are minted outside the Platform API by a trusted issuer | `auth.jwt.public_key_file` | Existing token service | +| `idp` | An external Identity Provider's token endpoint | The IDP's JWKS (`auth.idp.jwks_url`) | Production (Asgardeo, Keycloak, Auth0, Entra ID, WSO2 IS) | + +**Supported signing algorithms**: `RS256`, `RS384`, `RS512` (RSA) and, in IDP mode, `ES256`, +`ES384`, `ES512` (ECDSA). Symmetric (`HS*`) and unsigned (`none`) tokens are always rejected. + +### Mode: `idp` (recommended for production) + +Tokens are validated against the IDP's JWKS endpoint. `jwks_url` and `issuer` are required. + +```toml +[platform_api.auth] +mode = "idp" +scope_validation = true + +[platform_api.auth.idp] +name = "asgardeo" +jwks_url = "https://accounts.example.com/oauth2/jwks" +issuer = ["https://accounts.example.com"] # list of accepted issuers +audience = [] # accepted "aud" values; empty skips the audience check +validation_mode = "scope" # "scope" (default) or "role" +role_mappings = "" # path to a role→scope YAML file, used when validation_mode = "role" +``` + +Obtain a token from the IDP as usual (client credentials, authorization code, …) and send it as a +Bearer token. The Platform API never issues tokens in this mode. + +### Mode: `file` (local users) + +The login endpoint authenticates a username/password against `[[platform_api.auth.file.users]]` and +issues an RS256 JWT signed with `auth.jwt.private_key_file`. Suitable for initial or air-gapped +setup — prefer an IDP in production. + +```toml +[platform_api.auth] +mode = "file" + +[platform_api.auth.file.organization] +id = "default" +display_name = "Default" +uuid = "99089a17-72e0-4dd8-a2f4-c8dfbb085295" # emitted as the `organization` claim + +[[platform_api.auth.file.users]] +username = "admin" +password_hash = "$2a$12$..." # bcrypt; htpasswd -bnBC 12 "" | tr -d ':\n' +scopes = "ap:organization:manage ap:rest_api:manage ap:gateway:manage" + +[platform_api.auth.jwt] +issuer = "platform-api" +public_key_file = "/etc/platform-api/keys/jwt_public.pem" +private_key_file = "/etc/platform-api/keys/jwt_private.pem" +token_ttl = "1h" +``` + +The user's `scopes` string becomes the `scope` claim of the issued token — trim it to restrict what +that user can do. + +#### Obtaining a token + +`POST /api/portal/v0.9/auth/login` (form-encoded; this path bypasses the auth middleware): + +```shell +curl -X POST https://localhost:9243/api/portal/v0.9/auth/login \ + -H 'Content-Type: application/x-www-form-urlencoded' \ + -d 'username=admin' \ + -d 'password=' +``` + +> 200 Response + +```json +{ + "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", + "expires_at": 1767225600 +} +``` + +Use `token` as the Bearer credential on every subsequent call. Its lifetime is `auth.jwt.token_ttl`. + +### Mode: `external_token` + +Tokens are minted by a trusted external issuer and only *verified* here, using +`auth.jwt.public_key_file`. The Platform API does not sign anything in this mode, and +`private_key_file` is not required. Token expiry is whatever `exp` the issuer set. + +## Required Claims + +| Claim | Required | Purpose | +|---|---|---| +| `organization` | Yes | UUID of the caller's organization. Every operation is scoped to it. A token without it is rejected. | +| `scope` | Yes (in `scope` validation mode) | Space-separated list of granted scopes, checked against the operation's declared scopes. | +| `iss` | Yes | Must match the configured issuer (`auth.jwt.issuer`, or one of `auth.idp.issuer`). | +| `exp` | Yes | Expired tokens are rejected. | +| `sub` | Yes | The user's unique ID. | +| `aud` | Conditional | Enforced in IDP mode when `auth.idp.audience` is non-empty. | +| `org_name`, `org_handle`, `username`, `email` | No | Identity metadata surfaced to handlers. | + +### Claim name mappings + +Claim names are configurable, so the same mapping is used for both issuing and validating tokens +and the two can never drift apart. Each value is either a flat claim name (`org_id`) or a +dot-separated path into a nested claim (`realm_access.roles`) — useful for IDPs like Keycloak. + +```toml +[platform_api.auth.claim_mappings] +organization = "organization" # claim carrying the org ID +org_name = "org_name" +org_handle = "org_handle" +user_id = "sub" +username = "username" +email = "email" +scope = "scope" # space-separated scope string +roles = "" # e.g. "realm_access.roles" (Keycloak) or "roles" (Asgardeo) +``` + +## Authorization + +### Scope naming convention + +Scopes follow `ap:[:]:`: + +| Form | Grants | +|---|---| +| `ap:rest_api:read` | A single action on a resource | +| `ap:rest_api:manage` | Every action on that resource (read, create, update, delete) | +| `ap:rest_api:deployment:manage` | Every action on the **subresource** only — not on the parent `rest_api` | + +An operation may declare several acceptable scopes. The token needs **at least one** of them — +which is why `ap:rest_api:create` and `ap:rest_api:manage` typically both appear on a create +operation. + +### Validation modes + +`auth.idp.validation_mode` selects how effective scopes are derived: + +- **`scope`** (default) — the token's `scope` claim is used directly. +- **`role`** — the roles found at `claim_mappings.roles` are expanded into scopes via the YAML file + at `auth.idp.role_mappings`. When a token carries several roles, the effective scopes are the + **union** of all matching role entries (most permissive wins). + +```yaml +# role_mappings YAML — requires a server restart to take effect +roles: + - name: platform-admin + scopes: + - ap:organization:manage + - ap:rest_api:manage + - ap:gateway:manage + - name: platform-operator + scopes: + - ap:organization:read + - ap:rest_api:read +``` + +### Disabling scope checks + +```toml +[platform_api.auth] +scope_validation = false +``` + +This authenticates callers but performs **no** authorization checks. Use it only to temporarily +bypass authorization during development — never in a deployed environment. + +### Unauthenticated paths + +`[platform_api.auth] skip_paths` lists path prefixes that bypass the auth middleware entirely — +health and metrics probes, the file-mode login endpoint, and the internal gateway routes (which +authenticate with a gateway token instead). Setting this key **replaces** the built-in default +list, so keep any entries you still need. + +## Error Responses + +Authentication and authorization failures are deliberately uniform — the response never reveals +*why* a credential was rejected. The specific reason is logged internally with a tracking ID. + +| Status | Meaning | Cause | +|---|---|---| +| 401 | Unauthorized | Missing `Authorization` header, non-Bearer header, malformed/expired/invalid-signature token, wrong issuer, or missing `organization` claim | +| 403 | Forbidden | Token is valid, but its effective scopes do not satisfy the operation's required scopes | + +A `401` carries the identical payload for every one of those causes, so it cannot be used to probe +which part of a credential was wrong. + +## Troubleshooting + +- **Every request returns 401 after switching to IDP mode** — check that `jwks_url` is reachable + from the Platform API and that the token's `iss` is listed in `auth.idp.issuer`. +- **401 with a token that the IDP says is valid** — the `organization` claim is probably missing or + under a different name. Point `claim_mappings.organization` at the claim your IDP actually emits. +- **401 only for some clients in IDP mode** — `auth.idp.audience` is non-empty and those tokens were + minted for a different client. Add the audience value or leave the list empty to skip the check. +- **403 on operations the user should be able to perform** — compare the token's `scope` claim + against the scopes listed on that operation's page. In `role` mode, confirm the role name in the + token matches an entry in the `role_mappings` file (it requires a restart after editing). +- **403 in `role` mode on every operation** — `claim_mappings.roles` is unset, so no roles are read + from the token. Set it to the claim path your IDP uses (`realm_access.roles` for Keycloak, + `roles` for Asgardeo/Entra ID). +- **Login endpoint returns 401 in `file` mode** — the stored `password_hash` must be a bcrypt hash; + a plaintext password there will never match. + +## Scope Reference + +The complete set of scopes defined by this API, as declared in the OpenAPI `OAuth2Security` scheme. + +- OAuth2, flow: `clientCredentials` +- Token URL declared in the spec: `https://localhost:9243/oauth2/token` + +The declared token URL describes the `clientCredentials` flow an external IDP is expected to expose, +and is what generated clients target in `idp` mode — the Platform API does not serve it itself. +In `file` mode there is no client-credentials endpoint; obtain a token from the username/password +login endpoint described under [Obtaining a token](#obtaining-a-token) +(`POST /api/portal/v0.9/auth/login`) instead. + +|Scope|Scope Description| +|---|---| +|ap:api_key:read|Read API keys owned by the current user| +|ap:application:api_key:create|Create an application API key| +|ap:application:api_key:delete|Delete an application API key| +|ap:application:api_key:manage|Full access to application API keys| +|ap:application:api_key:read|Read application API keys| +|ap:application:association:api_key:read|Read API keys for an application association| +|ap:application:association:create|Create an application association| +|ap:application:association:delete|Delete an application association| +|ap:application:association:manage|Full access to application associations| +|ap:application:association:read|Read application associations| +|ap:application:create|Create an application| +|ap:application:delete|Delete an application| +|ap:application:manage|Full access to applications| +|ap:application:read|Read applications| +|ap:application:update|Update an application| +|ap:gateway:create|Create a gateway| +|ap:gateway:delete|Delete a gateway| +|ap:gateway:manage|Full access to gateways| +|ap:gateway:manifest:read|Read a gateway manifest| +|ap:gateway:read|Read gateways| +|ap:gateway_custom_policy:create|Create a gateway custom policy| +|ap:gateway_custom_policy:delete|Delete a gateway custom policy| +|ap:gateway_custom_policy:manage|Full access to gateway custom policies| +|ap:gateway_custom_policy:read|Read gateway custom policies| +|ap:gateway:token:create|Create a gateway token| +|ap:gateway:token:delete|Delete a gateway token| +|ap:gateway:token:manage|Full access to gateway tokens| +|ap:gateway:token:read|Read gateway tokens| +|ap:gateway:update|Update a gateway| +|ap:llm_provider:api_key:create|Create an LLM provider API key| +|ap:llm_provider:api_key:delete|Delete an LLM provider API key| +|ap:llm_provider:api_key:manage|Full access to LLM provider API keys| +|ap:llm_provider:api_key:read|Read LLM provider API keys| +|ap:llm_provider:create|Create an LLM provider| +|ap:llm_provider:delete|Delete an LLM provider| +|ap:llm_provider:deployment:create|Deploy an LLM provider| +|ap:llm_provider:deployment:delete|Delete an LLM provider deployment| +|ap:llm_provider:deployment:manage|Full access to LLM provider deployments| +|ap:llm_provider:deployment:read|Read LLM provider deployments| +|ap:llm_provider:deployment:restore|Restore an LLM provider deployment| +|ap:llm_provider:deployment:undeploy|Undeploy an LLM provider deployment| +|ap:llm_provider:manage|Full access to LLM providers| +|ap:llm_provider:read|Read LLM providers| +|ap:llm_provider:update|Update an LLM provider| +|ap:llm_proxy:api_key:create|Create an LLM proxy API key| +|ap:llm_proxy:api_key:delete|Delete an LLM proxy API key| +|ap:llm_proxy:api_key:manage|Full access to LLM proxy API keys| +|ap:llm_proxy:api_key:read|Read LLM proxy API keys| +|ap:llm_proxy:create|Create an LLM proxy| +|ap:llm_proxy:delete|Delete an LLM proxy| +|ap:llm_proxy:deployment:create|Deploy an LLM proxy| +|ap:llm_proxy:deployment:delete|Delete an LLM proxy deployment| +|ap:llm_proxy:deployment:manage|Full access to LLM proxy deployments| +|ap:llm_proxy:deployment:read|Read LLM proxy deployments| +|ap:llm_proxy:deployment:restore|Restore an LLM proxy deployment| +|ap:llm_proxy:deployment:undeploy|Undeploy an LLM proxy deployment| +|ap:llm_proxy:manage|Full access to LLM proxies| +|ap:llm_proxy:read|Read LLM proxies| +|ap:llm_proxy:update|Update an LLM proxy| +|ap:llm_template:create|Create an LLM provider template| +|ap:llm_template:delete|Delete an LLM provider template| +|ap:llm_template:manage|Full access to LLM provider templates| +|ap:llm_template:read|Read LLM provider templates| +|ap:llm_template:update|Update an LLM provider template| +|ap:mcp_proxy:create|Create an MCP proxy| +|ap:mcp_proxy:delete|Delete an MCP proxy| +|ap:mcp_proxy:deployment:create|Deploy an MCP proxy| +|ap:mcp_proxy:deployment:delete|Delete an MCP proxy deployment| +|ap:mcp_proxy:deployment:manage|Full access to MCP proxy deployments| +|ap:mcp_proxy:deployment:read|Read MCP proxy deployments| +|ap:mcp_proxy:deployment:restore|Restore an MCP proxy deployment| +|ap:mcp_proxy:deployment:undeploy|Undeploy an MCP proxy deployment| +|ap:mcp_proxy:manage|Full access to MCP proxies| +|ap:mcp_proxy:read|Read MCP proxies| +|ap:mcp_proxy:update|Update an MCP proxy| +|ap:organization:create|Create an organization| +|ap:organization:manage|Full access to an organization| +|ap:organization:read|Read an organization| +|ap:project:create|Create a project| +|ap:project:delete|Delete a project| +|ap:project:manage|Full access to projects| +|ap:project:read|Read projects| +|ap:project:update|Update a project| +|ap:rest_api:api_key:create|Create an API key for a REST API| +|ap:rest_api:api_key:delete|Delete an API key of a REST API| +|ap:rest_api:api_key:manage|Full access to a REST API's API keys| +|ap:rest_api:api_key:update|Update an API key of a REST API| +|ap:rest_api:create|Create a REST API| +|ap:rest_api:delete|Delete a REST API| +|ap:rest_api:deployment:create|Deploy a REST API| +|ap:rest_api:deployment:delete|Delete a REST API deployment| +|ap:rest_api:deployment:manage|Full access to REST API deployments| +|ap:rest_api:deployment:read|Read REST API deployments| +|ap:rest_api:deployment:restore|Restore a REST API deployment| +|ap:rest_api:deployment:undeploy|Undeploy a REST API deployment| +|ap:rest_api:gateway:create|Add gateways to a REST API| +|ap:rest_api:gateway:manage|Full access to a REST API's gateways| +|ap:rest_api:gateway:read|Read a REST API's gateways| +|ap:rest_api:manage|Full access to REST APIs| +|ap:rest_api:read|Read REST APIs| +|ap:rest_api:update|Update a REST API| +|ap:secret:create|Create a secret| +|ap:secret:delete|Delete a secret| +|ap:secret:manage|Full access to secrets| +|ap:secret:read|Read secrets| +|ap:secret:update|Update a secret| +|ap:subscription:create|Create a subscription| +|ap:subscription:delete|Delete a subscription| +|ap:subscription:manage|Full access to subscriptions| +|ap:subscription:read|Read subscriptions| +|ap:subscription:update|Update a subscription| +|ap:subscription_plan:create|Create a subscription plan| +|ap:subscription_plan:delete|Delete a subscription plan| +|ap:subscription_plan:manage|Full access to subscription plans| +|ap:subscription_plan:read|Read subscription plans| +|ap:subscription_plan:update|Update a subscription plan| diff --git a/docs/rest-apis/platform-api/gateway-policies.md b/docs/rest-apis/platform-api/gateway-policies.md new file mode 100644 index 0000000000..efdaef8c12 --- /dev/null +++ b/docs/rest-apis/platform-api/gateway-policies.md @@ -0,0 +1,431 @@ +

Gateway Policies

+ +Custom gateway policy management operations + +## Get synced custom policies for the current organization + + + +`GET /gateway-custom-policies` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/gateway-custom-policies \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns all custom policies synced to the current organization (from the JWT `organization` claim). + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +> Example responses +> +> 200 Response + +```json +{ + "count": 0, + "list": [ + { + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "organizationUuid": "bc554ded-7e40-44a7-b397-48480793ad03", + "name": "rate-limit-custom", + "version": "1.0.0", + "description": "Custom rate limiting policy", + "policyDefinition": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of custom policies|[CustomPolicyListResponse](schemas.md#schemacustompolicylistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Sync a custom policy from the gateway manifest + + + +`POST /gateway-custom-policies/sync` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/gateway-custom-policies/sync?gatewayId=prod-gateway-01&policyName=set-wso2-headers&policyVersion=1.0.0 \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Syncs a custom policy from the gateway manifest into the organization's custom policy registry. +Version-based rules apply: +- **New major version**: creates a new policy record (e.g. v1.x.x and v2.x.x coexist). +- **New minor version** (same major): updates the existing record (e.g. v1.1.0 → v1.2.0). +- **Patch version change** (same major.minor): not allowed. +- **Downgrade**: not allowed. +Policy names are case-insensitive. +After syncing, the policy can be applied to APIs in the organization. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway whose manifest contains the policy| +|policyName|query|string|true|Name of the custom policy (case-insensitive)| +|policyVersion|query|string|true|Version of the custom policy in MAJOR.MINOR.PATCH format| + +> Example responses +> +> 200 Response + +```json +{ + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "organizationUuid": "bc554ded-7e40-44a7-b397-48480793ad03", + "name": "rate-limit-custom", + "version": "1.0.0", + "description": "Custom rate limiting policy", + "policyDefinition": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 422 Response + +```json +{ + "status": "error", + "code": "POLICY_INVALID_STATE", + "message": "The policy is not a custom policy, or its manifest is unavailable." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Custom policy synced successfully|[CustomPolicyResponse](schemas.md#schemacustompolicyresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|422|[Unprocessable Entity](https://tools.ietf.org/html/rfc2518#section-10.3)|Policy is not a custom policy or manifest is unavailable|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get a specific custom policy version + + + +`GET /gateway-custom-policies/{gatewayCustomPolicyId}/versions/{version}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/gateway-custom-policies/{gatewayCustomPolicyId}/versions/{version} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns a custom policy by its UUID and version for the current organization. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayCustomPolicyId|path|string(uuid)|true|UUID of the custom policy record| +|version|path|string|true|Version of the custom policy (e.g. "1.0.0")| + +> Example responses +> +> 200 Response + +```json +{ + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "organizationUuid": "bc554ded-7e40-44a7-b397-48480793ad03", + "name": "rate-limit-custom", + "version": "1.0.0", + "description": "Custom rate limiting policy", + "policyDefinition": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Custom policy retrieved successfully|[CustomPolicyResponse](schemas.md#schemacustompolicyresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete a specific custom policy version + + + +`DELETE /gateway-custom-policies/{gatewayCustomPolicyId}/versions/{version}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/gateway-custom-policies/{gatewayCustomPolicyId}/versions/{version} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a custom policy by its UUID and version. The policy must not be in use by any APIs. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayCustomPolicyId|path|string(uuid)|true|UUID of the custom policy record| +|version|path|string|true|Version of the custom policy (e.g. "1.0.0")| + +> Example responses +> +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Custom policy deleted successfully|None| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/gateway-tokens.md b/docs/rest-apis/platform-api/gateway-tokens.md new file mode 100644 index 0000000000..25284026a5 --- /dev/null +++ b/docs/rest-apis/platform-api/gateway-tokens.md @@ -0,0 +1,358 @@ +

Gateway Tokens

+ +Gateway token rotation and revocation operations + +## List active gateway tokens + + + +`GET /gateways/{gatewayId}/tokens` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/gateways/{gatewayId}/tokens \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns all active tokens for the specified gateway. Token hashes and salts are never exposed. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayId|path|string|true|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway. + +> Example responses +> +> 200 Response + +```json +{ + "count": 0, + "list": [ + { + "id": "abc12345-f678-90de-f123-456789abcdef", + "status": "active", + "createdAt": "2025-10-14T10:30:00Z", + "revokedAt": null + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of active tokens|[GatewayTokenListResponse](schemas.md#schemagatewaytokenlistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Rotate gateway token + + + +`POST /gateways/{gatewayId}/tokens` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/gateways/{gatewayId}/tokens \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Generates a new authentication token for the gateway. The existing token remains active +to enable zero-downtime rotation. Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayId|path|string|true|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway. + +> Example responses +> +> 201 Response + +```json +{ + "id": "def45678-g901-23hi-j456-789012klmnop", + "token": "REDACTED_TOKEN", + "createdAt": "2025-10-15T14:20:00Z", + "message": "New token generated successfully. Old token remains active until revoked." +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|New token generated successfully|[TokenRotationResponse](schemas.md#schematokenrotationresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Revoke gateway token + + + +`DELETE /gateways/{gatewayId}/tokens/{tokenId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/gateways/{gatewayId}/tokens/{tokenId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Revokes a specific gateway token. Operation is idempotent - revoking an already-revoked +token succeeds. Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayId|path|string|true|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway.| +|tokenId|path|string(uuid)|true|**Token ID** consisting of the **UUID** of the Token.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway. + +**tokenId**: **Token ID** consisting of the **UUID** of the Token. + +> Example responses +> +> 200 Response + +```json +{ + "message": "Token revoked successfully" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Token revoked successfully|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +

Response Schema

+ +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» message|string|false|none|none| diff --git a/docs/rest-apis/platform-api/gateways.md b/docs/rest-apis/platform-api/gateways.md new file mode 100644 index 0000000000..3230c28b67 --- /dev/null +++ b/docs/rest-apis/platform-api/gateways.md @@ -0,0 +1,809 @@ +

Gateways

+ +Gateway registration and management operations + +## Register a new gateway + + + +`POST /gateways` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/gateways \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a new gateway within the organization specified in the JWT token. +Organization ID is automatically extracted from the token and does not need to be provided. + +> Payload + +```json +{ + "id": "prod-gateway-01", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "version": "1.0" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[CreateGatewayRequest](schemas.md#schemacreategatewayrequest)|true|Gateway registration details| + +> Example responses +> +> 201 Response + +```json +{ + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Gateway registered successfully|[GatewayResponse](schemas.md#schemagatewayresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List all gateways + + + +`GET /gateways` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/gateways \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a list of all registered gateways for the organization specified in the JWT token. +Organization ID is automatically extracted from the token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| +|sortBy|query|string|false|Field to sort the collection by. An unrecognized value falls back to the default sort (createdAt).| +|sortOrder|query|string|false|Sort direction applied to `sortBy`.| +|query|query|string|false|Case-insensitive substring filter matched against the resource id (handle).| + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|sortBy|name| +|sortBy|createdAt| +|sortOrder|asc| +|sortOrder|desc| + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Gateways retrieved successfully|[GatewayListResponse](schemas.md#schemagatewaylistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get gateway by ID + + + +`GET /gateways/{gatewayId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/gateways/{gatewayId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a specific gateway by its ID (handle). Access is validated against the organization +in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayId|path|string|true|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway. + +> Example responses +> +> 200 Response + +```json +{ + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Gateway retrieved successfully|[GatewayResponse](schemas.md#schemagatewayresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update gateway + + + +`PUT /gateways/{gatewayId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/gateways/{gatewayId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an existing gateway's mutable fields (description). +Access is validated against the organization in the JWT token. + +> Payload + +```json +{ + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayId|path|string|true|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway.| +|body|body|[GatewayResponse](schemas.md#schemagatewayresponse)|true|Gateway object that needs to be updated| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway. + +> Example responses +> +> 200 Response + +```json +{ + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Gateway updated successfully|[GatewayResponse](schemas.md#schemagatewayresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete gateway + + + +`DELETE /gateways/{gatewayId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/gateways/{gatewayId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Permanently deletes a gateway and all associated tokens (CASCADE). +Deletion is blocked if the gateway has active API deployments or WebSocket connections. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayId|path|string|true|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway. + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Gateway deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get gateway policy manifest + + + +`GET /gateways/{gatewayId}/manifest` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/gateways/{gatewayId}/manifest \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns the policy manifest for the specified gateway. The manifest is populated by the gateway +controller when it connects to the platform API, and contains all installed policies. Custom +policies additionally include their full policy definition schema. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|gatewayId|path|string|true|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway. + +> Example responses +> +> 200 Response + +```json +{ + "policies": [ + { + "name": "set-wso2-headers", + "version": "v0.8.0", + "description": "Sets WSO2-specific headers in the request and response.", + "isCustomPolicy": true, + "policyDefinition": {} + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Gateway policy manifest|[ManifestSyncResponse](schemas.md#schemamanifestsyncresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/llm-provider-deployments.md b/docs/rest-apis/platform-api/llm-provider-deployments.md new file mode 100644 index 0000000000..ea90b19793 --- /dev/null +++ b/docs/rest-apis/platform-api/llm-provider-deployments.md @@ -0,0 +1,764 @@ +

LLM Provider Deployments

+ +LLM provider deployment operations + +## Create and deploy a new LLM provider deployment + + + +`POST /llm-providers/{llmProviderId}/deployments` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/deployments \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates an immutable deployment artifact for an LLM provider and deploys it to a specified gateway. +Each deployment targets a single gateway. The providerId parameter is the LLM provider handle (identifier), +not the UUID. The operation returns a transitional DEPLOYING status. Final success or failure will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. +Access is validated against the organization in the JWT token. + +> Payload + +```json +{ + "name": "v1.0-production", + "base": "current", + "gatewayId": "prod-gateway-01", + "metadata": {} +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|body|body|[DeployRequest](schemas.md#schemadeployrequest)|true|Deployment request with gateway ID, base reference, and metadata| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|LLM provider deployed successfully|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Get deployments for an LLM provider + + + +`GET /llm-providers/{llmProviderId}/deployments` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/deployments \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves all deployment artifacts for a specific LLM provider. The providerId parameter is the +LLM provider handle (identifier), not the UUID. Supports filtering by gateway handle and deployment status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|gatewayId|query|string|false|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by.| +|status|query|string|false|Filter deployments by status (DEPLOYED, UNDEPLOYED, DEPLOYING, UNDEPLOYING, FAILED, or ARCHIVED)| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by. + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|status|DEPLOYED| +|status|UNDEPLOYED| +|status|DEPLOYING| +|status|UNDEPLOYING| +|status|FAILED| +|status|ARCHIVED| + +> Example responses +> +> 200 Response + +```json +{ + "count": 0, + "list": [ + { + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Deployments retrieved successfully|[DeploymentListResponse](schemas.md#schemadeploymentlistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get LLM provider deployment by ID + + + +`GET /llm-providers/{llmProviderId}/deployments/{deploymentId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/deployments/{deploymentId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves metadata for a specific LLM provider deployment artifact including status, gateway association, +and timestamps. Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|deploymentId|path|string(uuid)|true|The UUID of the deployment| + +> Example responses +> +> 200 Response + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Deployment metadata retrieved successfully|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete LLM provider deployment + + + +`DELETE /llm-providers/{llmProviderId}/deployments/{deploymentId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/deployments/{deploymentId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a deployment artifact. Deletion is only allowed when the deployment is in UNDEPLOYED status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|deploymentId|path|string(uuid)|true|The UUID of the deployment| + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "DEPLOYMENT_ACTIVE", + "message": "Cannot delete an active deployment - undeploy it first." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Deployment deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The deployment is still active and must be undeployed before deletion.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Undeploy LLM provider deployment from gateway + + + +`POST /llm-providers/{llmProviderId}/deployments/{deploymentId}/undeploy` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/deployments/{deploymentId}/undeploy?gatewayId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Undeploys an active LLM provider deployment, stopping it from being served on the specified gateway. +The deployment artifact remains in the system and can be restored later. +Returns the updated deployment object with initial status UNDEPLOYING. Final status (UNDEPLOYED or FAILED) will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. + +The gatewayId query parameter is validated against the deployment's bound gateway to prevent unintended operations. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|deploymentId|path|string|true|UUID of the deployment to undeploy| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway)| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes UNDEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "UNDEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Undeploy initiated successfully. Returns the deployment with initial status UNDEPLOYING. Poll status for final result.|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Restore a previous LLM provider deployment + + + +`POST /llm-providers/{llmProviderId}/deployments/{deploymentId}/restore` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/deployments/{deploymentId}/restore?gatewayId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Initiates restoring a previous deployment (ARCHIVED or UNDEPLOYED) on the specified gateway. +Returns the deployment with initial status DEPLOYING. Final success or failure will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. +The target deployment must not already be in DEPLOYED status. + +The gatewayId query parameter is validated against the deployment's bound gateway to prevent unintended operations. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|deploymentId|path|string|true|UUID of the deployment to restore (must be ARCHIVED or UNDEPLOYED)| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway)| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Restore initiated successfully. Returns the deployment with initial status DEPLOYING. Poll status for final result.|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/llm-provider-templates.md b/docs/rest-apis/platform-api/llm-provider-templates.md new file mode 100644 index 0000000000..ce41b77e41 --- /dev/null +++ b/docs/rest-apis/platform-api/llm-provider-templates.md @@ -0,0 +1,1478 @@ +

LLM Provider Templates

+ +LLM provider template management operations + +## Create a new LLM provider template family + + + +`POST /llm-provider-templates` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-provider-templates \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a new template family, starting at v1.0, with an +`LLMProviderTemplate` body. To add a new version to an existing family, +use `POST /llm-provider-templates/copy` instead. Organization is +identified via the JWT token. + +> Payload + +```json +{ + "id": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "version": "v1.0", + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + } +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[LLMProviderTemplate](schemas.md#schemallmprovidertemplate)|true|The template family to create (starts at v1.0).| + +> Example responses +> +> 201 Response + +```json +{ + "id": "openai", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "readOnly": false, + "version": "v1.0", + "isLatest": true, + "enabled": true, + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + }, + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|LLM provider template created successfully|[LLMProviderTemplate](schemas.md#schemallmprovidertemplate)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List templates, list a family's versions, or get a single version + + + +`GET /llm-provider-templates` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-provider-templates \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves LLM provider templates based on the `query` parameter: + - no `query` -> list all template versions + - `query=latest:true` -> list the latest version of each template + - `query=groupId:` -> list all versions for the template + - `query=groupId:&version:` -> retrieve a specific template version + +All filters are provided via the URL-encoded `query` parameter (e.g. +`?query=groupId%3Awso2-openai%26version%3Av2.0`). + +Returns a single `LLMProviderTemplate` only when both `groupId` and `version` are specified; +otherwise returns an `LLMProviderTemplateListResponse`. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|query|query|string|false|URL-encoded search DSL. `query=latest:true` lists only the latest version of each family; `query=groupId:` lists that family's versions; adding `&version:` returns the single full template for that version. Terms are `&`-separated `key:value` pairs and the whole value is percent-encoded (e.g. groupId%3Awso2-openai%26version%3Av2.0).| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +> Example responses +> +> 200 Response + +```json +{ + "count": 5, + "list": [ + { + "id": "openai", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "version": "v1.0", + "isLatest": true, + "enabled": true, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Without a `version` term: a list response — either templates (all versions by default, or the latest of each family when `query=latest:true`) or a family's versions. With `query=groupId:&version:`: the single full template for that version.|Inline| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +

Response Schema

+ +#### Enumerated Values + +|Property|Value| +|---|---| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| + +## Create a new version by copying an existing one + + + +`POST /llm-provider-templates/copy` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-provider-templates/copy?fromTemplateId=openai-v3-0&toVersion=v4.0 \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a new version within a template family by cloning an existing +version (`fromTemplateId`) and applying optional field overrides from +the body. The new version lands in the same family as the source and +becomes the latest. + +Versions start at v1.0 and only go higher — `toVersion` must match the +`v.` pattern with a major of at least 1 (e.g. v2.0); v0.x +is rejected. `toTemplateId`, when supplied, must equal the handle +derived from the family and `toVersion`. Organization is identified via +the JWT token. + +Built-in (WSO2-managed) template families are immutable: adding a version +to one is rejected with `403`. To base a custom template on a built-in, +create a new template (`POST /llm-provider-templates`) instead — it gets +its own `group_id` and starts at v1.0. + +> Payload + +```json +{ + "displayName": "OpenAI", + "version": "v2.0", + "managedBy": "organization", + "description": "Default OpenAI template", + "openapi": "string", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + } +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|fromTemplateId|query|string|true|Handle (id) of the source version to copy from.| +|toTemplateId|query|string|false|Expected handle of the new version. Must equal the handle derived from the source family and toVersion; used to validate the target.| +|toVersion|query|string|true|New version identifier, e.g. v4.0. Must match v. with a major of at least 1, and be unique within the family.| +|body|body|[CreateLLMProviderTemplateVersionRequest](schemas.md#schemacreatellmprovidertemplateversionrequest)|false|Optional overrides applied on top of the copied config. Any field present replaces the value cloned from the source version.| + +> Example responses +> +> New version created successfully + +```json +{ + "id": "openai-v4-0", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "organization", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "readOnly": false, + "version": "v4.0", + "isLatest": true, + "enabled": true +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|New version created successfully|[LLMProviderTemplate](schemas.md#schemallmprovidertemplate)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Get LLM provider template by id + + + +`GET /llm-provider-templates/{llmProviderTemplateId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-provider-templates/{llmProviderTemplateId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieve the complete configuration for a specific LLM provider template. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderTemplateId|path|string|true|Unique handle of the template.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "openai", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "readOnly": false, + "version": "v1.0", + "isLatest": true, + "enabled": true, + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + }, + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|LLM provider template details|[LLMProviderTemplate](schemas.md#schemallmprovidertemplate)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update an existing LLM provider template + + + +`PUT /llm-provider-templates/{llmProviderTemplateId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/llm-provider-templates/{llmProviderTemplateId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Update the configuration of an existing LLM provider template. The template +is identified by its unique handle (id). This operation updates the latest +version of the template. + +> Payload + +```json +{ + "id": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "version": "v1.0", + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + } +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderTemplateId|path|string|true|Unique identifier of the template to update| +|body|body|[LLMProviderTemplate](schemas.md#schemallmprovidertemplate)|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "id": "openai", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "readOnly": false, + "version": "v1.0", + "isLatest": true, + "enabled": true, + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + }, + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|LLM provider template updated successfully|[LLMProviderTemplate](schemas.md#schemallmprovidertemplate)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Enable or disable a template version + + + +`PATCH /llm-provider-templates/{llmProviderTemplateId}` + +> Code samples + +```shell + +curl -X PATCH https://localhost:9243/api/v0.9/llm-provider-templates/{llmProviderTemplateId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Enable or disable the single template version identified by its handle +(id). Only built-in templates can be toggled; disabling a version in use +by a provider is rejected (409). + +> Payload + +```json +{ + "enabled": true +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderTemplateId|path|string|true|Unique handle of the template version.| +|body|body|object|true|none| +|» enabled|body|boolean|true|Set to true to enable this template version, false to disable it.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "openai", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "readOnly": false, + "version": "v1.0", + "isLatest": true, + "enabled": true, + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + }, + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Updated template version|[LLMProviderTemplate](schemas.md#schemallmprovidertemplate)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete a template version + + + +`DELETE /llm-provider-templates/{llmProviderTemplateId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/llm-provider-templates/{llmProviderTemplateId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Delete the single template version identified by its handle (id). +Built-in versions are read-only (403); a version still in use by a +provider is blocked (409). + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderTemplateId|path|string|true|Unique handle of the template version.| + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Version deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/llm-providers.md b/docs/rest-apis/platform-api/llm-providers.md new file mode 100644 index 0000000000..53cd79a143 --- /dev/null +++ b/docs/rest-apis/platform-api/llm-providers.md @@ -0,0 +1,2008 @@ +

LLM Providers

+ +LLM provider management operations + +## Create a new LLM provider + + + +`POST /llm-providers` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-providers \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Deploy a new LLM provider configuration. + +> Payload + +```json +{ + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "version": "v1.0", + "context": "/openai", + "vhost": "api.openai.com", + "template": "openai", + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "modelProviders": [ + { + "id": "claude", + "displayName": "Claude", + "models": [ + { + "id": "claude-3-5-sonnet", + "displayName": "Claude 3.5 Sonnet", + "description": "High-quality reasoning model" + } + ] + } + ], + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "accessControl": { + "mode": "deny_all", + "exceptions": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ] + } + ] + }, + "rateLimiting": { + "providerLevel": { + "global": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + "consumerLevel": { + "resourceWise": { + "default": { + "request": { + "enabled": true, + "count": 50, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 100, + "reset": { + "duration": 1, + "unit": "month" + } + } + }, + "resources": [ + { + "resource": "/models", + "limit": { + "request": { + "enabled": true, + "count": 200, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 100000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 25, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 10, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + } + ] + } + } + }, + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu", + "configurations": { + "host": "prod-eu.platform-gw.local" + } + }, + { + "id": "prod-us", + "configurations": { + "host": "prod-us.platform-gw.local" + } + } + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[LLMProvider](schemas.md#schemallmprovider)|true|none| + +> Example responses +> +> 201 Response + +```json +{ + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "context": "/openai", + "vhost": "api.openai.com", + "template": "openai", + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "modelProviders": [ + { + "id": "claude", + "displayName": "Claude", + "models": [ + { + "id": "claude-3-5-sonnet", + "displayName": "Claude 3.5 Sonnet", + "description": "High-quality reasoning model" + } + ] + } + ], + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "accessControl": { + "mode": "deny_all", + "exceptions": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ] + } + ] + }, + "rateLimiting": { + "providerLevel": { + "global": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + "consumerLevel": { + "resourceWise": { + "default": { + "request": { + "enabled": true, + "count": 50, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 100, + "reset": { + "duration": 1, + "unit": "month" + } + } + }, + "resources": [ + { + "resource": "/models", + "limit": { + "request": { + "enabled": true, + "count": 200, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 100000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 25, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 10, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + } + ] + } + } + }, + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu", + "configurations": { + "host": "prod-eu.platform-gw.local" + } + }, + { + "id": "prod-us", + "configurations": { + "host": "prod-us.platform-gw.local" + } + } + ], + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|LLM provider created successfully|[LLMProvider](schemas.md#schemallmprovider)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List all LLM providers + + + +`GET /llm-providers` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-providers \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieve a list of all LLM providers. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "createdBy": "john.doe", + "readOnly": false, + "version": "v1.0", + "template": "openai", + "status": "deployed", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of LLM providers|[LLMProviderListResponse](schemas.md#schemallmproviderlistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get LLM provider by identifier + + + +`GET /llm-providers/{llmProviderId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-providers/{llmProviderId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieve the complete configuration for a specific LLM provider. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| + +> Example responses +> +> 200 Response + +```json +{ + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "context": "/openai", + "vhost": "api.openai.com", + "template": "openai", + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "modelProviders": [ + { + "id": "claude", + "displayName": "Claude", + "models": [ + { + "id": "claude-3-5-sonnet", + "displayName": "Claude 3.5 Sonnet", + "description": "High-quality reasoning model" + } + ] + } + ], + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "accessControl": { + "mode": "deny_all", + "exceptions": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ] + } + ] + }, + "rateLimiting": { + "providerLevel": { + "global": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + "consumerLevel": { + "resourceWise": { + "default": { + "request": { + "enabled": true, + "count": 50, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 100, + "reset": { + "duration": 1, + "unit": "month" + } + } + }, + "resources": [ + { + "resource": "/models", + "limit": { + "request": { + "enabled": true, + "count": 200, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 100000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 25, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 10, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + } + ] + } + } + }, + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu", + "configurations": { + "host": "prod-eu.platform-gw.local" + } + }, + { + "id": "prod-us", + "configurations": { + "host": "prod-us.platform-gw.local" + } + } + ], + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|LLM provider details|[LLMProvider](schemas.md#schemallmprovider)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update an existing LLM provider + + + +`PUT /llm-providers/{llmProviderId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/llm-providers/{llmProviderId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Update the configuration of an existing LLM provider. + +> Payload + +```json +{ + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "version": "v1.0", + "context": "/openai", + "vhost": "api.openai.com", + "template": "openai", + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "modelProviders": [ + { + "id": "claude", + "displayName": "Claude", + "models": [ + { + "id": "claude-3-5-sonnet", + "displayName": "Claude 3.5 Sonnet", + "description": "High-quality reasoning model" + } + ] + } + ], + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "accessControl": { + "mode": "deny_all", + "exceptions": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ] + } + ] + }, + "rateLimiting": { + "providerLevel": { + "global": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + "consumerLevel": { + "resourceWise": { + "default": { + "request": { + "enabled": true, + "count": 50, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 100, + "reset": { + "duration": 1, + "unit": "month" + } + } + }, + "resources": [ + { + "resource": "/models", + "limit": { + "request": { + "enabled": true, + "count": 200, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 100000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 25, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 10, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + } + ] + } + } + }, + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu", + "configurations": { + "host": "prod-eu.platform-gw.local" + } + }, + { + "id": "prod-us", + "configurations": { + "host": "prod-us.platform-gw.local" + } + } + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|body|body|[LLMProvider](schemas.md#schemallmprovider)|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "context": "/openai", + "vhost": "api.openai.com", + "template": "openai", + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "modelProviders": [ + { + "id": "claude", + "displayName": "Claude", + "models": [ + { + "id": "claude-3-5-sonnet", + "displayName": "Claude 3.5 Sonnet", + "description": "High-quality reasoning model" + } + ] + } + ], + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "accessControl": { + "mode": "deny_all", + "exceptions": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ] + } + ] + }, + "rateLimiting": { + "providerLevel": { + "global": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + "consumerLevel": { + "resourceWise": { + "default": { + "request": { + "enabled": true, + "count": 50, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 100, + "reset": { + "duration": 1, + "unit": "month" + } + } + }, + "resources": [ + { + "resource": "/models", + "limit": { + "request": { + "enabled": true, + "count": 200, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 100000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 25, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 10, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + } + ] + } + } + }, + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu", + "configurations": { + "host": "prod-eu.platform-gw.local" + } + }, + { + "id": "prod-us", + "configurations": { + "host": "prod-us.platform-gw.local" + } + } + ], + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|LLM provider updated successfully|[LLMProvider](schemas.md#schemallmprovider)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete an LLM provider + + + +`DELETE /llm-providers/{llmProviderId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/llm-providers/{llmProviderId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Remove an LLM provider. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|LLM provider deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## List LLM proxies by provider + + + +`GET /llm-providers/{llmProviderId}/llm-proxies` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/llm-proxies \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieve a list of LLM proxies that use the specified LLM provider. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "createdBy": "john.doe", + "context": "/wso2-con-assistant", + "version": "v1.0", + "projectId": "default-project", + "provider": "wso2-openai-provider", + "status": "deployed", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of LLM proxies|[LLMProxyListResponse](schemas.md#schemallmproxylistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Create a new API key for an LLM provider + + + +`POST /llm-providers/{llmProviderId}/api-keys` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Generates a new API key for the specified LLM provider. The generated key +is broadcasted to all gateways in the organization and can be used to +authenticate requests to the LLM provider when API key validation is enabled. + +> Payload + +```json +{ + "id": "production-key", + "displayName": "Production Key", + "expiresAt": "2026-12-31T23:59:59Z", + "issuer": "api-platform-devportal", + "allowedTargets": "dev_gateway,test_gateway" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|body|body|[CreateLLMProviderAPIKeyRequest](schemas.md#schemacreatellmproviderapikeyrequest)|true|API key creation details| + +> Example responses +> +> 201 Response + +```json +{ + "status": "success", + "message": "API key created and broadcasted to gateways successfully", + "id": "production-key", + "apiKey": "REDACTED_API_KEY" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "GATEWAY_CONNECTION_UNAVAILABLE", + "message": "No gateway connections are currently available.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|API key created successfully|[CreateLLMProviderAPIKeyResponse](schemas.md#schemacreatellmproviderapikeyresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. No gateway connections are currently available to service this request.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List API keys for an LLM provider + + + +`GET /llm-providers/{llmProviderId}/api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns all API keys associated with the specified LLM provider. The plain key value is never returned. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "maskedApiKey": "string", + "status": "active", + "createdAt": "2019-08-24T14:15:22Z", + "createdBy": "john.doe", + "updatedAt": "2019-08-24T14:15:22Z", + "expiresAt": "2019-08-24T14:15:22Z", + "issuer": "api-platform-devportal", + "allowedTargets": "string" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API keys retrieved successfully|[LLMProviderAPIKeyListResponse](schemas.md#schemallmproviderapikeylistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete an API key for an LLM provider + + + +`DELETE /llm-providers/{llmProviderId}/api-keys/{apiKeyId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/llm-providers/{llmProviderId}/api-keys/{apiKeyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes the key from the database and broadcasts a revoke event to the allowed gateways. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProviderId|path|string|true|Unique identifier of the LLM provider| +|apiKeyId|path|string|true|Name of the API key to delete| + +> Example responses +> +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "GATEWAY_CONNECTION_UNAVAILABLE", + "message": "No gateway connections are currently available.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|API key deleted successfully|None| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. No gateway connections are currently available to service this request.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/llm-proxies.md b/docs/rest-apis/platform-api/llm-proxies.md new file mode 100644 index 0000000000..870bfd7d98 --- /dev/null +++ b/docs/rest-apis/platform-api/llm-proxies.md @@ -0,0 +1,1315 @@ +

LLM Proxies

+ +LLM proxy management operations + +## Create a new LLM proxy + + + +`POST /llm-proxies` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-proxies \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Deploy a new LLM proxy configuration. + +> Payload + +```json +{ + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "version": "v1.0", + "projectId": "default-project", + "context": "/openai", + "vhost": "api.openai.com", + "provider": { + "id": "wso2-openai-provider", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "additionalProviders": [ + { + "id": "anthropic-provider", + "as": "anthropic-upstream", + "transformer": { + "type": "openai-to-anthropic", + "version": "v1", + "params": {} + } + } + ], + "openapi": "openapi: 3.0.3\ninfo:\n title: Proxy API\n version: v1.0\npaths: {}\n", + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[LLMProxy](schemas.md#schemallmproxy)|true|none| + +> Example responses +> +> 201 Response + +```json +{ + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "projectId": "default-project", + "context": "/openai", + "vhost": "api.openai.com", + "provider": { + "id": "wso2-openai-provider", + "auth": { + "type": "api-key", + "header": "X-API-Key" + } + }, + "additionalProviders": [ + { + "id": "anthropic-provider", + "as": "anthropic-upstream", + "transformer": { + "type": "openai-to-anthropic", + "version": "v1", + "params": {} + } + } + ], + "openapi": "openapi: 3.0.3\ninfo:\n title: Proxy API\n version: v1.0\npaths: {}\n", + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ], + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|LLM proxy created successfully|[LLMProxy](schemas.md#schemallmproxy)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List all LLM proxies + + + +`GET /llm-proxies` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-proxies?projectId=default-project \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieve a list of all LLM proxies for a project. Requires the projectId query parameter. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|projectId|query|string|true|**Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**projectId**: **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "createdBy": "john.doe", + "context": "/wso2-con-assistant", + "version": "v1.0", + "projectId": "default-project", + "provider": "wso2-openai-provider", + "status": "deployed", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of LLM proxies|[LLMProxyListResponse](schemas.md#schemallmproxylistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get LLM proxy by unique identifier + + + +`GET /llm-proxies/{llmProxyId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieve the complete configuration for a specific LLM proxy. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| + +> Example responses +> +> 200 Response + +```json +{ + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "projectId": "default-project", + "context": "/openai", + "vhost": "api.openai.com", + "provider": { + "id": "wso2-openai-provider", + "auth": { + "type": "api-key", + "header": "X-API-Key" + } + }, + "additionalProviders": [ + { + "id": "anthropic-provider", + "as": "anthropic-upstream", + "transformer": { + "type": "openai-to-anthropic", + "version": "v1", + "params": {} + } + } + ], + "openapi": "openapi: 3.0.3\ninfo:\n title: Proxy API\n version: v1.0\npaths: {}\n", + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ], + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|LLM proxy details|[LLMProxy](schemas.md#schemallmproxy)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update an existing LLM proxy + + + +`PUT /llm-proxies/{llmProxyId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Update the configuration of an existing LLM proxy. + +> Payload + +```json +{ + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "version": "v1.0", + "projectId": "default-project", + "context": "/openai", + "vhost": "api.openai.com", + "provider": { + "id": "wso2-openai-provider", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "additionalProviders": [ + { + "id": "anthropic-provider", + "as": "anthropic-upstream", + "transformer": { + "type": "openai-to-anthropic", + "version": "v1", + "params": {} + } + } + ], + "openapi": "openapi: 3.0.3\ninfo:\n title: Proxy API\n version: v1.0\npaths: {}\n", + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|body|body|[LLMProxy](schemas.md#schemallmproxy)|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "projectId": "default-project", + "context": "/openai", + "vhost": "api.openai.com", + "provider": { + "id": "wso2-openai-provider", + "auth": { + "type": "api-key", + "header": "X-API-Key" + } + }, + "additionalProviders": [ + { + "id": "anthropic-provider", + "as": "anthropic-upstream", + "transformer": { + "type": "openai-to-anthropic", + "version": "v1", + "params": {} + } + } + ], + "openapi": "openapi: 3.0.3\ninfo:\n title: Proxy API\n version: v1.0\npaths: {}\n", + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ], + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|LLM proxy updated successfully|[LLMProxy](schemas.md#schemallmproxy)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete an LLM proxy + + + +`DELETE /llm-proxies/{llmProxyId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Remove an LLM proxy. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|LLM proxy deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Create a new API key for an LLM proxy + + + +`POST /llm-proxies/{llmProxyId}/api-keys` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Generates a new API key for the specified LLM proxy. The generated key +is broadcasted to all gateways in the organization and can be used to +authenticate requests to the LLM proxy when API key validation is enabled. + +> Payload + +```json +{ + "id": "production-key", + "displayName": "Production Key", + "expiresAt": "2026-12-31T23:59:59Z", + "issuer": "api-platform-devportal", + "allowedTargets": "dev_gateway,test_gateway" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|body|body|[CreateLLMProxyAPIKeyRequest](schemas.md#schemacreatellmproxyapikeyrequest)|true|API key creation details| + +> Example responses +> +> 201 Response + +```json +{ + "status": "success", + "message": "API key created and broadcasted to gateways successfully", + "id": "production-key", + "apiKey": "REDACTED_API_KEY" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "GATEWAY_CONNECTION_UNAVAILABLE", + "message": "No gateway connections are currently available.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|API key created successfully|[CreateLLMProxyAPIKeyResponse](schemas.md#schemacreatellmproxyapikeyresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. No gateway connections are currently available to service this request.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List API keys for an LLM proxy + + + +`GET /llm-proxies/{llmProxyId}/api-keys` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns all API keys associated with the specified LLM proxy. The plain key value is never returned. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "maskedApiKey": "string", + "status": "active", + "createdAt": "2019-08-24T14:15:22Z", + "createdBy": "john.doe", + "updatedAt": "2019-08-24T14:15:22Z", + "expiresAt": "2019-08-24T14:15:22Z", + "issuer": "api-platform-devportal", + "allowedTargets": "string" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of API keys retrieved successfully|[LLMProxyAPIKeyListResponse](schemas.md#schemallmproxyapikeylistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete an API key for an LLM proxy + + + +`DELETE /llm-proxies/{llmProxyId}/api-keys/{apiKeyId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/api-keys/{apiKeyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes the key from the database and broadcasts a revoke event to the allowed gateways. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|apiKeyId|path|string|true|Name of the API key to delete| + +> Example responses +> +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "GATEWAY_CONNECTION_UNAVAILABLE", + "message": "No gateway connections are currently available.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|API key deleted successfully|None| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. No gateway connections are currently available to service this request.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/llm-proxy-deployments.md b/docs/rest-apis/platform-api/llm-proxy-deployments.md new file mode 100644 index 0000000000..57071e004e --- /dev/null +++ b/docs/rest-apis/platform-api/llm-proxy-deployments.md @@ -0,0 +1,764 @@ +

LLM Proxy Deployments

+ +LLM proxy deployment operations + +## Create and deploy a new LLM proxy deployment + + + +`POST /llm-proxies/{llmProxyId}/deployments` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/deployments \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates an immutable deployment artifact for an LLM proxy and deploys it to a specified gateway. +Each deployment targets a single gateway. The proxyId parameter is the LLM proxy handle (identifier), +not the UUID. The operation returns a transitional DEPLOYING status. Final success or failure will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. +Access is validated against the organization in the JWT token. + +> Payload + +```json +{ + "name": "v1.0-production", + "base": "current", + "gatewayId": "prod-gateway-01", + "metadata": {} +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|body|body|[DeployRequest](schemas.md#schemadeployrequest)|true|Deployment request with gateway ID, base reference, and metadata| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|LLM proxy deployed successfully|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Get deployments for an LLM proxy + + + +`GET /llm-proxies/{llmProxyId}/deployments` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/deployments \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves all deployment artifacts for a specific LLM proxy. The proxyId parameter is the +LLM proxy handle (identifier), not the UUID. Supports filtering by gateway handle and deployment status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|gatewayId|query|string|false|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by.| +|status|query|string|false|Filter deployments by status (DEPLOYED, UNDEPLOYED, DEPLOYING, UNDEPLOYING, FAILED, or ARCHIVED)| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by. + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|status|DEPLOYED| +|status|UNDEPLOYED| +|status|DEPLOYING| +|status|UNDEPLOYING| +|status|FAILED| +|status|ARCHIVED| + +> Example responses +> +> 200 Response + +```json +{ + "count": 0, + "list": [ + { + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Deployments retrieved successfully|[DeploymentListResponse](schemas.md#schemadeploymentlistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get LLM proxy deployment by ID + + + +`GET /llm-proxies/{llmProxyId}/deployments/{deploymentId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/deployments/{deploymentId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves metadata for a specific LLM proxy deployment artifact including status, gateway association, +and timestamps. Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|deploymentId|path|string(uuid)|true|The UUID of the deployment| + +> Example responses +> +> 200 Response + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Deployment metadata retrieved successfully|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete LLM proxy deployment + + + +`DELETE /llm-proxies/{llmProxyId}/deployments/{deploymentId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/deployments/{deploymentId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a deployment artifact. Deletion is only allowed when the deployment is in UNDEPLOYED status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|deploymentId|path|string(uuid)|true|The UUID of the deployment| + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "DEPLOYMENT_ACTIVE", + "message": "Cannot delete an active deployment - undeploy it first." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Deployment deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The deployment is still active and must be undeployed before deletion.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Undeploy LLM proxy deployment from gateway + + + +`POST /llm-proxies/{llmProxyId}/deployments/{deploymentId}/undeploy` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/deployments/{deploymentId}/undeploy?gatewayId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Undeploys an active LLM proxy deployment, stopping it from being served on the specified gateway. +The deployment artifact remains in the system and can be restored later. +Returns the updated deployment object with initial status UNDEPLOYING. Final status (UNDEPLOYED or FAILED) will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. + +The gatewayId query parameter is validated against the deployment's bound gateway to prevent unintended operations. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|deploymentId|path|string|true|UUID of the deployment to undeploy| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway)| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes UNDEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "UNDEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Undeploy initiated successfully. Returns the deployment with initial status UNDEPLOYING. Poll status for final result.|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Restore a previous LLM proxy deployment + + + +`POST /llm-proxies/{llmProxyId}/deployments/{deploymentId}/restore` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/llm-proxies/{llmProxyId}/deployments/{deploymentId}/restore?gatewayId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Initiates restoring a previous deployment (ARCHIVED or UNDEPLOYED) on the specified gateway. +Returns the deployment with initial status DEPLOYING. Final success or failure will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. +The target deployment must not already be in DEPLOYED status. + +The gatewayId query parameter is validated against the deployment's bound gateway to prevent unintended operations. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|llmProxyId|path|string|true|Unique identifier of the LLM proxy| +|deploymentId|path|string|true|UUID of the deployment to restore (must be ARCHIVED or UNDEPLOYED)| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway)| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Restore initiated successfully. Returns the deployment with initial status DEPLOYING. Poll status for final result.|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/mcp-proxies.md b/docs/rest-apis/platform-api/mcp-proxies.md new file mode 100644 index 0000000000..09fd41bcf0 --- /dev/null +++ b/docs/rest-apis/platform-api/mcp-proxies.md @@ -0,0 +1,958 @@ +

MCP Proxies

+ +MCP proxy management operations + +## Create a new MCP proxy + + + +`POST /mcp-proxies` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/mcp-proxies \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Deploy a new MCP proxy configuration. + +> Payload + +```json +{ + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "version": "v1.0", + "projectId": "default-project", + "context": "/", + "vhost": "mcp.gw.com", + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "mcpSpecVersion": "2025-06-18", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "kind": "Mcp", + "capabilities": { + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[MCPProxy](schemas.md#schemamcpproxy)|true|none| + +> Example responses +> +> 201 Response + +```json +{ + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "projectId": "default-project", + "context": "/", + "vhost": "mcp.gw.com", + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "mcpSpecVersion": "2025-06-18", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "kind": "Mcp", + "capabilities": { + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ], + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|MCP proxy created successfully|[MCPProxy](schemas.md#schemamcpproxy)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List all MCP proxies + + + +`GET /mcp-proxies` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/mcp-proxies?projectId=default-project \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieve a list of all MCP proxies for a project. Requires the projectId query parameter. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|projectId|query|string|true|**Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**projectId**: **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "createdBy": "john.doe", + "context": "/weather-mcp-proxy", + "version": "v1.0", + "projectId": "default-project", + "status": "deployed", + "mcpSpecVersion": "2025-11-25", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of MCP proxies|[MCPProxyListResponse](schemas.md#schemamcpproxylistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get MCP proxy by unique identifier + + + +`GET /mcp-proxies/{mcpProxyId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieve the complete configuration for a specific MCP proxy. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| + +> Example responses +> +> 200 Response + +```json +{ + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "projectId": "default-project", + "context": "/", + "vhost": "mcp.gw.com", + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "mcpSpecVersion": "2025-06-18", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "kind": "Mcp", + "capabilities": { + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ], + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|MCP proxy details|[MCPProxy](schemas.md#schemamcpproxy)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update an existing MCP proxy + + + +`PUT /mcp-proxies/{mcpProxyId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Update the configuration of an existing MCP proxy. + +> Payload + +```json +{ + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "version": "v1.0", + "projectId": "default-project", + "context": "/", + "vhost": "mcp.gw.com", + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "mcpSpecVersion": "2025-06-18", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "kind": "Mcp", + "capabilities": { + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| +|body|body|[MCPProxy](schemas.md#schemamcpproxy)|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "projectId": "default-project", + "context": "/", + "vhost": "mcp.gw.com", + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "mcpSpecVersion": "2025-06-18", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "kind": "Mcp", + "capabilities": { + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ], + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|MCP proxy updated successfully|[MCPProxy](schemas.md#schemamcpproxy)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete an MCP proxy + + + +`DELETE /mcp-proxies/{mcpProxyId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Remove an MCP proxy. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|MCP proxy deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Fetch server info from MCP proxy backend services + + + +`POST /mcp-proxies/fetch-server-info` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/mcp-proxies/fetch-server-info \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Fetches server information from the backend services of an MCP proxy. +This is used to validate connectivity and retrieve metadata about the backend services. + +> Payload + +```json +{ + "url": "https://mcp.server.com/mcp", + "proxyId": "my-mcp-proxy", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[MCPServerInfoFetchRequest](schemas.md#schemamcpserverinfofetchrequest)|true|Target MCP server to introspect — either a direct `url` (with optional `auth`), or a `proxyId` to refetch using a stored proxy configuration.| + +> Example responses +> +> 200 Response + +```json +{ + "serverInfo": {}, + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Server info retrieved successfully|[MCPServerInfoFetchResponse](schemas.md#schemamcpserverinfofetchresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/mcp-proxy-deployments.md b/docs/rest-apis/platform-api/mcp-proxy-deployments.md new file mode 100644 index 0000000000..4043292cf4 --- /dev/null +++ b/docs/rest-apis/platform-api/mcp-proxy-deployments.md @@ -0,0 +1,720 @@ +

MCP Proxy Deployments

+ +MCP proxy deployment operations + +## Create and deploy a new deployment for MCP proxy + + + +`POST /mcp-proxies/{mcpProxyId}/deployments` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId}/deployments \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates an immutable deployment artifact for an MCP proxy and deploys it to a specified gateway. +Each deployment targets a single gateway. The id parameter is the MCP proxy handle (identifier), +not the UUID. The operation returns a transitional DEPLOYING status. Final success or failure will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. +Access is validated against the organization in the JWT token. + +> Payload + +```json +{ + "name": "v1.0-production", + "base": "current", + "gatewayId": "prod-gateway-01", + "metadata": {} +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| +|body|body|[DeployRequest](schemas.md#schemadeployrequest)|true|Deployment request with gateway ID, base reference, and metadata| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|MCP proxy deployed successfully|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Get deployments for an MCP proxy + + + +`GET /mcp-proxies/{mcpProxyId}/deployments` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId}/deployments \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves all deployment artifacts for a specific MCP proxy. The id parameter is the MCP proxy handle (identifier), +not the UUID. Supports filtering by gateway handle and deployment status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| +|gatewayId|query|string|false|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by.| +|status|query|string|false|Filter deployments by status (DEPLOYED, UNDEPLOYED, DEPLOYING, UNDEPLOYING, FAILED, or ARCHIVED)| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by. + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|status|DEPLOYED| +|status|UNDEPLOYED| +|status|DEPLOYING| +|status|UNDEPLOYING| +|status|FAILED| +|status|ARCHIVED| + +> Example responses +> +> 200 Response + +```json +{ + "count": 0, + "list": [ + { + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Deployments retrieved successfully|[DeploymentListResponse](schemas.md#schemadeploymentlistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get deployment by ID + + + +`GET /mcp-proxies/{mcpProxyId}/deployments/{deploymentId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId}/deployments/{deploymentId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves metadata for a specific deployment artifact including status, gateway association, +and timestamps. Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| +|deploymentId|path|string(uuid)|true|The UUID of the deployment| + +> Example responses +> +> 200 Response + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Deployment metadata retrieved successfully|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete deployment + + + +`DELETE /mcp-proxies/{mcpProxyId}/deployments/{deploymentId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId}/deployments/{deploymentId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a deployment artifact. Deletion is only allowed when the deployment is in UNDEPLOYED status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| +|deploymentId|path|string(uuid)|true|The UUID of the deployment| + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "DEPLOYMENT_ACTIVE", + "message": "Cannot delete an active deployment - undeploy it first." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Deployment deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The deployment is still active and must be undeployed before deletion.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Undeploy deployment from gateway + + + +`POST /mcp-proxies/{mcpProxyId}/deployments/{deploymentId}/undeploy` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId}/deployments/{deploymentId}/undeploy?gatewayId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Undeploys an active deployment, stopping the MCP proxy from being served on the specified gateway. +The deployment artifact remains in the system and can be restored later. +Returns the updated deployment object with initial status UNDEPLOYING. Final status (UNDEPLOYED or FAILED) will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. + +The gatewayId query parameter is validated against the deployment's bound gateway to prevent unintended operations. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| +|deploymentId|path|string|true|UUID of the deployment to undeploy| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway)| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes UNDEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "UNDEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Undeploy initiated successfully. Returns the deployment with initial status UNDEPLOYING. Poll status for final result.|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Restore a previous deployment + + + +`POST /mcp-proxies/{mcpProxyId}/deployments/{deploymentId}/restore` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/mcp-proxies/{mcpProxyId}/deployments/{deploymentId}/restore?gatewayId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Initiates restoring a previous deployment (ARCHIVED or UNDEPLOYED) on the specified gateway. +Returns the deployment with initial status DEPLOYING. Final success or failure will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. +The target deployment must not already be in DEPLOYED status. + +The gatewayId query parameter is validated against the deployment's bound gateway to prevent unintended operations. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|mcpProxyId|path|string|true|Unique identifier of the MCP proxy| +|deploymentId|path|string|true|UUID of the deployment to restore (must be ARCHIVED or UNDEPLOYED)| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway)| + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Restore initiated successfully. Returns the deployment with initial status DEPLOYING. Poll status for final result.|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/organizations.md b/docs/rest-apis/platform-api/organizations.md new file mode 100644 index 0000000000..71e172c723 --- /dev/null +++ b/docs/rest-apis/platform-api/organizations.md @@ -0,0 +1,372 @@ +

Organizations

+ +Organization management operations + +## Register a new organization + + + +`POST /organizations` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/organizations \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Registers a new organization with a unique UUID and handle. +This endpoint is used during the organization onboarding process. +A valid Bearer JWT token with the `ap:organization:create` or `ap:organization:manage` scope is required. + +> Payload + +```json +{ + "displayName": "Acme Corporation", + "region": "us" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[Organization](schemas.md#schemaorganization)|true|Organization that needs to be added| + +> Example responses +> +> 201 Response + +```json +{ + "id": "acme", + "displayName": "Acme Corporation", + "region": "us", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Organization registered successfully|[Organization](schemas.md#schemaorganization)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List organizations + + + +`GET /organizations` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/organizations \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves the list of organizations. Results are returned in a paginated +collection envelope. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "acme", + "displayName": "Acme Corporation", + "region": "us", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Organizations retrieved successfully|[OrganizationListResponse](schemas.md#schemaorganizationlistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get organization by ID + + + +`GET /organizations/{organizationId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/organizations/{organizationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns the organization with the specified handle (id). + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|organizationId|path|string|true|ID of the organization.| + +> Example responses +> +> 200 Response + +```json +{ + "id": "acme", + "displayName": "Acme Corporation", + "region": "us", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Organization retrieved successfully|[Organization](schemas.md#schemaorganization)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Check if organization exists + + + +`HEAD /organizations/{organizationId}` + +> Code samples + +```shell + +curl -X HEAD https://localhost:9243/api/v0.9/organizations/{organizationId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Checks if an organization with the specified handle (id) exists. +Returns only headers without a response body. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|organizationId|path|string|true|ID of the organization.| + +> Example responses +> +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Organization exists|None| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/projects.md b/docs/rest-apis/platform-api/projects.md new file mode 100644 index 0000000000..9165633945 --- /dev/null +++ b/docs/rest-apis/platform-api/projects.md @@ -0,0 +1,620 @@ +

Projects

+ +Project management operations + +## Create a new project + + + +`POST /projects` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/projects \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a new project within the organization specified in the JWT token. +Organization ID is automatically extracted from the token and does not need to be provided. + +> Payload + +```json +{ + "id": "default-project", + "displayName": "Default Project", + "description": "This is the default project for development" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[CreateProjectRequest](schemas.md#schemacreateprojectrequest)|true|none| + +> Example responses +> +> 201 Response + +```json +{ + "id": "default-project", + "displayName": "Default Project", + "description": "This is the default project for development", + "organizationId": "acme", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Project created successfully|[Project](schemas.md#schemaproject)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Get all projects for current user's organization + + + +`GET /projects` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/projects \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves all projects belonging to the organization specified in the JWT token. +Organization ID is automatically extracted from the token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| +|sortBy|query|string|false|Field to sort the collection by. An unrecognized value falls back to the default sort (createdAt).| +|sortOrder|query|string|false|Sort direction applied to `sortBy`.| +|query|query|string|false|Case-insensitive substring filter matched against the resource id (handle).| + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|sortBy|name| +|sortBy|createdAt| +|sortOrder|asc| +|sortOrder|desc| + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "default-project", + "displayName": "Default Project", + "description": "This is the default project for development", + "organizationId": "acme", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Projects retrieved successfully|[ProjectListResponse](schemas.md#schemaprojectlistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get project by ID + + + +`GET /projects/{projectId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/projects/{projectId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a specific project by its ID (handle). Access is validated against the organization +in the JWT token to ensure users can only access projects in their organization. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|projectId|path|string|true|**Project ID** consisting of the **handle** (unique slug identifier) of the Project.| + +#### Detailed descriptions + +**projectId**: **Project ID** consisting of the **handle** (unique slug identifier) of the Project. + +> Example responses +> +> 200 Response + +```json +{ + "id": "default-project", + "displayName": "Default Project", + "description": "This is the default project for development", + "organizationId": "acme", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Project retrieved successfully|[Project](schemas.md#schemaproject)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update project + + + +`PUT /projects/{projectId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/projects/{projectId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an existing project's name. Access is validated against the organization +in the JWT token. + +> Payload + +```json +{ + "displayName": "Default Project", + "description": "This is the default project for development" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|projectId|path|string|true|**Project ID** consisting of the **handle** (unique slug identifier) of the Project.| +|body|body|[Project](schemas.md#schemaproject)|true|none| + +#### Detailed descriptions + +**projectId**: **Project ID** consisting of the **handle** (unique slug identifier) of the Project. + +> Example responses +> +> 200 Response + +```json +{ + "id": "default-project", + "displayName": "Default Project", + "description": "This is the default project for development", + "organizationId": "acme", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Project updated successfully|[Project](schemas.md#schemaproject)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete project + + + +`DELETE /projects/{projectId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/projects/{projectId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a specific project by its handle (unique slug identifier). Access is validated +against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|projectId|path|string|true|**Project ID** consisting of the **handle** (unique slug identifier) of the Project.| + +#### Detailed descriptions + +**projectId**: **Project ID** consisting of the **handle** (unique slug identifier) of the Project. + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Project deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/rest-api-deployments.md b/docs/rest-apis/platform-api/rest-api-deployments.md new file mode 100644 index 0000000000..685aa8a548 --- /dev/null +++ b/docs/rest-apis/platform-api/rest-api-deployments.md @@ -0,0 +1,786 @@ +

REST API Deployments

+ +API deployment artifact management and lifecycle operations + +## Create and deploy a new deployment + + + +`POST /rest-apis/{restApiId}/deployments` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/rest-apis/{restApiId}/deployments \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates an immutable deployment artifact for an API and deploys it to a specified gateway. +Each deployment targets a single gateway. The apiId parameter is the API handle (identifier), +not the UUID. The operation returns a transitional DEPLOYING status. Final success or failure will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. +Access is validated against the organization in the JWT token. + +> Payload + +```json +{ + "name": "v1.0-production", + "base": "current", + "gatewayId": "prod-gateway-01", + "metadata": {} +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|body|body|[DeployRequest](schemas.md#schemadeployrequest)|true|Deployment request with gateway ID, base reference, and metadata| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|API deployed successfully|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Get deployments for a REST API + + + +`GET /rest-apis/{restApiId}/deployments` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/rest-apis/{restApiId}/deployments \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves all deployment artifacts for a specific API. The apiId parameter is the API handle (identifier), +not the UUID. Supports filtering by gateway handle and deployment status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|gatewayId|query|string|false|**Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by.| +|status|query|string|false|Filter deployments by status (DEPLOYED, UNDEPLOYED, DEPLOYING, UNDEPLOYING, FAILED, or ARCHIVED)| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +**gatewayId**: **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by. + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|status|DEPLOYED| +|status|UNDEPLOYED| +|status|DEPLOYING| +|status|UNDEPLOYING| +|status|FAILED| +|status|ARCHIVED| + +> Example responses +> +> 200 Response + +```json +{ + "count": 0, + "list": [ + { + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Deployments retrieved successfully|[DeploymentListResponse](schemas.md#schemadeploymentlistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get deployment by ID + + + +`GET /rest-apis/{restApiId}/deployments/{deploymentId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/rest-apis/{restApiId}/deployments/{deploymentId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves metadata for a specific deployment artifact including status, gateway association, +and timestamps. Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|deploymentId|path|string(uuid)|true|The UUID of the deployment| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 200 Response + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Deployment metadata retrieved successfully|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete deployment + + + +`DELETE /rest-apis/{restApiId}/deployments/{deploymentId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/rest-apis/{restApiId}/deployments/{deploymentId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a deployment artifact. Deletion is only allowed when the deployment is in UNDEPLOYED status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|deploymentId|path|string(uuid)|true|The UUID of the deployment| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "DEPLOYMENT_ACTIVE", + "message": "Cannot delete an active deployment - undeploy it first." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Deployment deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The deployment is still active and must be undeployed before deletion.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Undeploy deployment from gateway + + + +`POST /rest-apis/{restApiId}/deployments/{deploymentId}/undeploy` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/rest-apis/{restApiId}/deployments/{deploymentId}/undeploy?gatewayId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Undeploys an active deployment, stopping the API from being served on the specified gateway. +The deployment artifact remains in the system and can be restored later. +Returns the updated deployment object with initial status UNDEPLOYING. Final status (UNDEPLOYED or FAILED) will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. + +The gatewayId query parameter is validated against deployment's bound gateway to prevent unintended operations. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|deploymentId|path|string|true|UUID of the deployment to undeploy| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway)| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes UNDEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "UNDEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Undeploy initiated successfully. Returns the deployment with initial status UNDEPLOYING. Poll status for final result.|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Restore a previous deployment + + + +`POST /rest-apis/{restApiId}/deployments/{deploymentId}/restore` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/rest-apis/{restApiId}/deployments/{deploymentId}/restore?gatewayId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Initiates restoring a previous deployment (ARCHIVED or UNDEPLOYED) on the specified gateway. +Returns the deployment with initial status DEPLOYING. Final success or failure will be reported asynchronously via the deployment's status and statusReason once the gateway acknowledges. +The target deployment must not already be in DEPLOYED status. + +The gatewayId query parameter is validated against the deployment's bound gateway to prevent unintended operations. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|deploymentId|path|string|true|UUID of the deployment to restore (must be ARCHIVED or UNDEPLOYED)| +|gatewayId|query|string|true|Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway)| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYING", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Restore initiated successfully. Returns the deployment with initial status DEPLOYING. Poll status for final result.|[DeploymentResponse](schemas.md#schemadeploymentresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/rest-apis.md b/docs/rest-apis/platform-api/rest-apis.md new file mode 100644 index 0000000000..9a69c34382 --- /dev/null +++ b/docs/rest-apis/platform-api/rest-apis.md @@ -0,0 +1,1900 @@ +

REST APIs

+ +API management operations + +## Get all REST APIs for an organization + + + +`GET /rest-apis` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/rest-apis?projectId=default-project \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves all REST APIs belonging to an organization. +Requires the projectId query parameter to filter APIs by project. +Provide name and version together to check uniqueness (empty result = name/version combination is available). +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|projectId|query|string|true|**Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| +|sortBy|query|string|false|Field to sort the collection by. An unrecognized value falls back to the default sort (createdAt).| +|sortOrder|query|string|false|Sort direction applied to `sortBy`.| +|query|query|string|false|Case-insensitive substring filter matched against the resource id (handle).| + +#### Detailed descriptions + +**projectId**: **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|sortBy|name| +|sortBy|createdAt| +|sortOrder|asc| +|sortOrder|desc| + +> Example responses +> +> 200 Response + +```json +{ + "count": 2, + "list": [ + { + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "projectId": "default-project", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z", + "readOnly": false, + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|REST APIs retrieved successfully|[RESTAPIListResponse](schemas.md#schemarestapilistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Create a new REST API + + + +`POST /rest-apis` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/rest-apis \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a new REST API in the platform. The API is associated with a project, which must +belong to the organization specified in the JWT token. + +> Payload + +```json +{ + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "projectId": "default-project", + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[CreateRESTAPIRequest](schemas.md#schemacreaterestapirequest)|true|API object that needs to be added| + +> Example responses +> +> 201 Response + +```json +{ + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "projectId": "default-project", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z", + "readOnly": false, + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|API created successfully|[RESTAPI](schemas.md#schemarestapi)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Get REST API by ID + + + +`GET /rest-apis/{restApiId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/rest-apis/{restApiId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves a specific API by its ID (handle). Access is validated against the organization +in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 200 Response + +```json +{ + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "projectId": "default-project", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z", + "readOnly": false, + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API retrieved successfully|[RESTAPI](schemas.md#schemarestapi)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update REST API + + + +`PUT /rest-apis/{restApiId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/rest-apis/{restApiId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an existing API's details. Access is validated against the organization +in the JWT token. + +> Payload + +```json +{ + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "projectId": "default-project", + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|body|body|[RESTAPI](schemas.md#schemarestapi)|true|API object that needs to be updated| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 200 Response + +```json +{ + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "projectId": "default-project", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z", + "readOnly": false, + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API updated successfully|[RESTAPI](schemas.md#schemarestapi)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete REST API + + + +`DELETE /rest-apis/{restApiId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/rest-apis/{restApiId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Deletes a specific API by its ID (handle). Access is validated against the organization +in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|API deleted successfully|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get gateways for REST API + + + +`GET /rest-apis/{restApiId}/gateways` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/rest-apis/{restApiId}/gateways \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Retrieves all gateways associated with the specified API, including deployment details. +Returns gateway information along with association timestamps and deployment status. +Access is validated against the organization in the JWT token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 200 Response + +```json +{ + "count": 3, + "list": [ + { + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z", + "associatedAt": "2025-10-15T10:30:00Z", + "isDeployed": true, + "deployment": { + "status": "CREATED", + "deployedAt": "2025-10-15T11:00:00Z" + } + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of gateways associated with the API, including deployment details|[RESTAPIGatewayListResponse](schemas.md#schemarestapigatewaylistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Add gateways for REST API + + + +`POST /rest-apis/{restApiId}/gateways` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/rest-apis/{restApiId}/gateways \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Associates gateways to the specified API. If gateways are already associated, +updates the association timestamp. Returns all gateways associated with the API +including deployment details. Access is validated against the organization +in the JWT token. + +> Payload + +```json +[ + { + "gatewayId": "prod-gateway-01" + } +] +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|body|body|[AddGatewayToRESTAPIRequest](schemas.md#schemaaddgatewaytorestapirequest)|false|List of gateways to associate with the API| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 200 Response + +```json +{ + "count": 3, + "list": [ + { + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z", + "associatedAt": "2025-10-15T10:30:00Z", + "isDeployed": true, + "deployment": { + "status": "CREATED", + "deployedAt": "2025-10-15T11:00:00Z" + } + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of all gateways associated with the API, including deployment details|[RESTAPIGatewayListResponse](schemas.md#schemarestapigatewaylistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Create API key + + + +`POST /rest-apis/{restApiId}/api-keys` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/rest-apis/{restApiId}/api-keys \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a new API key for the specified API. The API key will be hashed before +storage and broadcasted to all gateways where the API is deployed. This endpoint +allows external platforms to inject API keys to hybrid gateways. + +> Payload + +```json +{ + "id": "production-key-01", + "displayName": "Production API Key", + "apiKey": "sk_example_1234567890abcdef", + "externalRefId": "ext-ref-12345", + "expiresAt": "2026-12-31T23:59:59Z", + "expiresIn": { + "duration": 30, + "unit": "days" + }, + "issuer": "api-platform-devportal" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|body|body|[CreateAPIKeyRequest](schemas.md#schemacreateapikeyrequest)|true|API key creation request| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 201 Response + +```json +{ + "status": "success", + "message": "API key created and broadcasted to gateways successfully", + "keyId": "production-key-01" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "GATEWAY_CONNECTION_UNAVAILABLE", + "message": "No gateway connections are currently available.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|API key created successfully|[CreateAPIKeyResponse](schemas.md#schemacreateapikeyresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. No gateway connections are currently available to service this request.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## Update API key + + + +`PUT /rest-apis/{restApiId}/api-keys/{apiKeyId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/rest-apis/{restApiId}/api-keys/{apiKeyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates an existing API key for the specified API. The new API key value will +be hashed before storage and broadcasted to all gateways where the API is deployed. +This endpoint allows external platforms to rotate API keys on hybrid gateways. + +> Payload + +```json +{ + "name": "production-key-01", + "displayName": "Production API Key (Updated)", + "apiKey": "sk_example_new1234567890abcdef", + "externalRefId": "ext-ref-12345", + "expiresAt": "2027-12-31T23:59:59Z", + "expiresIn": { + "duration": 30, + "unit": "days" + }, + "issuer": "api-platform-devportal" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|apiKeyId|path|string|true|The unique name/identifier of the API key| +|body|body|[UpdateAPIKeyRequest](schemas.md#schemaupdateapikeyrequest)|true|API key update request| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 200 Response + +```json +{ + "status": "success", + "message": "API key updated and broadcasted to gateways successfully", + "keyId": "production-key-01" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "GATEWAY_CONNECTION_UNAVAILABLE", + "message": "No gateway connections are currently available.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|API key updated successfully|[UpdateAPIKeyResponse](schemas.md#schemaupdateapikeyresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. No gateway connections are currently available to service this request.|[Error](schemas.md#schemaerror)| + +## Revoke API key + + + +`DELETE /rest-apis/{restApiId}/api-keys/{apiKeyId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/rest-apis/{restApiId}/api-keys/{apiKeyId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Revokes an API key for the specified API. The revocation will be broadcasted +to all gateways where the API is deployed. This endpoint allows external platforms +to revoke API keys on hybrid gateways. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|restApiId|path|string|true|**API ID** consisting of the **handle** (unique identifier) of the API.| +|apiKeyId|path|string|true|The unique name/identifier of the API key to revoke| + +#### Detailed descriptions + +**restApiId**: **API ID** consisting of the **handle** (unique identifier) of the API. + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "GATEWAY_CONNECTION_UNAVAILABLE", + "message": "No gateway connections are currently available.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|API key revoked successfully (no content)|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. No gateway connections are currently available to service this request.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/schemas.md b/docs/rest-apis/platform-api/schemas.md new file mode 100644 index 0000000000..e6b8c72b32 --- /dev/null +++ b/docs/rest-apis/platform-api/schemas.md @@ -0,0 +1,5424 @@ +# Schemas + +

APIKeyItem

+ + + + + + +```json +{ + "id": "string", + "displayName": "string", + "maskedApiKey": "string", + "status": "active", + "createdAt": "2019-08-24T14:15:22Z", + "createdBy": "john.doe", + "updatedAt": "2019-08-24T14:15:22Z", + "expiresAt": "2019-08-24T14:15:22Z", + "issuer": "api-platform-devportal", + "allowedTargets": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|URL-safe handle (identifier) of the API key (generated from displayName when not supplied)| +|displayName|string|true|none|Human-readable display name of the API key| +|maskedApiKey|string|true|none|Masked representation of the API key for display purposes| +|status|string|true|none|Current status of the key| +|createdAt|string(date-time)|true|none|Timestamp when the key was created| +|createdBy|string|true|read-only|User identifier of the user who created this resource| +|updatedAt|string(date-time)|true|none|Timestamp when the key was last updated| +|expiresAt|string(date-time)|false|none|Optional expiration timestamp| +|issuer|string|false|none|Optional identifier of the developer portal that provisioned this key| +|allowedTargets|string|true|none|Comma-separated list of allowed gateways; 'ALL' means unrestricted| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|active| +|status|revoked| +|status|expired| + +

UserAPIKeyItem

+ + + + + + +```json +{ + "id": "string", + "displayName": "string", + "maskedApiKey": "string", + "status": "active", + "createdAt": "2019-08-24T14:15:22Z", + "createdBy": "john.doe", + "updatedAt": "2019-08-24T14:15:22Z", + "expiresAt": "2019-08-24T14:15:22Z", + "issuer": "api-platform-devportal", + "allowedTargets": "string", + "artifactId": "wso2-openai-provider", + "artifactType": "RestApi" +} + +``` + +### Properties + +allOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[APIKeyItem](#schemaapikeyitem)|false|none|none| + +and + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| +|» artifactId|string|true|none|Handle (ID) of the artifact this key belongs to| +|» artifactType|string|true|none|Type of the artifact this key belongs to| + +#### Enumerated Values + +|Property|Value| +|---|---| +|artifactType|RestApi| +|artifactType|LlmProvider| +|artifactType|LlmProxy| + +

UserAPIKeyListResponse

+ + + + + + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "maskedApiKey": "string", + "status": "active", + "createdAt": "2019-08-24T14:15:22Z", + "createdBy": "john.doe", + "updatedAt": "2019-08-24T14:15:22Z", + "expiresAt": "2019-08-24T14:15:22Z", + "issuer": "api-platform-devportal", + "allowedTargets": "string", + "artifactId": "wso2-openai-provider", + "artifactType": "RestApi" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|list|[[UserAPIKeyItem](#schemauserapikeyitem)]|true|none|List of API keys| +|count|integer|true|none|Number of API keys in current response| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

LLMProviderAPIKeyListResponse

+ + + + + + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "maskedApiKey": "string", + "status": "active", + "createdAt": "2019-08-24T14:15:22Z", + "createdBy": "john.doe", + "updatedAt": "2019-08-24T14:15:22Z", + "expiresAt": "2019-08-24T14:15:22Z", + "issuer": "api-platform-devportal", + "allowedTargets": "string" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|list|[[APIKeyItem](#schemaapikeyitem)]|true|none|List of API keys| +|count|integer|true|none|Number of API keys in current response| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

LLMProxyAPIKeyListResponse

+ + + + + + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "maskedApiKey": "string", + "status": "active", + "createdAt": "2019-08-24T14:15:22Z", + "createdBy": "john.doe", + "updatedAt": "2019-08-24T14:15:22Z", + "expiresAt": "2019-08-24T14:15:22Z", + "issuer": "api-platform-devportal", + "allowedTargets": "string" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|list|[[APIKeyItem](#schemaapikeyitem)]|true|none|List of API keys| +|count|integer|true|none|Number of API keys in current response| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

Organization

+ + + + + + +```json +{ + "id": "acme", + "displayName": "Acme Corporation", + "region": "us", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|read-only|Handle (URL-friendly slug) for the organization| +|displayName|string|true|none|Human-readable name for the organization| +|region|string|true|none|Geographic region where the organization operates| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource| +|createdAt|string(date-time)|false|read-only|Timestamp when the organization was created| +|updatedAt|string(date-time)|false|read-only|Timestamp when the organization was last updated| + +

CreateProjectRequest

+ + + + + + +```json +{ + "id": "default-project", + "displayName": "Default Project", + "description": "This is the default project for development" +} + +``` + +Request body for creating a project. Organization ID is automatically extracted +from the JWT token and does not need to be provided. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Handle (URL-friendly slug) for the project. Auto-generated from displayName if not provided.| +|displayName|string|true|none|Human-readable name for the project| +|description|string|false|none|Description of the project| + +

Project

+ + + + + + +```json +{ + "id": "default-project", + "displayName": "Default Project", + "description": "This is the default project for development", + "organizationId": "acme", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|read-only|Handle (URL-friendly slug) for the project| +|displayName|string|true|none|Human-readable name for the project| +|description|string|false|none|Description of the project| +|organizationId|string|true|read-only|Handle (URL-friendly slug) of the organization this project belongs to| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /projects/{projectId}), omitted from list responses.| +|createdAt|string(date-time)|false|read-only|Timestamp when the project was created| +|updatedAt|string(date-time)|false|read-only|Timestamp when the project was last updated| + +

ApplicationType

+ + + + + + +```json +"genai" + +``` + +Type of the application + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|string|false|none|Type of the application| + +#### Enumerated Values + +|Property|Value| +|---|---| +|*anonymous*|genai| + +

CreateApplicationRequest

+ + + + + + +```json +{ + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application" +} + +``` + +Request body for creating an application. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique handle/identifier for the application. Can be provided during creation or auto-generated.| +|displayName|string|true|none|Human-readable name for the application| +|projectId|string|true|none|Handle (URL-friendly slug) of the project this application belongs to.| +|type|[ApplicationType](#schemaapplicationtype)|true|none|Type of the application| +|description|string|false|none|Description of the application| + +

Application

+ + + + + + +```json +{ + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|Application handle/identifier| +|displayName|string|true|none|Human-readable name for the application| +|projectId|string|true|none|Handle (URL-friendly slug) of the project this application belongs to| +|type|[ApplicationType](#schemaapplicationtype)|true|none|Type of the application| +|description|string|false|none|Description of the application| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /applications/{appId}), omitted from list responses.| +|createdAt|string(date-time)|false|read-only|none| +|updatedAt|string(date-time)|false|read-only|none| + +

ApplicationListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "my-app-handle", + "displayName": "GenAI Demo App", + "projectId": "default-project", + "type": "genai", + "description": "Sample GenAI application", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of items in current response| +|list|[[Application](#schemaapplication)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

AddApplicationAPIKeysRequest

+ + + + + + +```json +{ + "apiKeys": [ + { + "keyId": "client-key-1", + "associatedEntity": { + "id": "pizza-api" + } + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|apiKeys|[[APIKeyMappingSelector](#schemaapikeymappingselector)]|true|none|List of API key selectors to add to the application mappings| + +

AddApplicationAssociationsRequest

+ + + + + + +```json +{ + "associations": [ + { + "id": "provider-handle", + "kind": "LlmProvider" + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|associations|[[ApplicationAssociationSelector](#schemaapplicationassociationselector)]|true|none|List of association selectors to add to the application| + +

ApplicationAssociationSelector

+ + + + + + +```json +{ + "id": "provider-handle", + "kind": "LlmProvider" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|ID or handle of the association target| +|kind|string|true|none|Type of the association target| + +#### Enumerated Values + +|Property|Value| +|---|---| +|kind|LlmProvider| +|kind|LlmProxy| + +

ApplicationAssociation

+ + + + + + +```json +{ + "id": "provider-handle", + "displayName": "OpenAI Provider", + "version": "v1.0", + "kind": "LlmProvider", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|Handle/ID of the associated target| +|displayName|string|true|none|Human-readable name for the associated target| +|version|string|true|none|Version of the associated target| +|kind|string|true|none|Type of associated target| +|createdAt|string(date-time)|false|none|Timestamp when the association was created| +|updatedAt|string(date-time)|false|none|Timestamp when the association was updated| + +

ApplicationAssociationListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "provider-handle", + "displayName": "OpenAI Provider", + "version": "v1.0", + "kind": "LlmProvider", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of items in current response| +|list|[[ApplicationAssociation](#schemaapplicationassociation)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

APIKeyMappingSelector

+ + + + + + +```json +{ + "keyId": "client-key-1", + "associatedEntity": { + "id": "pizza-api" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|keyId|string|true|none|API key name| +|associatedEntity|[APIKeyMappingAssociatedEntity](#schemaapikeymappingassociatedentity)|true|none|none| + +

APIKeyMappingAssociatedEntity

+ + + + + + +```json +{ + "id": "pizza-api" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|ID of the entity that owns the API key| + +

AssociatedEntity

+ + + + + + +```json +{ + "id": "pizza-api", + "kind": "RestApi" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|ID of the associated entity| +|kind|string|true|none|Type of the associated entity| + +

MappedAPIKey

+ + + + + + +```json +{ + "keyId": "client-key-1", + "associatedEntity": { + "id": "pizza-api", + "kind": "RestApi" + }, + "status": "ACTIVE", + "userId": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z", + "expiresAt": "2026-11-15T10:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|keyId|string|true|none|Name of the API key| +|associatedEntity|[AssociatedEntity](#schemaassociatedentity)|true|none|none| +|status|string|false|none|Status of the API key| +|userId|string|false|read-only|User identifier of the user who created this resource| +|createdAt|string(date-time)|false|none|Timestamp when the API key was created| +|updatedAt|string(date-time)|false|none|Timestamp when the API key was updated| +|expiresAt|string(date-time)|false|none|Expiration timestamp of the API key| + +

MappedAPIKeyListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "keyId": "client-key-1", + "associatedEntity": { + "id": "pizza-api", + "kind": "RestApi" + }, + "status": "ACTIVE", + "userId": "john.doe", + "createdAt": "2025-11-15T10:30:00Z", + "updatedAt": "2025-11-15T11:30:00Z", + "expiresAt": "2026-11-15T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of items in current response| +|list|[[MappedAPIKey](#schemamappedapikey)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

RESTAPI

+ + + + + + +```json +{ + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "projectId": "default-project", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z", + "readOnly": false, + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] +} + +``` + +API object + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique handle/identifier for the API. Can be provided during creation or auto-generated. On update (PUT), if provided must match the path parameter — returns 400 if they differ.| +|displayName|string|true|none|Human-readable name for the API| +|description|string|false|none|none| +|context|string|true|none|none| +|version|string|true|none|none| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /rest-apis/{apiId}), omitted from list responses.| +|projectId|string|true|none|Handle (URL-friendly slug) of the project this API belongs to| +|createdAt|string(date-time)|false|read-only|Timestamp when the api was created| +|updatedAt|string(date-time)|false|read-only|Timestamp when the api was last updated| +|readOnly|boolean|false|read-only|True if the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane; false for control-plane created artifacts.| +|upstream|[Upstream](#schemaupstream)|true|none|Upstream backend configuration with main and sandbox endpoints| +|lifeCycleStatus|string|false|none|Current lifecycle status of the API| +|kind|string|false|none|Kind of the API based on its communication protocol or architectural style| +|transport|[string]|false|none|Supported transports for the API (http and/or https)| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies to be applied on the API| +|operations|[[Operation](#schemaoperation)]|false|none|List of operations exposed by this API| +|channels|[[Channel](#schemachannel)]|false|none|List of channels exposed by this API| +|subscriptionPlans|[string]|false|none|List of subscription plan names enabled for this API (e.g. Gold, Silver).
When set, only these plans can be used when subscribing to the API.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|lifeCycleStatus|STAGED| +|lifeCycleStatus|CREATED| +|lifeCycleStatus|PUBLISHED| +|lifeCycleStatus|DEPRECATED| +|lifeCycleStatus|RETIRED| +|lifeCycleStatus|BLOCKED| + +

SecurityConfig

+ + + + + + +```json +{ + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } +} + +``` + +Security Configuration + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|enabled|boolean|false|none|Whether security is enabled| +|apiKey|[APIKeySecurity](#schemaapikeysecurity)|false|none|Configuration for API key based authentication| + +

APIKeySecurity

+ + + + + + +```json +{ + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" +} + +``` + +API Key Security + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|enabled|boolean|false|none|Whether API key authentication is enabled| +|key|string|false|none|Name of the header or query parameter to be used for the API key| +|valuePrefix|string|false|none|Optional prefix to strip from the inbound API key value before validation, for example "Bearer"| +|in|string|false|none|Location of the API key (header or query)| + +#### Enumerated Values + +|Property|Value| +|---|---| +|in|header| +|in|query| + +

Operation

+ + + + + + +```json +{ + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } +} + +``` + +API Operation + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|false|none|Name of the operation| +|description|string|false|none|Description of the operation| +|request|[OperationRequest](#schemaoperationrequest)|true|none|Request details for an API operation| + +

Channel

+ + + + + + +```json +{ + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } +} + +``` + +Async API Channel + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|false|none|Name of the channel| +|description|string|false|none|Description of the channel| +|request|[ChannelRequest](#schemachannelrequest)|true|none|Request details for a channel within the Async API| + +

OperationRequest

+ + + + + + +```json +{ + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] +} + +``` + +Operation Request + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|method|string|true|none|HTTP method for the operation| +|path|string|true|none|Resource path for the operation| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies to be applied on the operation| + +#### Enumerated Values + +|Property|Value| +|---|---| +|method|GET| +|method|POST| +|method|PUT| +|method|DELETE| +|method|PATCH| +|method|HEAD| +|method|OPTIONS| + +

ChannelRequest

+ + + + + + +```json +{ + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] +} + +``` + +Channel Request + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|method|string|true|none|Async method for the channel| +|name|string|true|none|Channel name for the event| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies to be applied on the operation| + +#### Enumerated Values + +|Property|Value| +|---|---| +|method|SUB| + +

Policy

+ + + + + + +```json +{ + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" +} + +``` + +Policy Configuration + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|executionCondition|string|false|none|Conditional expression that determines when this policy executes| +|name|string|true|none|Name of the policy to apply| +|params|object|false|none|Key-value pairs of parameters for the policy| +|version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected.| + +

AddGatewayToRESTAPIRequest

+ + + + + + +```json +{ + "gatewayId": "prod-gateway-01" +} + +``` + +AddGatewayToAPIRequest object with basic gateway details + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|gatewayId|string|true|none|Handle (URL-friendly slug) of the gateway to associate with the REST API| + +

RESTAPIGatewayListResponse

+ + + + + + +```json +{ + "count": 3, + "list": [ + { + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z", + "associatedAt": "2025-10-15T10:30:00Z", + "isDeployed": true, + "deployment": { + "status": "CREATED", + "deployedAt": "2025-10-15T11:00:00Z" + } + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of gateways in current response| +|list|[[RESTAPIGatewayResponse](#schemarestapigatewayresponse)]|true|none|List of gateways associated with the API, including deployment details when deployed| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

RESTAPIGatewayResponse

+ + + + + + +```json +{ + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z", + "associatedAt": "2025-10-15T10:30:00Z", + "isDeployed": true, + "deployment": { + "status": "CREATED", + "deployedAt": "2025-10-15T11:00:00Z" + } +} + +``` + +### Properties + +allOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[GatewayResponse](#schemagatewayresponse)|false|none|none| + +and + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| +|» associatedAt|string(date-time)|true|none|Timestamp when the gateway was associated with the API| +|» isDeployed|boolean|true|none|Whether the API is currently deployed to this gateway| +|» deployment|[RESTAPIDeploymentDetails](#schemarestapideploymentdetails)|false|none|Deployment details (only present when isDeployed is true)| + +

RESTAPIDeploymentDetails

+ + + + + + +```json +{ + "status": "CREATED", + "deployedAt": "2025-10-15T11:00:00Z" +} + +``` + +Details about API deployment to a specific gateway + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|status|string|true|none|Current deployment status| +|deployedAt|string(date-time)|true|none|Timestamp when the API was deployed| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|CREATED| +|status|APPROVED| +|status|REJECTED| + +

CreateGatewayRequest

+ + + + + + +```json +{ + "id": "prod-gateway-01", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "version": "1.0" +} + +``` + +Request body for creating a gateway. Organization ID is automatically extracted +from the JWT token and does not need to be provided. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Handle (URL-friendly slug) for the gateway. Immutable after creation.| +|displayName|string|true|none|Human-readable gateway name| +|description|string|false|none|Description of the gateway| +|endpoints|[string]|true|none|Network endpoints exposed by this gateway, each as a full URL string| +|isCritical|boolean|false|none|Whether the gateway is critical for production| +|functionalityType|string|true|none|Type of gateway functionality| +|properties|object|false|none|Custom key-value properties for the gateway| +|version|string|false|none|Gateway version in `major.minor` format (e.g. `1.0`) or CalVer `YYYY.MM.DD` format (e.g. `2026.05.13`). Defaults to `1.0` if not provided.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|functionalityType|regular| +|functionalityType|ai| +|functionalityType|event| + +

CustomPolicyResponse

+ + + + + + +```json +{ + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "organizationUuid": "bc554ded-7e40-44a7-b397-48480793ad03", + "name": "rate-limit-custom", + "version": "1.0.0", + "description": "Custom rate limiting policy", + "policyDefinition": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +A custom policy stored in the platform's custom policy registry. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|uuid|string(uuid)|true|none|Unique identifier of the custom policy record| +|organizationUuid|string(uuid)|true|none|Organization this policy belongs to| +|name|string|true|none|Policy name| +|version|string|true|none|Policy version| +|description|string|false|none|Human-readable description of the policy| +|policyDefinition|object|true|none|The full policy schema as declared in the policy's policy-definition.yaml.
Contains `parameters` and `systemParameters` JSON Schema documents.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +

GatewayPolicyDefinition

+ + + + + + +```json +{ + "name": "set-wso2-headers", + "version": "v0.8.0", + "description": "Sets WSO2-specific headers in the request and response.", + "isCustomPolicy": true, + "policyDefinition": {} +} + +``` + +A policy installed on a gateway controller. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|true|none|Unique policy name| +|version|string|true|none|Semantic version of the policy| +|description|string|false|none|Human-readable description of the policy| +|isCustomPolicy|boolean|true|none|Whether this is a user-installed custom policy.| +|policyDefinition|object|false|none|The full policy schema as declared in the policy's policy-definition.yaml.
Contains `parameters` and `systemParameters` JSON Schema documents.
Only present for custom policies.| + +

ManifestSyncResponse

+ + + + + + +```json +{ + "policies": [ + { + "name": "set-wso2-headers", + "version": "v0.8.0", + "description": "Sets WSO2-specific headers in the request and response.", + "isCustomPolicy": true, + "policyDefinition": {} + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|policies|[[GatewayPolicyDefinition](#schemagatewaypolicydefinition)]|false|none|All policies installed on the gateway. Each entry includes name, version, and isCustomPolicy.
Custom policies additionally include policyDefinition with their parameters and systemParameters schemas.| + +

GatewayResponse

+ + + + + + +```json +{ + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|read-only|Handle (URL-friendly slug) for the gateway| +|organizationId|string|false|none|Handle (URL-friendly slug) of the organization this gateway belongs to| +|displayName|string|true|none|Human-readable gateway name| +|description|string|false|none|Description of the gateway| +|properties|object|false|none|Custom key-value properties for the gateway| +|endpoints|[string]|false|none|Network endpoints exposed by this gateway, each as a full URL string| +|isCritical|boolean|false|none|Whether the gateway is critical for production| +|functionalityType|string|false|none|Type of gateway functionality| +|version|string|false|none|Gateway version in `major.minor` format (e.g. `1.0`) or CalVer `YYYY.MM.DD` format (e.g. `2026.05.13`)| +|isActive|boolean|false|none|Indicates if the gateway is currently connected to the platform via WebSocket| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /gateways/{gatewayId}), omitted from list responses.| +|createdAt|string(date-time)|false|none|Timestamp when gateway was registered| +|updatedAt|string(date-time)|false|none|Timestamp when gateway was last updated| + +#### Enumerated Values + +|Property|Value| +|---|---| +|functionalityType|regular| +|functionalityType|ai| +|functionalityType|event| + +

Pagination

+ + + + + + +```json +{ + "total": 10, + "offset": 0, + "limit": 10 +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|total|integer|true|none|Total number of items available across all pages| +|offset|integer|true|none|Zero-based index of first item in current response| +|limit|integer|true|none|Maximum number of items returned per page| + +

GatewayListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "prod-gateway-01", + "organizationId": "acme", + "displayName": "Production Gateway 01", + "description": "Production gateway for handling API traffic", + "properties": { + "region": "us-west", + "tier": "premium" + }, + "endpoints": [ + "https://api.example.com:8443/api/v1", + "wss://events.example.com:8444" + ], + "isCritical": true, + "functionalityType": "regular", + "version": "1.0", + "isActive": true, + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2025-10-14T10:30:00Z", + "updatedAt": "2025-10-14T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +GatewayListResponse for paginated gateway results + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of items in current response| +|list|[[GatewayResponse](#schemagatewayresponse)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

GatewayStatusResponse

+ + + + + + +```json +{ + "id": "prod-gateway-01", + "isActive": true, + "isCritical": true +} + +``` + +Lightweight gateway status information optimized for frequent polling + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|read-only|Handle (URL-friendly slug) for the gateway| +|isActive|boolean|false|none|Indicates if the gateway is currently connected to the platform via WebSocket| +|isCritical|boolean|false|none|Whether the gateway is critical for production| + +

GatewayStatusListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "prod-gateway-01", + "isActive": true, + "isCritical": true + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +List of gateway status information for polling + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of items in current response| +|list|[[GatewayStatusResponse](#schemagatewaystatusresponse)]|true|none|[Lightweight gateway status information optimized for frequent polling]| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

ProjectListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "default-project", + "displayName": "Default Project", + "description": "This is the default project for development", + "organizationId": "acme", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of items in current response| +|list|[[Project](#schemaproject)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

OrganizationListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "acme", + "displayName": "Acme Corporation", + "region": "us", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of items in current response| +|list|[[Organization](#schemaorganization)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

RESTAPIListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "projectId": "default-project", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z", + "readOnly": false, + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of items in current response| +|list|[[RESTAPI](#schemarestapi)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

TokenRotationResponse

+ + + + + + +```json +{ + "id": "def45678-g901-23hi-j456-789012klmnop", + "token": "REDACTED_TOKEN", + "createdAt": "2025-10-15T14:20:00Z", + "message": "New token generated successfully. Old token remains active until revoked." +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string(uuid)|false|none|ID of the newly generated token| +|token|string|false|none|Plain-text new authentication token (only exposed once during rotation). The example value is a non-functional placeholder.| +|createdAt|string(date-time)|false|none|Timestamp when new token was created| +|message|string|false|none|Informational message about token rotation| + +

TokenInfoResponse

+ + + + + + +```json +{ + "id": "abc12345-f678-90de-f123-456789abcdef", + "status": "active", + "createdAt": "2025-10-14T10:30:00Z", + "revokedAt": null +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string(uuid)|false|none|Token UUID| +|status|string|false|none|Current token status| +|createdAt|string(date-time)|false|none|Timestamp when token was created| +|revokedAt|string(date-time)¦null|false|none|Timestamp when token was revoked (null if active)| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|active| +|status|revoked| + +

CreateRESTAPIRequest

+ + + + + + +```json +{ + "id": "my-rest-api-handle", + "displayName": "PizzaShackAPI", + "description": "This is a simple API for Pizza Shack online pizza delivery store", + "context": "/pizza", + "version": "1.0.0", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "projectId": "default-project", + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z", + "readOnly": false, + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "lifeCycleStatus": "CREATED", + "kind": "RestApi", + "transport": [ + "http", + "https" + ], + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operations": [ + { + "name": "getPetById", + "description": "Find pet by ID", + "request": { + "method": "GET", + "path": "/pet/{petId}", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "channels": [ + { + "name": "issues", + "description": "Channel for order events", + "request": { + "method": "SUB", + "name": "issues", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ] + } + } + ], + "subscriptionPlans": [ + "Gold", + "Silver" + ] +} + +``` + +### Properties + +allOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[RESTAPI](#schemarestapi)|false|none|none| + +and + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| + +

TimeUnit

+ + + + + + +```json +"days" + +``` + +Time unit for API key expiration duration + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|string|false|none|Time unit for API key expiration duration| + +#### Enumerated Values + +|Property|Value| +|---|---| +|*anonymous*|seconds| +|*anonymous*|minutes| +|*anonymous*|hours| +|*anonymous*|days| +|*anonymous*|weeks| +|*anonymous*|months| + +

ExpirationDuration

+ + + + + + +```json +{ + "duration": 30, + "unit": "days" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|duration|integer|true|none|Duration value (must be positive)| +|unit|[TimeUnit](#schematimeunit)|true|none|Time unit for API key expiration duration| + +

CreateAPIKeyRequest

+ + + + + + +```json +{ + "id": "production-key-01", + "displayName": "Production API Key", + "apiKey": "sk_example_1234567890abcdef", + "externalRefId": "ext-ref-12345", + "expiresAt": "2026-12-31T23:59:59Z", + "expiresIn": { + "duration": 30, + "unit": "days" + }, + "issuer": "api-platform-devportal" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique identifier for this API key within the API (optional; if omitted,
generated from displayName)| +|displayName|string|true|none|Human-readable name for the API key| +|apiKey|string|true|none|The plain text API key value that will be hashed before storage| +|externalRefId|string¦null|false|none|Optional reference ID for tracing purposes (from external platforms)| +|expiresAt|string(date-time)¦null|false|none|Optional expiration time in ISO 8601 format| +|expiresIn|[ExpirationDuration](#schemaexpirationduration)|false|none|Optional expiration duration| +|issuer|string¦null|false|none|Identifier of the developer portal that provisioned this API key. Null if not provided.| + +

CreateAPIKeyResponse

+ + + + + + +```json +{ + "status": "success", + "message": "API key created and broadcasted to gateways successfully", + "keyId": "production-key-01" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|status|string|true|none|Status of the operation| +|message|string|true|none|Additional details about the operation result| +|keyId|string|false|none|The internal ID generated for tracking| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|success| +|status|error| + +

UpdateAPIKeyRequest

+ + + + + + +```json +{ + "name": "production-key-01", + "displayName": "Production API Key (Updated)", + "apiKey": "sk_example_new1234567890abcdef", + "externalRefId": "ext-ref-12345", + "expiresAt": "2027-12-31T23:59:59Z", + "expiresIn": { + "duration": 30, + "unit": "days" + }, + "issuer": "api-platform-devportal" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|false|none|Unique identifier for this API key within the API (optional; if omitted, generated from displayName)| +|displayName|string|true|none|Human-readable name for the API key| +|apiKey|string|true|none|The new plain text API key value that will be hashed before storage| +|externalRefId|string¦null|false|none|Optional reference ID for tracing purposes (from external platforms)| +|expiresAt|string(date-time)¦null|false|none|Optional expiration time in ISO 8601 format| +|expiresIn|[ExpirationDuration](#schemaexpirationduration)|false|none|Optional expiration duration| +|issuer|string|false|none|Identifies the portal that created this key| + +

UpdateAPIKeyResponse

+ + + + + + +```json +{ + "status": "success", + "message": "API key updated and broadcasted to gateways successfully", + "keyId": "production-key-01" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|status|string|true|none|Status of the operation| +|message|string|true|none|Additional details about the operation result| +|keyId|string|false|none|The internal ID of the updated key| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|success| +|status|error| + +

SubscriptionPlanLimit

+ + + + + + +```json +{ + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|limitType|string|false|none|Kind of quota this limit enforces. Only REQUEST_COUNT is currently enforced; other values are accepted by the schema for forward compatibility but are rejected by the API today.| +|timeUnit|string|true|none|Throttle window unit| +|timeAmount|integer|false|none|Number of timeUnit windows the limit applies over (e.g. 2 with timeUnit=HOUR means "per 2 hours")| +|limitCount|integer|true|none|Number of requests (or units, for BANDWIDTH/TOTAL_TOKEN_COUNT) allowed in the throttle window| +|limitCountUnit|string|false|none|Unit for limitCount when limitType is BANDWIDTH (e.g. MB, GB)| +|stopOnQuotaReach|boolean|false|none|Whether to block requests when this limit's quota is exhausted| + +#### Enumerated Values + +|Property|Value| +|---|---| +|limitType|REQUEST_COUNT| +|limitType|BANDWIDTH| +|limitType|TOTAL_TOKEN_COUNT| +|timeUnit|MINUTE| +|timeUnit|HOUR| +|timeUnit|DAY| +|timeUnit|MONTH| + +

CreateSubscriptionPlanRequest

+ + + + + + +```json +{ + "id": "gold", + "displayName": "Gold", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "status": "ACTIVE" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Handle (URL-friendly slug) for the plan. Immutable after creation.| +|displayName|string|true|none|Human-readable name for the subscription plan| +|limits|[[SubscriptionPlanLimit](#schemasubscriptionplanlimit)]|false|none|Throttling limits for the plan (e.g. requests per hour, requests per month). The table backing this API already supports multiple limits per plan, but the platform-api currently only persists and enforces the first entry in this array; any additional entries are accepted but ignored.| +|expiryTime|string(date-time)|false|none|Optional plan expiry time (RFC3339)| +|status|string|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|INACTIVE| + +

SubscriptionPlan

+ + + + + + +```json +{ + "id": "string", + "displayName": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Handle (slug) for the subscription plan| +|displayName|string|true|none|Human-readable name for the subscription plan| +|limits|[[SubscriptionPlanLimit](#schemasubscriptionplanlimit)]|false|none|Throttling limits configured for the plan. Only one entry is currently supported and returned, even though the underlying storage allows multiple.| +|expiryTime|string(date-time)|false|none|none| +|organizationId|string|false|none|Handle (URL-friendly slug) of the organization this plan belongs to| +|status|string|false|none|none| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /subscription-plans/{planId}), omitted from list responses.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|ACTIVE| +|status|INACTIVE| + +

SubscriptionPlanListResponse

+ + + + + + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|list|[[SubscriptionPlan](#schemasubscriptionplan)]|true|none|List of subscription plans in current response| +|count|integer|true|none|Number of subscription plans in current response| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

CreateSubscriptionRequest

+ + + + + + +```json +{ + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "user-123", + "applicationId": "my-app-handle", + "subscriptionPlanId": "gold", + "status": "ACTIVE" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|artifactId|string|true|none|Handle (ID) of the artifact to subscribe to. Resolved against the table for the given kind.| +|kind|string|true|none|Type of the artifact identified by artifactId. Determines which artifact table artifactId is resolved against.| +|subscriberId|string|true|none|Unique subscriber identifier for the subscription (required)| +|applicationId|string|false|none|Handle (ID) of the application this subscription belongs to. Optional in token-based subscriptions.| +|subscriptionPlanId|string|false|none|Handle (slug) of the subscription plan. Links the subscription to rate limit and billing configuration.| +|status|string|false|none|Subscription status (default ACTIVE)| + +#### Enumerated Values + +|Property|Value| +|---|---| +|kind|RestApi| +|kind|LlmProvider| +|kind|LlmProxy| +|kind|Mcp| +|status|ACTIVE| +|status|INACTIVE| +|status|REVOKED| + +

Subscription

+ + + + + + +```json +{ + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "string", + "applicationId": "my-app-handle", + "subscriptionToken": "string", + "subscriptionPlanId": "gold", + "subscriptionPlanName": "string", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string(uuid)|false|none|Subscription ID| +|artifactId|string|false|none|Handle (ID) of the subscribed artifact| +|kind|string|false|none|Type of the subscribed artifact| +|subscriberId|string|false|none|Unique subscriber identifier for this API (required)| +|applicationId|string|false|none|Handle (ID) of the application this subscription belongs to (optional for token-based subscriptions)| +|subscriptionToken|string|false|none|Opaque subscription token for API invocation via Subscription-Key header| +|subscriptionPlanId|string|false|none|Handle (slug) of the subscription plan| +|subscriptionPlanName|string|false|none|Subscription plan display name (e.g. Bronze, Gold)| +|organizationId|string|false|none|Handle (URL-friendly slug) of the organization this subscription belongs to| +|status|string|false|none|none| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /subscriptions/{subscriptionId}), omitted from list responses.| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|kind|RestApi| +|kind|LlmProvider| +|kind|LlmProxy| +|kind|Mcp| +|status|ACTIVE| +|status|INACTIVE| +|status|REVOKED| + +

SubscriptionListResponse

+ + + + + + +```json +{ + "list": [ + { + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "string", + "applicationId": "my-app-handle", + "subscriptionToken": "string", + "subscriptionPlanId": "gold", + "subscriptionPlanName": "string", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|list|[[Subscription](#schemasubscription)]|true|none|List of subscriptions in current response| +|count|integer|true|none|Number of subscriptions in current response| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

Error

+ + + + + + +```json +{ + "status": "error", + "code": "REST_API_NOT_FOUND", + "message": "The requested REST API could not be found.", + "errors": [ + { + "field": "", + "message": "must start with /" + } + ], + "details": {}, + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} + +``` + +Standard error response + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|status|string|true|none|Always the literal "error".| +|code|string|true|none|Stable, machine-readable error code from the error catalog, in the form `_` (e.g. `REST_API_NOT_FOUND`). Clients and agents should branch on this, not on the HTTP status.| +|message|string|true|none|Human-readable description of the error.| +|errors|[[FieldError](#schemafielderror)]|false|none|Per-field validation failures. Present when the error is a validation failure.| +|details|object|false|none|Optional structured metadata specific to this error condition (e.g. the resources referencing a secret that blocked its deletion). Shape varies by `code`; absent when not applicable.| +|trackingId|string(uuid)|false|none|Correlation ID for server-side failures. Present only on 5xx responses; quote it when reporting the error so operators can find the corresponding server log entry.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|error| + +

FieldError

+ + + + + + +```json +{ + "field": "", + "message": "must start with /" +} + +``` + +Field-level validation error + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|field|string|true|none|Path of the offending field.| +|message|string|true|none|Why the field failed validation.| + +

DeployRequest

+ + + + + + +```json +{ + "name": "v1.0-production", + "base": "current", + "gatewayId": "prod-gateway-01", + "metadata": {} +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|true|none|Name/label for this deployment (e.g., "v1.0-prod", "hotfix-2024-01-15")| +|base|string|true|none|The source for the API definition. Can be "current" (latest working copy) or a deploymentId (existing deployment)| +|gatewayId|string|true|none|Handle (URL-friendly slug) of the target gateway for this deployment| +|metadata|object|false|none|Optional metadata for the deployment. Supported keys include `endpointUrl`, `vhostMain`, and `vhostSandbox`.| + +

DeploymentResponse

+ + + + + + +```json +{ + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|deploymentId|string(uuid)|true|none|Unique identifier for the deployment| +|name|string|true|none|Name/label for this deployment| +|gatewayId|string|true|none|Handle (URL-friendly slug) of the gateway| +|status|string|true|none|Current deployment lifecycle state:
- DEPLOYED: Currently active on the gateway
- UNDEPLOYED: Suspended but can be rolled back
- DEPLOYING: Deployment in progress, waiting for gateway acknowledgement
- UNDEPLOYING: Undeployment in progress, waiting for gateway acknowledgement
- FAILED: Deployment or undeployment failed (see statusReason for error code)
- ARCHIVED: Historical deployment, can be rolled back| +|baseDeploymentId|string(uuid)¦null|false|none|UUID of the base deployment this was created from| +|metadata|object|false|none|Metadata associated with the deployment| +|createdAt|string(date-time)|true|none|Timestamp when the deployment artifact was created| +|statusReason|string¦null|false|none|Error code explaining the failure reason. Null unless status is FAILED (e.g. DEPLOYMENT_TIMEOUT, GATEWAY_PROCESSING_ERROR)| +|updatedAt|string(date-time)¦null|false|none|Timestamp when the deployment status last changed (null for ARCHIVED deployments)| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|DEPLOYED| +|status|UNDEPLOYED| +|status|DEPLOYING| +|status|UNDEPLOYING| +|status|FAILED| +|status|ARCHIVED| + +

DeploymentListResponse

+ + + + + + +```json +{ + "count": 0, + "list": [ + { + "deploymentId": "a73c85a1-d857-491e-a6b2-51dce05de7a2", + "name": "v1.0-production", + "gatewayId": "prod-gateway-01", + "status": "DEPLOYED", + "baseDeploymentId": "be6d8692-b9de-400e-b6c1-14db50154e27", + "metadata": {}, + "createdAt": "2019-08-24T14:15:22Z", + "statusReason": "string", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of deployments in current response| +|list|[[DeploymentResponse](#schemadeploymentresponse)]|true|none|List of deployments| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

Upstream

+ + + + + + +```json +{ + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } +} + +``` + +Upstream backend configuration with main and sandbox endpoints + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|main|[UpstreamDefinition](#schemaupstreamdefinition)|true|none|Upstream endpoint configuration. Provide exactly one of `url` (a direct backend URL) or
`ref` (a reference to a predefined upstream definition) — never both.| +|sandbox|[UpstreamDefinition](#schemaupstreamdefinition)|false|none|Upstream endpoint configuration. Provide exactly one of `url` (a direct backend URL) or
`ref` (a reference to a predefined upstream definition) — never both.| + +

UpstreamDefinition

+ + + + + + +```json +{ + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } +} + +``` + +Upstream endpoint configuration. Provide exactly one of `url` (a direct backend URL) or +`ref` (a reference to a predefined upstream definition) — never both. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|url|string(uri)|false|none|Direct backend URL to route traffic to. Mutually exclusive with `ref`.| +|ref|string|false|none|Reference to a predefined upstreamDefinition. Mutually exclusive with `url`.| +|auth|[UpstreamAuth](#schemaupstreamauth)|false|none|Authentication configuration for upstream endpoints| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| + +

UpstreamAuth

+ + + + + + +```json +{ + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" +} + +``` + +Authentication configuration for upstream endpoints + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|type|string|false|none|Authentication type| +|header|string|false|none|Header name for api-key authentication (e.g., 'Authorization' for bearer/basic, custom header for api-key)| +|value|string(password)|false|write-only|Authentication value (API key, Bearer token, or Base64 encoded credentials for basic auth)| + +#### Enumerated Values + +|Property|Value| +|---|---| +|type|basic| +|type|bearer| +|type|api-key| +|type|other| +|type|none| + +

ExtractionIdentifier

+ + + + + + +```json +{ + "location": "payload", + "identifier": "$.usage.inputTokens" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|location|string|true|none|Where to find the token information| +|identifier|string|true|none|JSONPath expression or header name to identify the token value| + +#### Enumerated Values + +|Property|Value| +|---|---| +|location|payload| +|location|header| +|location|queryParam| +|location|pathParam| + +

LLMProviderTemplateAuth

+ + + + + + +```json +{ + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|type|string|false|none|Authentication type used by the LLM provider template| +|header|string|false|none|Header name to send the auth value| +|valuePrefix|string|false|none|Prefix to attach before the auth value| + +

LLMProviderTemplateMetadata

+ + + + + + +```json +{ + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|endpointUrl|string(uri)|false|none|Default endpoint URL for the template| +|auth|[LLMProviderTemplateAuth](#schemallmprovidertemplateauth)|false|none|none| +|logoUrl|string(uri)|false|none|URL of the provider logo| +|openapiSpecUrl|string(uri)|false|none|URL to the OpenAPI specification for the provider| + +

LLMProviderTemplateResourceMapping

+ + + + + + +```json +{ + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|resource|string|true|none|Resource path pattern this mapping applies to (for example /responses or /chat/*)| +|promptTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|completionTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|totalTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|remainingTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|requestModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|responseModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| + +

LLMProviderTemplateResourceMappings

+ + + + + + +```json +{ + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|resources|[[LLMProviderTemplateResourceMapping](#schemallmprovidertemplateresourcemapping)]|false|none|none| + +

LLMProviderTemplate

+ + + + + + +```json +{ + "id": "openai", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "readOnly": false, + "version": "v1.0", + "isLatest": true, + "enabled": true, + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + }, + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique handle for the template| +|groupId|string|false|read-only|Stable identifier shared by every version of a template family.| +|displayName|string|true|none|Human-readable LLM Template name| +|managedBy|string|false|none|Identifies who manages the template. Built-in templates use 'wso2';
custom templates default to 'organization' and may be set to any value.| +|description|string|false|none|Description of the LLM provider template| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource| +|readOnly|boolean|false|read-only|True if the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane; false for control-plane created artifacts.| +|version|string|true|none|Content version, matching the v. pattern (e.g. v1.0, v2.0).
Must be unique for this template.| +|isLatest|boolean|false|read-only|Whether this is the latest version of the template.| +|enabled|boolean|false|read-only|Whether this version is offered when creating providers. If false, the
template version is hidden from the provider creation UI and API.| +|openapi|string|false|none|OpenAPI specification content (JSON or YAML) for the provider, when
uploaded/pasted. Use metadata.openapiSpecUrl instead to reference the
spec by URL.| +|metadata|[LLMProviderTemplateMetadata](#schemallmprovidertemplatemetadata)|false|none|none| +|promptTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|completionTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|totalTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|remainingTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|requestModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|responseModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|resourceMappings|[LLMProviderTemplateResourceMappings](#schemallmprovidertemplateresourcemappings)|false|none|none| +|createdAt|string(date-time)|false|read-only|Timestamp when the resource was created| +|updatedAt|string(date-time)|false|read-only|Timestamp when the resource was last updated| + +

CreateLLMProviderTemplateVersionRequest

+ + + + + + +```json +{ + "displayName": "OpenAI", + "version": "v2.0", + "managedBy": "organization", + "description": "Default OpenAI template", + "openapi": "string", + "metadata": { + "endpointUrl": "https://api.openai.com", + "auth": { + "type": "bearer", + "header": "Authorization", + "valuePrefix": "Bearer " + }, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "openapiSpecUrl": "https://api.openai.com/openapi.json" + }, + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + }, + "resourceMappings": { + "resources": [ + { + "resource": "/responses", + "promptTokens": { + "location": "payload", + "identifier": "$.usage.inputTokens" + }, + "completionTokens": { + "location": "payload", + "identifier": "$.usage.outputTokens" + }, + "totalTokens": { + "location": "payload", + "identifier": "$.usage.totalTokens" + }, + "remainingTokens": { + "location": "header", + "identifier": "x-ratelimit-remaining-tokens" + }, + "requestModel": { + "location": "payload", + "identifier": "$.model" + }, + "responseModel": { + "location": "payload", + "identifier": "$.model" + } + } + ] + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|displayName|string|false|none|Human-readable LLM Template name. Optional — when omitted, the new
version inherits the family's existing name. Supplying a different
value renames the template family.| +|version|string|true|none|New version identifier, e.g. v2.0. Must be unique for this template.| +|managedBy|string|false|none|Identifies who manages the template. Custom templates default to 'organization'.| +|description|string|false|none|Description of the LLM provider template| +|openapi|string|false|none|OpenAPI specification content (JSON or YAML) for the provider, when
uploaded/pasted. Use metadata.openapiSpecUrl instead to reference the
spec by URL.| +|metadata|[LLMProviderTemplateMetadata](#schemallmprovidertemplatemetadata)|false|none|none| +|promptTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|completionTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|totalTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|remainingTokens|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|requestModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|responseModel|[ExtractionIdentifier](#schemaextractionidentifier)|false|none|none| +|resourceMappings|[LLMProviderTemplateResourceMappings](#schemallmprovidertemplateresourcemappings)|false|none|none| + +

LLMProviderTemplateListItem

+ + + + + + +```json +{ + "id": "openai", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "version": "v1.0", + "isLatest": true, + "enabled": true, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique handle for this specific template version.| +|groupId|string|false|read-only|Stable identifier shared by every version of a template family
(defaults to the first version's handle). Read-only.| +|displayName|string|true|none|Human-readable name for the LLM provider template| +|managedBy|string|false|none|Who manages the template ('wso2' for built-in, otherwise custom-defined).| +|description|string|false|none|none| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|version|string|false|none|Content version, matching the v. pattern (e.g. v1.0, v2.0).| +|isLatest|boolean|false|none|Whether this is the latest version of the template.| +|enabled|boolean|false|none|Whether this version is offered when creating providers.| +|logoUrl|string(uri)|false|none|URL of the provider logo| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| +|readOnly|boolean|false|none|True when the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane.| + +

LLMProviderTemplateListResponse

+ + + + + + +```json +{ + "count": 5, + "list": [ + { + "id": "openai", + "groupId": "openai", + "displayName": "OpenAI", + "managedBy": "wso2", + "description": "Default OpenAI template", + "createdBy": "john.doe", + "version": "v1.0", + "isLatest": true, + "enabled": true, + "logoUrl": "https://cdn.example.com/logos/openai.svg", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|none| +|list|[[LLMProviderTemplateListItem](#schemallmprovidertemplatelistitem)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

LLMAccessControl

+ + + + + + +```json +{ + "mode": "deny_all", + "exceptions": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ] + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|mode|string|true|none|Access control mode| +|exceptions|[[RouteException](#schemarouteexception)]|false|none|Path exceptions to the access control mode| + +#### Enumerated Values + +|Property|Value| +|---|---| +|mode|allow_all| +|mode|deny_all| + +

RouteException

+ + + + + + +```json +{ + "path": "/chat/completions", + "methods": [ + "GET" + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|path|string|true|none|Path pattern| +|methods|[string]|true|none|HTTP methods| + +

LLMPolicy

+ + + + + + +```json +{ + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|true|none|none| +|version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected.| +|paths|[[LLMPolicyPath](#schemallmpolicypath)]|true|none|none| + +

LLMPolicyPath

+ + + + + + +```json +{ + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|path|string|true|none|none| +|methods|[string]|true|none|none| +|params|object|true|none|JSON Schema describing the parameters accepted by this policy. This itself is a JSON Schema document.| + +

OperationPolicy

+ + + + + + +```json +{ + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|name|string|true|none|none| +|version|string|true|none|Version of the policy. Only major-only version is allowed (e.g., v0, v1). Full semantic version (e.g., v1.0.0) is not accepted and will be rejected.| +|executionCondition|string|false|none|Optional per-request CEL expression controlling whether the policy runs| +|paths|[[OperationPolicyPath](#schemaoperationpolicypath)]|true|none|none| + +

OperationPolicyPath

+ + + + + + +```json +{ + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|path|string|true|none|none| +|methods|[string]|true|none|none| +|params|object|true|none|Policy parameters| + +

LLMRateLimitingConfig

+ + + + + + +```json +{ + "providerLevel": { + "global": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + "consumerLevel": { + "resourceWise": { + "default": { + "request": { + "enabled": true, + "count": 50, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 100, + "reset": { + "duration": 1, + "unit": "month" + } + } + }, + "resources": [ + { + "resource": "/models", + "limit": { + "request": { + "enabled": true, + "count": 200, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 100000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 25, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 10, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + } + ] + } + } +} + +``` + +Rate limiting configuration for an LLM provider at provider and consumer levels. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|providerLevel|[RateLimitingScopeConfig](#schemaratelimitingscopeconfig)|false|none|Rate limiting configuration for a scope (provider or consumer). Either global or resource-wise limits can be defined.| +|consumerLevel|[RateLimitingScopeConfig](#schemaratelimitingscopeconfig)|false|none|Rate limiting configuration for a scope (provider or consumer). Either global or resource-wise limits can be defined.| + +

RateLimitingScopeConfig

+ + + + + + +```json +{ + "global": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 1000, + "reset": { + "duration": 2, + "unit": "week" + } + } + }, + "resourceWise": { + "default": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 1000, + "reset": { + "duration": 2, + "unit": "week" + } + } + }, + "resources": [ + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 1000, + "reset": { + "duration": 2, + "unit": "week" + } + } + } + } + ] + } +} + +``` + +Rate limiting configuration for a scope (provider or consumer). Either global or resource-wise limits can be defined. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|global|[RateLimitingLimitConfig](#schemaratelimitinglimitconfig)|false|none|Limit definition with independent request/token/cost dimensions. If all dimensions are disabled (or absent), rate limiting is off for that scope.| +|resourceWise|[ResourceWiseRateLimitingConfig](#schemaresourcewiseratelimitingconfig)|false|none|Resource-specific limits with a required default limit.| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| + +

ResourceWiseRateLimitingConfig

+ + + + + + +```json +{ + "default": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 1000, + "reset": { + "duration": 2, + "unit": "week" + } + } + }, + "resources": [ + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 1000, + "reset": { + "duration": 2, + "unit": "week" + } + } + } + } + ] +} + +``` + +Resource-specific limits with a required default limit. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|default|[RateLimitingLimitConfig](#schemaratelimitinglimitconfig)|true|none|Limit definition with independent request/token/cost dimensions. If all dimensions are disabled (or absent), rate limiting is off for that scope.| +|resources|[[RateLimitingResourceLimit](#schemaratelimitingresourcelimit)]|true|none|Explicit resource limits that override the default limit.| + +

RateLimitingResourceLimit

+ + + + + + +```json +{ + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 1000, + "reset": { + "duration": 2, + "unit": "week" + } + } + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|resource|string|true|none|Explicit resource path to apply the limit to.| +|limit|[RateLimitingLimitConfig](#schemaratelimitinglimitconfig)|true|none|Limit definition with independent request/token/cost dimensions. If all dimensions are disabled (or absent), rate limiting is off for that scope.| + +

RateLimitResetWindow

+ + + + + + +```json +{ + "duration": 2, + "unit": "week" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|duration|integer|true|none|Reset duration for the limit window.| +|unit|string|true|none|Reset time unit for the limit window.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|unit|minute| +|unit|hour| +|unit|day| +|unit|week| +|unit|month| + +

RequestRateLimitDimension

+ + + + + + +```json +{ + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|enabled|boolean|false|none|Enable request-count based limiting.| +|count|integer|false|none|Maximum number of requests in the reset window.| +|reset|[RateLimitResetWindow](#schemaratelimitresetwindow)|false|none|none| + +

TokenRateLimitDimension

+ + + + + + +```json +{ + "enabled": true, + "count": 1000000, + "reset": { + "duration": 2, + "unit": "week" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|enabled|boolean|false|none|Enable token-count based limiting.| +|count|integer|false|none|Maximum number of tokens in the reset window.| +|reset|[RateLimitResetWindow](#schemaratelimitresetwindow)|false|none|none| + +

CostRateLimitDimension

+ + + + + + +```json +{ + "enabled": true, + "amount": 1000, + "reset": { + "duration": 2, + "unit": "week" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|enabled|boolean|false|none|Enable cost-based limiting.| +|amount|number(float)|false|none|Maximum cost in the reset window.| +|reset|[RateLimitResetWindow](#schemaratelimitresetwindow)|false|none|none| + +

RateLimitingLimitConfig

+ + + + + + +```json +{ + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 1000, + "reset": { + "duration": 2, + "unit": "week" + } + } +} + +``` + +Limit definition with independent request/token/cost dimensions. If all dimensions are disabled (or absent), rate limiting is off for that scope. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|request|[RequestRateLimitDimension](#schemarequestratelimitdimension)|false|none|none| +|token|[TokenRateLimitDimension](#schematokenratelimitdimension)|false|none|none| +|cost|[CostRateLimitDimension](#schemacostratelimitdimension)|false|none|none| + +

LLMProvider

+ + + + + + +```json +{ + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "context": "/openai", + "vhost": "api.openai.com", + "template": "openai", + "openapi": "openapi: 3.0.3\ninfo:\n title: Provider API\n version: v1.0\npaths: {}\n", + "modelProviders": [ + { + "id": "claude", + "displayName": "Claude", + "models": [ + { + "id": "claude-3-5-sonnet", + "displayName": "Claude 3.5 Sonnet", + "description": "High-quality reasoning model" + } + ] + } + ], + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "accessControl": { + "mode": "deny_all", + "exceptions": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ] + } + ] + }, + "rateLimiting": { + "providerLevel": { + "global": { + "request": { + "enabled": true, + "count": 1500, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 1000000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + "consumerLevel": { + "resourceWise": { + "default": { + "request": { + "enabled": true, + "count": 50, + "reset": { + "duration": 2, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 100, + "reset": { + "duration": 1, + "unit": "month" + } + } + }, + "resources": [ + { + "resource": "/models", + "limit": { + "request": { + "enabled": true, + "count": 200, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "token": { + "enabled": true, + "count": 100000, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + }, + { + "resource": "/chat/completions", + "limit": { + "request": { + "enabled": true, + "count": 25, + "reset": { + "duration": 1, + "unit": "week" + } + }, + "cost": { + "enabled": true, + "amount": 10, + "reset": { + "duration": 1, + "unit": "month" + } + } + } + } + ] + } + } + }, + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu", + "configurations": { + "host": "prod-eu.platform-gw.local" + } + }, + { + "id": "prod-us", + "configurations": { + "host": "prod-us.platform-gw.local" + } + } + ], + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique handle for the provider| +|displayName|string|true|none|Human-readable LLM Provider name| +|description|string|false|none|Description of the LLM provider| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|readOnly|boolean|false|read-only|True if the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane; false for control-plane created artifacts.| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /llm-providers/{id}), omitted from list responses.| +|version|string|true|none|Semantic version of the LLM Provider| +|context|string|false|none|Base path for all routes exposed by this provider. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default.| +|vhost|string|false|none|Virtual host name used for routing. Supports standard domain names, subdomains, or wildcard domains. Must follow RFC-compliant hostname rules. Wildcards are only allowed in the left-most label (e.g., *.example.com).| +|template|string|true|none|Template name to use for this LLM Provider| +|openapi|string|false|none|OpenAPI specification (JSON or YAML) for the provider endpoint| +|modelProviders|[[LLMModelProvider](#schemallmmodelprovider)]|false|none|List of model providers and their models supported by this provider. For non-aggregator templates, only a single model provider is allowed (aggregator templates: awsbedrock, azureaifoundry).| +|upstream|[Upstream](#schemaupstream)|true|none|Upstream backend configuration with main and sandbox endpoints| +|accessControl|[LLMAccessControl](#schemallmaccesscontrol)|true|none|none| +|rateLimiting|[LLMRateLimitingConfig](#schemallmratelimitingconfig)|false|none|Rate limiting configuration for an LLM provider at provider and consumer levels.| +|globalPolicies|[[Policy](#schemapolicy)]|false|none|Global (api-level) policies applied across ALL operations as one shared scope, evaluated before operation-level policies.| +|operationPolicies|[[OperationPolicy](#schemaoperationpolicy)]|false|none|Operation-level policies scoped to specific paths/methods, evaluated after global policies.| +|policies|[[LLMPolicy](#schemallmpolicy)]|false|none|DEPRECATED - use operationPolicies. Still honoured (treated identically to operationPolicies).| +|security|[SecurityConfig](#schemasecurityconfig)|false|none|Defines security mechanisms (API key, OAuth2) applicable to the API| +|associatedGateways|[[AssociatedGateway](#schemaassociatedgateway)]|false|none|Optional list of gateways this LLM provider can be deployed to, along with per-gateway configuration overrides. This field is optional; omitting it does not change existing behaviour.| +|createdAt|string(date-time)|false|read-only|Timestamp when the resource was created| +|updatedAt|string(date-time)|false|read-only|Timestamp when the resource was last updated| + +

AssociatedGateway

+ + + + + + +```json +{ + "id": "prod-eu", + "configurations": { + "someKey": "someValue" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|Handle of the gateway this artifact can be deployed to| +|configurations|object|false|none|Per-gateway configuration overrides for this artifact. This is a free-form object; the supported keys depend on the deployed artifact type.| + +

LLMProviderListItem

+ + + + + + +```json +{ + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "createdBy": "john.doe", + "readOnly": false, + "version": "v1.0", + "template": "openai", + "status": "deployed", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|displayName|string|true|none|Human-readable name for the LLM provider| +|description|string|false|none|none| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|readOnly|boolean|false|none|True when the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane.| +|version|string|false|none|none| +|template|string|false|none|none| +|status|string|false|none|none| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|pending| +|status|deployed| +|status|failed| + +

LLMModelProvider

+ + + + + + +```json +{ + "id": "claude", + "displayName": "Claude", + "models": [ + { + "id": "claude-3-5-sonnet", + "displayName": "Claude 3.5 Sonnet", + "description": "High-quality reasoning model" + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique model provider identifier| +|displayName|string|true|none|Human-readable model provider name| +|models|[[LLMModel](#schemallmmodel)]|false|none|Models under this model provider| + +

LLMModel

+ + + + + + +```json +{ + "id": "claude-3-5-sonnet", + "displayName": "Claude 3.5 Sonnet", + "description": "High-quality reasoning model" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique model identifier| +|displayName|string|true|none|Human-readable model name| +|description|string|false|none|Model description| + +

LLMProviderListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "wso2-openai-provider", + "displayName": "WSO2 OpenAI Provider", + "description": "Primary OpenAI provider", + "createdBy": "john.doe", + "readOnly": false, + "version": "v1.0", + "template": "openai", + "status": "deployed", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|none| +|list|[[LLMProviderListItem](#schemallmproviderlistitem)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

LLMProxy

+ + + + + + +```json +{ + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "projectId": "default-project", + "context": "/openai", + "vhost": "api.openai.com", + "provider": { + "id": "wso2-openai-provider", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "additionalProviders": [ + { + "id": "anthropic-provider", + "as": "anthropic-upstream", + "transformer": { + "type": "openai-to-anthropic", + "version": "v1", + "params": {} + } + } + ], + "openapi": "openapi: 3.0.3\ninfo:\n title: Proxy API\n version: v1.0\npaths: {}\n", + "globalPolicies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "operationPolicies": [ + { + "name": "token-based-ratelimit", + "version": "v1", + "executionCondition": "string", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "policies": [ + { + "name": "budgetControl", + "version": "v1", + "paths": [ + { + "path": "/chat/completions", + "methods": [ + "GET" + ], + "params": {} + } + ] + } + ], + "security": { + "enabled": true, + "apiKey": { + "enabled": true, + "key": "X-API-Key", + "valuePrefix": "Bearer", + "in": "header" + } + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ], + "createdAt": "2023-10-12T10:30:00Z", + "updatedAt": "2023-10-12T10:30:00Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique handle for the proxy| +|displayName|string|true|none|Human-readable LLM proxy name| +|description|string|false|none|Description of the LLM proxy| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|readOnly|boolean|false|read-only|True if the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane; false for control-plane created artifacts.| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /llm-proxies/{id}), omitted from list responses.| +|version|string|true|none|Semantic version of the LLM proxy| +|projectId|string|true|none|Handle (URL-friendly slug) of the project this proxy belongs to| +|context|string|false|none|Base path for all routes exposed by this proxy. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default.| +|vhost|string|false|none|Virtual host name used for routing. Supports standard domain names, subdomains, or wildcard domains. Must follow RFC-compliant hostname rules. Wildcards are only allowed in the left-most label (e.g., *.example.com).| +|provider|[LLMProxyProvider](#schemallmproxyprovider)|true|none|none| +|additionalProviders|[[LLMProxyAdditionalProvider](#schemallmproxyadditionalprovider)]|false|none|Optional list of additional LLM providers attached to this proxy as selectable upstreams. Policies route requests to any of these by setting the upstream name. The primary `provider` field above remains the default upstream and the FK target.| +|openapi|string|false|none|OpenAPI specification (JSON or YAML) for the proxy endpoint| +|globalPolicies|[[Policy](#schemapolicy)]|false|none|Global (api-level) policies applied across ALL operations as one shared scope, evaluated before operation-level policies.| +|operationPolicies|[[OperationPolicy](#schemaoperationpolicy)]|false|none|Operation-level policies scoped to specific paths/methods, evaluated after global policies.| +|policies|[[LLMPolicy](#schemallmpolicy)]|false|none|DEPRECATED - use operationPolicies. Still honoured (treated identically to operationPolicies).| +|security|[SecurityConfig](#schemasecurityconfig)|false|none|Defines security mechanisms (API key, OAuth2) applicable to the API| +|associatedGateways|[[AssociatedGateway](#schemaassociatedgateway)]|false|none|Optional list of gateways this LLM proxy can be deployed to, along with per-gateway configuration overrides. This field is optional; omitting it does not change existing behaviour.| +|createdAt|string(date-time)|false|read-only|Timestamp when the resource was created| +|updatedAt|string(date-time)|false|read-only|Timestamp when the resource was last updated| + +

LLMProxyListItem

+ + + + + + +```json +{ + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "createdBy": "john.doe", + "context": "/wso2-con-assistant", + "version": "v1.0", + "projectId": "default-project", + "provider": "wso2-openai-provider", + "status": "deployed", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|displayName|string|true|none|Human-readable name for the LLM proxy| +|description|string|false|none|none| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|context|string|false|none|Context path where the proxy is exposed| +|version|string|false|none|none| +|projectId|string|false|none|Handle (URL-friendly slug) of the project this proxy belongs to| +|provider|string|false|none|Unique id of a deployed llm provider| +|status|string|false|none|none| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| +|readOnly|boolean|false|none|True when the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|pending| +|status|deployed| +|status|failed| + +

LLMProxyListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "wso2-con-assistant", + "displayName": "WSO2 Con Assistant", + "description": "Customer support assistant", + "createdBy": "john.doe", + "context": "/wso2-con-assistant", + "version": "v1.0", + "projectId": "default-project", + "provider": "wso2-openai-provider", + "status": "deployed", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|none| +|list|[[LLMProxyListItem](#schemallmproxylistitem)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

LLMProxyProvider

+ + + + + + +```json +{ + "id": "wso2-openai-provider", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|Unique id of a deployed llm provider| +|auth|[UpstreamAuth](#schemaupstreamauth)|false|none|Authentication configuration for upstream endpoints| + +

LLMProxyAdditionalProvider

+ + + + + + +```json +{ + "id": "anthropic-provider", + "as": "anthropic-upstream", + "transformer": { + "type": "openai-to-anthropic", + "version": "v1", + "params": {} + } +} + +``` + +Additional LLM provider attached to this proxy as a selectable upstream. Policies route to it by referring to the `as` name (defaults to `id`). + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|true|none|Unique id of a deployed llm provider| +|as|string|false|none|Logical LLM Provider name used by policies to select this provider. Must be unique within the proxy. Defaults to `id` when omitted.| +|transformer|[LLMProxyTransformer](#schemallmproxytransformer)|false|none|Request/response translator applied when this provider is the selected upstream. The proxy injects the translator as a conditional policy whose execution condition matches this provider, so it runs only when the provider is selected. The provider's `as` name (defaults to `id`) is passed to the translator as its target upstream.| + +

LLMProxyTransformer

+ + + + + + +```json +{ + "type": "openai-to-anthropic", + "version": "v1", + "params": {} +} + +``` + +Request/response translator applied when this provider is the selected upstream. The proxy injects the translator as a conditional policy whose execution condition matches this provider, so it runs only when the provider is selected. The provider's `as` name (defaults to `id`) is passed to the translator as its target upstream. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|type|string|true|none|Translator policy name (for example openai-to-anthropic).| +|version|string|true|none|Major-only translator policy version (for example v1). The Gateway Controller resolves it to the installed full version.| +|params|object|false|none|Translator-specific parameters (for example model, apiVersion).| + +

CreateLLMProviderAPIKeyRequest

+ + + + + + +```json +{ + "id": "production-key", + "displayName": "Production Key", + "expiresAt": "2026-12-31T23:59:59Z", + "issuer": "api-platform-devportal", + "allowedTargets": "dev_gateway,test_gateway" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique identifier for the API key within the LLM provider. If not provided, generated from displayName.| +|displayName|string|true|none|Human-readable name for the API key| +|expiresAt|string(date-time)|false|none|Optional expiration time in ISO 8601 format| +|issuer|string¦null|false|none|Identifier of the developer portal that provisioned this API key. Null if not provided.| +|allowedTargets|string¦null|false|none|Comma-separated list of gateways this key is valid for.
Use 'ALL' to allow all targets (default).| + +

CreateLLMProviderAPIKeyResponse

+ + + + + + +```json +{ + "status": "success", + "message": "API key created and broadcasted to gateways successfully", + "id": "production-key", + "apiKey": "REDACTED_API_KEY" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|status|string|true|none|Status of the operation| +|message|string|true|none|Detailed message about the operation result| +|id|string|true|none|Unique identifier of the generated key| +|apiKey|string|true|none|The generated API key value — 64 hexadecimal characters, returned only in this creation response and never retrievable afterwards. The example value is a non-functional placeholder.| + +

CreateLLMProxyAPIKeyRequest

+ + + + + + +```json +{ + "id": "production-key", + "displayName": "Production Key", + "expiresAt": "2026-12-31T23:59:59Z", + "issuer": "api-platform-devportal", + "allowedTargets": "dev_gateway,test_gateway" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique identifier for the API key within the LLM proxy. If not provided, generated from displayName.| +|displayName|string|true|none|Human-readable name for the API key| +|expiresAt|string(date-time)|false|none|Optional expiration time in ISO 8601 format| +|issuer|string¦null|false|none|Identifier of the developer portal that provisioned this API key. Null if not provided.| +|allowedTargets|string¦null|false|none|Comma-separated list of gateways this key is valid for.
Use 'ALL' to allow all targets (default).| + +

CreateLLMProxyAPIKeyResponse

+ + + + + + +```json +{ + "status": "success", + "message": "API key created and broadcasted to gateways successfully", + "id": "production-key", + "apiKey": "REDACTED_API_KEY" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|status|string|true|none|Status of the operation| +|message|string|true|none|Detailed message about the operation result| +|id|string|true|none|Unique identifier of the generated key| +|apiKey|string|true|none|The generated API key value — 64 hexadecimal characters, returned only in this creation response and never retrievable afterwards. The example value is a non-functional placeholder.| + +

MCPProxy

+ + + + + + +```json +{ + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "createdBy": "john.doe", + "readOnly": false, + "updatedBy": "john.doe", + "version": "v1.0", + "projectId": "default-project", + "context": "/", + "vhost": "mcp.gw.com", + "upstream": { + "main": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + }, + "sandbox": { + "url": "http://prod-backend:5000/api/v2", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } + } + }, + "mcpSpecVersion": "2025-06-18", + "policies": [ + { + "executionCondition": "request.header.x-custom == 'enabled'", + "name": "SET_HEADER", + "params": { + "key": "MyHeader", + "value": "MyValue" + }, + "version": "v1" + } + ], + "kind": "Mcp", + "capabilities": { + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] + }, + "associatedGateways": [ + { + "id": "prod-eu" + }, + { + "id": "prod-us" + } + ], + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Unique handle for the proxy| +|displayName|string|true|none|Human-readable MCP proxy name| +|description|string|false|none|Description of the MCP proxy| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|readOnly|boolean|false|read-only|True if the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane; false for control-plane created artifacts.| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource. Only present in the detail response (GET /mcp-proxies/{id}), omitted from list responses.| +|version|string|true|none|Semantic version of the MCP proxy| +|projectId|string|false|none|Handle (URL-friendly slug) of the project this proxy belongs to| +|context|string|false|none|Base path for all routes exposed by this proxy. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default.| +|vhost|string|false|none|Virtual host name used for routing. Supports standard domain names, subdomains, or wildcard domains. Must follow RFC-compliant hostname rules. Wildcards are only allowed in the left-most label (e.g., *.example.com).| +|upstream|[Upstream](#schemaupstream)|true|none|Upstream backend configuration with main and sandbox endpoints| +|mcpSpecVersion|string|false|none|MCP specification version supported by this proxy| +|policies|[[Policy](#schemapolicy)]|false|none|List of policies to be applied| +|kind|string|false|none|Kind of the API based on its communication protocol or architectural style| +|capabilities|[MCPProxyCapabilities](#schemamcpproxycapabilities)|false|none|List of capabilities supported by this proxy. This will be stored as-is and can be used in the future if we need this for governance purposes| +|associatedGateways|[[AssociatedGateway](#schemaassociatedgateway)]|false|none|Optional list of gateways this MCP proxy can be deployed to, along with per-gateway configuration overrides. This field is optional; omitting it does not change existing behaviour.| +|createdAt|string(date-time)|false|read-only|Timestamp when the resource was created| +|updatedAt|string(date-time)|false|read-only|Timestamp when the resource was last updated| + +#### Enumerated Values + +|Property|Value| +|---|---| +|mcpSpecVersion|2025-06-18| +|mcpSpecVersion|2025-11-25| + +

MCPProxyListItem

+ + + + + + +```json +{ + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "createdBy": "john.doe", + "context": "/weather-mcp-proxy", + "version": "v1.0", + "projectId": "default-project", + "status": "deployed", + "mcpSpecVersion": "2025-11-25", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|displayName|string|true|none|Human-readable name for the MCP proxy| +|description|string|false|none|none| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|context|string|false|none|Context path where the proxy is exposed| +|version|string|false|none|none| +|projectId|string|false|none|Handle (URL-friendly slug) of the project this proxy belongs to| +|status|string|false|none|none| +|mcpSpecVersion|string|false|none|none| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| +|readOnly|boolean|false|none|True when the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane.| + +#### Enumerated Values + +|Property|Value| +|---|---| +|status|pending| +|status|deployed| +|status|failed| + +

MCPProxyListResponse

+ + + + + + +```json +{ + "count": 2, + "list": [ + { + "id": "weather-mcp-proxy", + "displayName": "Weather Server", + "description": "An MCP server which provides weather information", + "createdBy": "john.doe", + "context": "/weather-mcp-proxy", + "version": "v1.0", + "projectId": "default-project", + "status": "deployed", + "mcpSpecVersion": "2025-11-25", + "createdAt": "2025-11-25T10:30:00Z", + "updatedAt": "2025-11-25T10:30:00Z", + "readOnly": false + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|none| +|list|[[MCPProxyListItem](#schemamcpproxylistitem)]|true|none|none| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

MCPServerInfoFetchRequest

+ + + + + + +```json +{ + "url": "https://mcp.server.com/mcp", + "proxyId": "my-mcp-proxy", + "auth": { + "type": "api-key", + "header": "X-API-Key", + "value": "my-api-key-value" + } +} + +``` + +Target MCP server to introspect. Provide exactly one of `url` (a direct backend URL, +optionally with `auth`) or `proxyId` (refetch using a stored proxy configuration) — never +both. `auth` must not be sent together with `proxyId`; the stored auth is always used in +the refetch flow. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|url|string(uri)|false|none|Endpoint URL of the MCP server to fetch information from. Mutually exclusive
with `proxyId`; exactly one of the two must be provided.| +|proxyId|string|false|none|MCP proxy handle (identifier) for refresh operations. When provided, the server
fetches URL and auth from the stored proxy configuration. Mutually exclusive
with `url`; exactly one of the two must be provided.| +|auth|[UpstreamAuth](#schemaupstreamauth)|false|none|Authentication configuration for the fetch request. Allowed only alongside
`url` (initial creation flow); sending it with `proxyId` is rejected, as the
stored auth is used in the refetch flow.| + +oneOf + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| + +xor + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|object|false|none|none| + +not + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|» *anonymous*|object|false|none|none| + +

MCPServerInfoFetchResponse

+ + + + + + +```json +{ + "serverInfo": {}, + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|serverInfo|object|false|none|none| +|tools|[object]|false|none|none| +|resources|[object]|false|none|none| +|prompts|[object]|false|none|none| + +

MCPProxyCapabilities

+ + + + + + +```json +{ + "tools": [ + {} + ], + "resources": [ + {} + ], + "prompts": [ + {} + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|tools|[object]|false|none|List of tool capabilities supported by this proxy| +|resources|[object]|false|none|List of resource capabilities supported by this proxy| +|prompts|[object]|false|none|List of prompt capabilities supported by this proxy| + +

SecretCreateRequest

+ + + + + + +```json +{ + "id": "wso2-openai-key", + "displayName": "WSO2 OpenAI API Key", + "description": "Primary API key for WSO2 OpenAI integration", + "value": "sk-xxx", + "type": "GENERIC" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Handle (slug) used in {{ secret "id" }} placeholders. Immutable after creation.| +|displayName|string|true|none|Human-readable name for the secret| +|description|string|false|none|none| +|value|string|true|write-only|Plaintext secret value — encrypted at rest, never returned in any response| +|type|string|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|type|GENERIC| +|type|CERTIFICATE| + +

SecretUpdateRequest

+ + + + + + +```json +{ + "id": "wso2-openai-key", + "displayName": "string", + "description": "string", + "value": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Secret handle — if provided, must match the path parameter; returns 400 if they differ. The handle is immutable and cannot be changed via update.| +|displayName|string|true|none|Human-readable name for the secret| +|description|string|false|none|none| +|value|string|true|write-only|New plaintext secret value — re-encrypted at rest| + +

SecretResponse

+ + + + + + +```json +{ + "id": "wso2-openai-key", + "displayName": "WSO2 OpenAI API Key", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +Returned on create (201) and rotate (200). The plaintext value is never included. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|displayName|string|true|none|Human-readable name for the secret| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|updatedBy|string|false|read-only|User identifier of the user who last updated this resource| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +

SecretSummary

+ + + + + + +```json +{ + "id": "wso2-openai-key", + "displayName": "WSO2 OpenAI API Key", + "description": "string", + "type": "GENERIC", + "provider": "IN_BUILT", + "status": "ACTIVE", + "hash": "hmac-sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576d4b3d4c57e3f428a", + "createdBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} + +``` + +Secret metadata — never includes the plaintext value. + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|displayName|string|true|none|Human-readable name for the secret| +|description|string|false|none|none| +|type|string|false|none|none| +|provider|string|false|none|none| +|status|string|false|none|none| +|hash|string|false|none|none| +|createdBy|string|false|read-only|User identifier of the user who created this resource| +|createdAt|string(date-time)|false|none|none| +|updatedAt|string(date-time)|false|none|none| + +#### Enumerated Values + +|Property|Value| +|---|---| +|type|GENERIC| +|type|CERTIFICATE| +|provider|IN_BUILT| +|status|ACTIVE| +|status|DEPRECATED| + +

SecretListResponse

+ + + + + + +```json +{ + "count": 0, + "list": [ + { + "id": "wso2-openai-key", + "displayName": "WSO2 OpenAI API Key", + "description": "string", + "type": "GENERIC", + "provider": "IN_BUILT", + "status": "ACTIVE", + "hash": "hmac-sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576d4b3d4c57e3f428a", + "createdBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of secrets in current response| +|list|[[SecretSummary](#schemasecretsummary)]|true|none|[Secret metadata — never includes the plaintext value.]| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

GatewayTokenListResponse

+ + + + + + +```json +{ + "count": 0, + "list": [ + { + "id": "abc12345-f678-90de-f123-456789abcdef", + "status": "active", + "createdAt": "2025-10-14T10:30:00Z", + "revokedAt": null + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of tokens in current response| +|list|[[TokenInfoResponse](#schematokeninforesponse)]|true|none|List of active tokens| +|pagination|[Pagination](#schemapagination)|true|none|none| + +

CustomPolicyListResponse

+ + + + + + +```json +{ + "count": 0, + "list": [ + { + "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "organizationUuid": "bc554ded-7e40-44a7-b397-48480793ad03", + "name": "rate-limit-custom", + "version": "1.0.0", + "description": "Custom rate limiting policy", + "policyDefinition": {}, + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|count|integer|true|none|Number of custom policies in current response| +|list|[[CustomPolicyResponse](#schemacustompolicyresponse)]|true|none|List of custom policies| +|pagination|[Pagination](#schemapagination)|true|none|none| diff --git a/docs/rest-apis/platform-api/secrets.md b/docs/rest-apis/platform-api/secrets.md new file mode 100644 index 0000000000..f5c58d4cb1 --- /dev/null +++ b/docs/rest-apis/platform-api/secrets.md @@ -0,0 +1,625 @@ +

Secrets

+ +Encrypted secret management — create, rotate, and delete organization-scoped secrets referenced via {{ secret "name" }} placeholders + +## Create a secret + + + +`POST /secrets` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/secrets \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: multipart/form-data' \ + -H 'Accept: application/json' \ + -F 'id=wso2-openai-key' \ + -F 'displayName=WSO2 OpenAI API Key' \ + -F 'description=Primary API key for WSO2 OpenAI integration' \ + -F 'value=sk-xxx' \ + -F 'type=GENERIC' + +``` + +Create a new encrypted secret scoped to the organization. The plaintext value is never returned. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[SecretCreateRequest](schemas.md#schemasecretcreaterequest)|true|none| + +> Example responses +> +> 201 Response + +```json +{ + "id": "wso2-openai-key", + "displayName": "WSO2 OpenAI API Key", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "SERVICE_UNAVAILABLE", + "message": "Secrets management is not configured.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Secret created successfully.|[SecretResponse](schemas.md#schemasecretresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. The secrets management feature is not configured.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List secrets + + + +`GET /secrets` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/secrets \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns metadata for all secrets in the organization. The plaintext value is +never included in list or get responses. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| +|updatedAfter|query|string(date-time)|false|RFC3339 timestamp — return only secrets updated after this time. Used by GW controller for incremental polling.| + +> Example responses +> +> 200 Response + +```json +{ + "count": 0, + "list": [ + { + "id": "wso2-openai-key", + "displayName": "WSO2 OpenAI API Key", + "description": "string", + "type": "GENERIC", + "provider": "IN_BUILT", + "status": "ACTIVE", + "hash": "hmac-sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576d4b3d4c57e3f428a", + "createdBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "SERVICE_UNAVAILABLE", + "message": "Secrets management is not configured.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of secret metadata|[SecretListResponse](schemas.md#schemasecretlistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. The secrets management feature is not configured.|[Error](schemas.md#schemaerror)| + +## Get a secret by handle + + + +`GET /secrets/{secretId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/secrets/{secretId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns metadata for a single secret. The plaintext value is never returned. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|secretId|path|string|true|The secret handle| + +> Example responses +> +> 200 Response + +```json +{ + "id": "wso2-openai-key", + "displayName": "WSO2 OpenAI API Key", + "description": "string", + "type": "GENERIC", + "provider": "IN_BUILT", + "status": "ACTIVE", + "hash": "hmac-sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe04294e576d4b3d4c57e3f428a", + "createdBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "SERVICE_UNAVAILABLE", + "message": "Secrets management is not configured.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Secret metadata|[SecretSummary](schemas.md#schemasecretsummary)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. The secrets management feature is not configured.|[Error](schemas.md#schemaerror)| + +## Rotate a secret value + + + +`PUT /secrets/{secretId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/secrets/{secretId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: multipart/form-data' \ + -H 'Accept: application/json' \ + -F 'id=wso2-openai-key' \ + -F 'displayName=string' \ + -F 'description=string' \ + -F 'value=string' + +``` + +Re-encrypts and stores a new value for an existing secret. The handle is immutable +so all `{{ secret "handle" }}` placeholder references across resources remain valid +without modification. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|secretId|path|string|true|The secret handle| +|body|body|[SecretUpdateRequest](schemas.md#schemasecretupdaterequest)|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "id": "wso2-openai-key", + "displayName": "WSO2 OpenAI API Key", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "SERVICE_UNAVAILABLE", + "message": "Secrets management is not configured.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Secret rotated successfully.|[SecretResponse](schemas.md#schemasecretresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. The secrets management feature is not configured.|[Error](schemas.md#schemaerror)| + +## Delete a secret + + + +`DELETE /secrets/{secretId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/secrets/{secretId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Soft-deletes a secret by marking it as DEPRECATED. Returns 409 if the secret is +still referenced by any LLM provider or API configuration. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|secretId|path|string|true|The secret handle| + +> Example responses +> +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "SECRET_IN_USE", + "message": "The secret is referenced by one or more active resources.", + "details": { + "references": [ + { + "type": "llm_provider", + "handle": "wso2-openai-provider", + "name": "WSO2 OpenAI Provider" + } + ] + } +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +> 503 Response + +```json +{ + "status": "error", + "code": "SERVICE_UNAVAILABLE", + "message": "Secrets management is not configured.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Secret deleted successfully|None| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The secret is referenced by one or more active resources.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| +|503|[Service Unavailable](https://tools.ietf.org/html/rfc7231#section-6.6.4)|Service Unavailable. The secrets management feature is not configured.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/subscriptionplans.md b/docs/rest-apis/platform-api/subscriptionplans.md new file mode 100644 index 0000000000..9c0390dfcc --- /dev/null +++ b/docs/rest-apis/platform-api/subscriptionplans.md @@ -0,0 +1,610 @@ +

SubscriptionPlans

+ +Subscription plan management operations — rate-limit tiers that subscriptions are created against + +## Create subscription plan + + + +`POST /subscription-plans` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/subscription-plans \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates an organization-scoped subscription plan. + +> Payload + +```json +{ + "id": "gold", + "displayName": "Gold", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "status": "ACTIVE" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[CreateSubscriptionPlanRequest](schemas.md#schemacreatesubscriptionplanrequest)|true|none| + +> Example responses +> +> 201 Response + +```json +{ + "id": "string", + "displayName": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Subscription plan created successfully|[SubscriptionPlan](schemas.md#schemasubscriptionplan)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List subscription plans + + + +`GET /subscription-plans` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/subscription-plans \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns subscription plans for the organization. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "string", + "displayName": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of subscription plans|[SubscriptionPlanListResponse](schemas.md#schemasubscriptionplanlistresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get subscription plan by ID + + + +`GET /subscription-plans/{subscriptionPlanId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/subscription-plans/{subscriptionPlanId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriptionPlanId|path|string|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "id": "string", + "displayName": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Subscription plan details|[SubscriptionPlan](schemas.md#schemasubscriptionplan)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update subscription plan + + + +`PUT /subscription-plans/{subscriptionPlanId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/subscription-plans/{subscriptionPlanId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +> Payload + +```json +{ + "id": "string", + "displayName": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "organizationId": "acme", + "status": "ACTIVE", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriptionPlanId|path|string|true|none| +|body|body|[SubscriptionPlan](schemas.md#schemasubscriptionplan)|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "id": "string", + "displayName": "string", + "limits": [ + { + "limitType": "REQUEST_COUNT", + "timeUnit": "HOUR", + "timeAmount": 1, + "limitCount": 10000, + "limitCountUnit": "string", + "stopOnQuotaReach": true + } + ], + "expiryTime": "2019-08-24T14:15:22Z", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Updated subscription plan|[SubscriptionPlan](schemas.md#schemasubscriptionplan)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete subscription plan + + + +`DELETE /subscription-plans/{subscriptionPlanId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/subscription-plans/{subscriptionPlanId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriptionPlanId|path|string|true|none| + +> Example responses +> +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Subscription plan deleted (no content)|None| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/docs/rest-apis/platform-api/subscriptions.md b/docs/rest-apis/platform-api/subscriptions.md new file mode 100644 index 0000000000..890c015d2b --- /dev/null +++ b/docs/rest-apis/platform-api/subscriptions.md @@ -0,0 +1,675 @@ +

Subscriptions

+ +Subscription management operations linking applications to APIs under a subscription plan + +## Create subscription + + + +`POST /subscriptions` + +> Code samples + +```shell + +curl -X POST https://localhost:9243/api/v0.9/subscriptions \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Creates a subscription for the specified artifact. +`subscriberId` identifies the unique subscriber for this artifact, allowing multiple subscribers to create subscriptions for the same artifact. + +> Payload + +```json +{ + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "user-123", + "applicationId": "my-app-handle", + "subscriptionPlanId": "gold", + "status": "ACTIVE" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|body|body|[CreateSubscriptionRequest](schemas.md#schemacreatesubscriptionrequest)|true|none| + +> Example responses +> +> 201 Response + +```json +{ + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "string", + "applicationId": "my-app-handle", + "subscriptionToken": "string", + "subscriptionPlanId": "gold", + "subscriptionPlanName": "string", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 409 Response + +```json +{ + "status": "error", + "code": "CONFLICT", + "message": "The request conflicts with the current state of the resource." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|201|[Created](https://tools.ietf.org/html/rfc7231#section-6.3.2)|Subscription created successfully|[Subscription](schemas.md#schemasubscription)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|409|[Conflict](https://tools.ietf.org/html/rfc7231#section-6.5.8)|Conflict. The request conflicts with the current state of the resource.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +### Response Headers + +|Status|Header|Type|Format|Description| +|---|---|---|---|---| +|201|Location|string|uri|URL of the newly created resource.| + +## List subscriptions + + + +`GET /subscriptions` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/subscriptions \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns subscriptions filtered by artifact and/or application. +Optional query parameters artifactId, subscriberId, applicationId and status filter the list. +Supports pagination via limit and offset. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|artifactId|query|string|false|Filter by artifact ID (UUID or handle)| +|subscriberId|query|string|false|Filter by subscriber ID| +|applicationId|query|string|false|Filter by application ID| +|status|query|string|false|Filter by status (ACTIVE, INACTIVE, REVOKED)| +|limit|query|integer|false|Maximum number of items to return per page.| +|offset|query|integer|false|Zero-based index of the first item to return.| + +#### Enumerated Values + +|Parameter|Value| +|---|---| +|status|ACTIVE| +|status|INACTIVE| +|status|REVOKED| + +> Example responses +> +> 200 Response + +```json +{ + "list": [ + { + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "string", + "applicationId": "my-app-handle", + "subscriptionToken": "string", + "subscriptionPlanId": "gold", + "subscriptionPlanName": "string", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "count": 0, + "pagination": { + "total": 10, + "offset": 0, + "limit": 10 + } +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|List of subscriptions|[SubscriptionListResponse](schemas.md#schemasubscriptionlistresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Get subscription by ID + + + +`GET /subscriptions/{subscriptionId}` + +> Code samples + +```shell + +curl -X GET https://localhost:9243/api/v0.9/subscriptions/{subscriptionId} \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Returns a single subscription by ID, scoped to the organization in the access token. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriptionId|path|string(uuid)|true|Subscription UUID| + +> Example responses +> +> 200 Response + +```json +{ + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "string", + "applicationId": "my-app-handle", + "subscriptionToken": "string", + "subscriptionPlanId": "gold", + "subscriptionPlanName": "string", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Subscription details|[Subscription](schemas.md#schemasubscription)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Update subscription + + + +`PUT /subscriptions/{subscriptionId}` + +> Code samples + +```shell + +curl -X PUT https://localhost:9243/api/v0.9/subscriptions/{subscriptionId}?subscriberId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -d @payload.json + +``` + +Updates a subscription (e.g. status). +Query parameter `subscriberId` is required and must match the subscription's subscriber for access control. + +> Payload + +```json +{ + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "string", + "applicationId": "my-app-handle", + "subscriptionToken": "string", + "subscriptionPlanId": "gold", + "subscriptionPlanName": "string", + "organizationId": "acme", + "status": "ACTIVE", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriptionId|path|string(uuid)|true|Subscription UUID| +|subscriberId|query|string|true|Subscriber ID; must match the subscription's subscriberId.| +|body|body|[Subscription](schemas.md#schemasubscription)|true|none| + +> Example responses +> +> 200 Response + +```json +{ + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "artifactId": "my-rest-api", + "kind": "RestApi", + "subscriberId": "string", + "applicationId": "my-app-handle", + "subscriptionToken": "string", + "subscriptionPlanId": "gold", + "subscriptionPlanName": "string", + "organizationId": "acme", + "status": "ACTIVE", + "createdBy": "john.doe", + "updatedBy": "john.doe", + "createdAt": "2019-08-24T14:15:22Z", + "updatedAt": "2019-08-24T14:15:22Z" +} +``` + +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Updated subscription|[Subscription](schemas.md#schemasubscription)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| + +## Delete subscription + + + +`DELETE /subscriptions/{subscriptionId}` + +> Code samples + +```shell + +curl -X DELETE https://localhost:9243/api/v0.9/subscriptions/{subscriptionId}?subscriberId=string \ + -H 'Authorization: Bearer {access_token}' \ + -H 'Accept: application/json' + +``` + +Removes the subscription for the API. +Query parameter `subscriberId` is required and must match the subscription's subscriber for access control. + +### Authentication + + + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|subscriptionId|path|string(uuid)|true|Subscription UUID| +|subscriberId|query|string|true|Subscriber ID; must match the subscription's subscriberId.| + +> Example responses +> +> 400 Response + +```json +{ + "status": "error", + "code": "VALIDATION_FAILED", + "message": "The request failed validation.", + "errors": [ + { + "field": "", + "message": "" + } + ] +} +``` + +> 401 Response + +```json +{ + "status": "error", + "code": "UNAUTHORIZED", + "message": "Authorization header is required, or the token is invalid or expired." +} +``` + +> 403 Response + +```json +{ + "status": "error", + "code": "FORBIDDEN", + "message": "You do not have permission to perform this action." +} +``` + +> 404 Response + +```json +{ + "status": "error", + "code": "NOT_FOUND", + "message": "The specified resource does not exist." +} +``` + +> 500 Response + +```json +{ + "status": "error", + "code": "INTERNAL_ERROR", + "message": "An unexpected error occurred.", + "trackingId": "4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|204|[No Content](https://tools.ietf.org/html/rfc7231#section-6.3.5)|Subscription deleted (no content)|None| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad Request. Invalid request or validation error.|[Error](schemas.md#schemaerror)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized. Authentication credentials are missing or invalid.|[Error](schemas.md#schemaerror)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden. The authenticated user does not have permission to access this resource.|[Error](schemas.md#schemaerror)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found. The specified resource does not exist.|[Error](schemas.md#schemaerror)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal Server Error.|[Error](schemas.md#schemaerror)| diff --git a/platform-api/Makefile b/platform-api/Makefile index f9f1644d9c..26bcb8104e 100644 --- a/platform-api/Makefile +++ b/platform-api/Makefile @@ -34,7 +34,12 @@ MSSQL_PASSWORD ?= Strong!Passw0rd # On Apple Silicon, override with: MSSQL_IMAGE=mcr.microsoft.com/azure-sql-edge:latest MSSQL_IMAGE ?= mcr.microsoft.com/mssql/server:2022-latest -.PHONY: help build test push build-and-push-multiarch it it-sqlite it-postgres it-sqlserver it-all-dbs +# Doc generation +REPO_ROOT := $(shell git rev-parse --show-toplevel) +APIDOCS_TOOLS_DIR := $(REPO_ROOT)/tools/apidocs +DOCS_OUT_DIR := $(REPO_ROOT)/docs/rest-apis/platform-api + +.PHONY: help build test push build-and-push-multiarch it it-sqlite it-postgres it-sqlserver it-all-dbs generate generate-apidocs help: ## Show this help message @echo 'Platform API Build System (Version: $(VERSION))' @@ -105,6 +110,17 @@ generate: ## Generate API server code from OpenAPI spec @echo "Generating API models for the eventgateway plugin spec..." @go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.5.1 --config=oapi-codegen-eventgateway.yaml plugins/eventgateway/openapi.yaml +generate-apidocs: ## Generate REST API docs (Markdown) from the Platform API OpenAPI spec + @command -v widdershins >/dev/null 2>&1 || { echo "widdershins not found, installing..."; npm install -g widdershins; } + @echo "Generating REST API docs..." + @widdershins resources/openapi.yaml -o /tmp/platform-api-openapi.md \ + --omitHeader --summary \ + --language_tabs 'shell:Shell:curl' \ + --user_templates $(APIDOCS_TOOLS_DIR)/widdershins_templates/ + @bash $(APIDOCS_TOOLS_DIR)/split-openapi.sh /tmp/platform-api-openapi.md $(DOCS_OUT_DIR) + @rm -f /tmp/platform-api-openapi.md + @echo "REST API docs generated at $(DOCS_OUT_DIR)" + push: ## Push Docker image to registry @echo "Pushing Docker images..." docker push $(IMAGE_NAME):$(VERSION) diff --git a/platform-api/api/generated.go b/platform-api/api/generated.go index d0b5a2021a..a44ee4f2bd 100644 --- a/platform-api/api/generated.go +++ b/platform-api/api/generated.go @@ -788,7 +788,7 @@ type CreateLLMProviderAPIKeyRequest struct { // CreateLLMProviderAPIKeyResponse defines model for CreateLLMProviderAPIKeyResponse. type CreateLLMProviderAPIKeyResponse struct { - // ApiKey The generated API key value (shown only once, 64 hexadecimal characters) + // ApiKey The generated API key value — 64 hexadecimal characters, returned only in this creation response and never retrievable afterwards. The example value is a non-functional placeholder. ApiKey string `binding:"required" json:"apiKey" yaml:"apiKey"` // Id Unique identifier of the generated key @@ -853,7 +853,7 @@ type CreateLLMProxyAPIKeyRequest struct { // CreateLLMProxyAPIKeyResponse defines model for CreateLLMProxyAPIKeyResponse. type CreateLLMProxyAPIKeyResponse struct { - // ApiKey The generated API key value (shown only once, 64 hexadecimal characters) + // ApiKey The generated API key value — 64 hexadecimal characters, returned only in this creation response and never retrievable afterwards. The example value is a non-functional placeholder. ApiKey string `binding:"required" json:"apiKey" yaml:"apiKey"` // Id Unique identifier of the generated key @@ -1312,7 +1312,7 @@ type LLMProvider struct { // AssociatedGateways Optional list of gateways this LLM provider can be deployed to, along with per-gateway configuration overrides. This field is optional; omitting it does not change existing behaviour. AssociatedGateways *[]AssociatedGateway `json:"associatedGateways,omitempty" yaml:"associatedGateways,omitempty"` - // Context Base path for all REST API routes (must start with /, no trailing slash) + // Context Base path for all routes exposed by this proxy. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default. Context *string `json:"context,omitempty" yaml:"context,omitempty"` // CreatedAt Timestamp when the resource was created @@ -1570,7 +1570,7 @@ type LLMProxy struct { // AssociatedGateways Optional list of gateways this LLM proxy can be deployed to, along with per-gateway configuration overrides. This field is optional; omitting it does not change existing behaviour. AssociatedGateways *[]AssociatedGateway `json:"associatedGateways,omitempty" yaml:"associatedGateways,omitempty"` - // Context Base path for all REST API routes (must start with /, no trailing slash) + // Context Base path for all routes exposed by this proxy. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default. Context *string `json:"context,omitempty" yaml:"context,omitempty"` // CreatedAt Timestamp when the resource was created @@ -1601,7 +1601,7 @@ type LLMProxy struct { // Deprecated: this property has been marked as deprecated upstream, but no `x-deprecated-reason` was set Policies *[]LLMPolicy `json:"policies,omitempty" yaml:"policies,omitempty"` - // ProjectId UUID of the project this proxy belongs to + // ProjectId Handle (URL-friendly slug) of the project this proxy belongs to ProjectId string `binding:"required" json:"projectId" yaml:"projectId"` Provider LLMProxyProvider `json:"provider" yaml:"provider"` @@ -1660,7 +1660,7 @@ type LLMProxyListItem struct { DisplayName string `binding:"required" json:"displayName" yaml:"displayName"` Id *string `json:"id,omitempty" yaml:"id,omitempty"` - // ProjectId UUID of the project this proxy belongs to + // ProjectId Handle (URL-friendly slug) of the project this proxy belongs to ProjectId *string `json:"projectId,omitempty" yaml:"projectId,omitempty"` // Provider Unique id of a deployed llm provider @@ -1719,7 +1719,7 @@ type MCPProxy struct { AssociatedGateways *[]AssociatedGateway `json:"associatedGateways,omitempty" yaml:"associatedGateways,omitempty"` Capabilities *MCPProxyCapabilities `json:"capabilities,omitempty" yaml:"capabilities,omitempty"` - // Context Base path for all REST API routes (must start with /, no trailing slash) + // Context Base path for all routes exposed by this proxy. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default. Context *string `json:"context,omitempty" yaml:"context,omitempty"` // CreatedAt Timestamp when the resource was created @@ -1746,7 +1746,7 @@ type MCPProxy struct { // Policies List of policies to be applied Policies *[]Policy `json:"policies,omitempty" yaml:"policies,omitempty"` - // ProjectId UUID of the project this proxy belongs to + // ProjectId Handle (URL-friendly slug) of the project this proxy belongs to ProjectId *string `json:"projectId,omitempty" yaml:"projectId,omitempty"` // ReadOnly True if the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane; false for control-plane created artifacts. @@ -1798,7 +1798,7 @@ type MCPProxyListItem struct { Id *string `json:"id,omitempty" yaml:"id,omitempty"` McpSpecVersion *string `json:"mcpSpecVersion,omitempty" yaml:"mcpSpecVersion,omitempty"` - // ProjectId UUID of the project this proxy belongs to + // ProjectId Handle (URL-friendly slug) of the project this proxy belongs to ProjectId *string `json:"projectId,omitempty" yaml:"projectId,omitempty"` // ReadOnly True when the artifact originated from a data-plane gateway (origin gateway_api) and is read-only in the control plane. @@ -2499,7 +2499,7 @@ type TokenRotationResponse struct { // Message Informational message about token rotation Message *string `json:"message,omitempty" yaml:"message,omitempty"` - // Token Plain-text new authentication token (only exposed once during rotation) + // Token Plain-text new authentication token (only exposed once during rotation). The example value is a non-functional placeholder. Token *string `json:"token,omitempty" yaml:"token,omitempty"` } @@ -2542,10 +2542,12 @@ type UpdateAPIKeyResponseStatus string // Upstream Upstream backend configuration with main and sandbox endpoints type Upstream struct { - // Main Upstream endpoint configuration (single target or reference) + // Main Upstream endpoint configuration. Provide exactly one of `url` (a direct backend URL) or + // `ref` (a reference to a predefined upstream definition) — never both. Main UpstreamDefinition `json:"main" yaml:"main"` - // Sandbox Upstream endpoint configuration (single target or reference) + // Sandbox Upstream endpoint configuration. Provide exactly one of `url` (a direct backend URL) or + // `ref` (a reference to a predefined upstream definition) — never both. Sandbox *UpstreamDefinition `json:"sandbox,omitempty" yaml:"sandbox,omitempty"` } @@ -2564,15 +2566,16 @@ type UpstreamAuth struct { // UpstreamAuthType Authentication type type UpstreamAuthType string -// UpstreamDefinition Upstream endpoint configuration (single target or reference) +// UpstreamDefinition Upstream endpoint configuration. Provide exactly one of `url` (a direct backend URL) or +// `ref` (a reference to a predefined upstream definition) — never both. type UpstreamDefinition struct { // Auth Authentication configuration for upstream endpoints Auth *UpstreamAuth `json:"auth,omitempty" yaml:"auth,omitempty"` - // Ref Reference to a predefined upstreamDefinition + // Ref Reference to a predefined upstreamDefinition. Mutually exclusive with `url`. Ref *string `json:"ref,omitempty" yaml:"ref,omitempty"` - // Url Direct backend URL to route traffic to + // Url Direct backend URL to route traffic to. Mutually exclusive with `ref`. Url *string `json:"url,omitempty" yaml:"url,omitempty"` union json.RawMessage } @@ -2695,6 +2698,9 @@ type BadRequest = Error // Conflict The single error shape returned by every failed request across the API. type Conflict = Error +// DeploymentActiveConflict The single error shape returned by every failed request across the API. +type DeploymentActiveConflict = Error + // Forbidden The single error shape returned by every failed request across the API. type Forbidden = Error @@ -2715,7 +2721,7 @@ type Unauthorized = Error // ListApplicationsParams defines parameters for ListApplications. type ListApplicationsParams struct { - // ProjectId **Project ID** consisting of the **handle** (unique slug identifier) of the Project to filter APIs by. + // ProjectId **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. ProjectId ProjectIdQ `form:"projectId" json:"projectId" yaml:"projectId"` // Limit Maximum number of items to return per page. @@ -2829,7 +2835,7 @@ type ListGatewayTokensParams struct { // ListLLMProviderTemplatesParams defines parameters for ListLLMProviderTemplates. type ListLLMProviderTemplatesParams struct { - // Query URL-encoded search DSL. `query=latest:true` lists only the latest version of each family; `query=groupId:` lists that family's versions; adding `&version:` returns the single full template for that version. Terms are `&`-separated `key:value` pairs and the whole value is percent-encoded (e.g. groupId%3Aopenai%26version%3Av2.0). + // Query URL-encoded search DSL. `query=latest:true` lists only the latest version of each family; `query=groupId:` lists that family's versions; adding `&version:` returns the single full template for that version. Terms are `&`-separated `key:value` pairs and the whole value is percent-encoded (e.g. groupId%3Awso2-openai%26version%3Av2.0). Query *string `form:"query,omitempty" json:"query,omitempty" yaml:"query,omitempty"` // Limit Maximum number of items to return per page. @@ -2853,6 +2859,7 @@ type CopyLLMProviderTemplateVersionParams struct { // SetLLMProviderTemplateVersionEnabledJSONBody defines parameters for SetLLMProviderTemplateVersionEnabled. type SetLLMProviderTemplateVersionEnabledJSONBody struct { + // Enabled Set to true to enable this template version, false to disable it. Enabled bool `json:"enabled" yaml:"enabled"` } @@ -2876,7 +2883,7 @@ type ListLLMProviderAPIKeysParams struct { // GetLLMProviderDeploymentsParams defines parameters for GetLLMProviderDeployments. type GetLLMProviderDeploymentsParams struct { - // GatewayId **Gateway ID** consisting of the **UUID** of the Gateway to filter status by. + // GatewayId **Gateway ID** (handle — unique slug identifier) of the Gateway to filter deployments by. GatewayId *GatewayIdQ `form:"gatewayId,omitempty" json:"gatewayId,omitempty" yaml:"gatewayId,omitempty"` // Status Filter deployments by status (DEPLOYED, UNDEPLOYED, DEPLOYING, UNDEPLOYING, FAILED, or ARCHIVED) @@ -2894,13 +2901,13 @@ type GetLLMProviderDeploymentsParamsStatus string // RestoreLLMProviderDeploymentParams defines parameters for RestoreLLMProviderDeployment. type RestoreLLMProviderDeploymentParams struct { - // GatewayId UUID of the gateway (validated against deployment's bound gateway) + // GatewayId Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway) GatewayId string `form:"gatewayId" json:"gatewayId" yaml:"gatewayId"` } // UndeployLLMProviderDeploymentParams defines parameters for UndeployLLMProviderDeployment. type UndeployLLMProviderDeploymentParams struct { - // GatewayId UUID of the gateway (validated against deployment's bound gateway) + // GatewayId Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway) GatewayId string `form:"gatewayId" json:"gatewayId" yaml:"gatewayId"` } @@ -2915,7 +2922,7 @@ type ListLLMProxiesByProviderParams struct { // ListLLMProxiesParams defines parameters for ListLLMProxies. type ListLLMProxiesParams struct { - // ProjectId **Project ID** consisting of the **handle** (unique slug identifier) of the Project to filter APIs by. + // ProjectId **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. ProjectId ProjectIdQ `form:"projectId" json:"projectId" yaml:"projectId"` // Limit Maximum number of items to return per page. @@ -2936,7 +2943,7 @@ type ListLLMProxyAPIKeysParams struct { // GetLLMProxyDeploymentsParams defines parameters for GetLLMProxyDeployments. type GetLLMProxyDeploymentsParams struct { - // GatewayId **Gateway ID** consisting of the **UUID** of the Gateway to filter status by. + // GatewayId **Gateway ID** (handle — unique slug identifier) of the Gateway to filter deployments by. GatewayId *GatewayIdQ `form:"gatewayId,omitempty" json:"gatewayId,omitempty" yaml:"gatewayId,omitempty"` // Status Filter deployments by status (DEPLOYED, UNDEPLOYED, DEPLOYING, UNDEPLOYING, FAILED, or ARCHIVED) @@ -2954,19 +2961,19 @@ type GetLLMProxyDeploymentsParamsStatus string // RestoreLLMProxyDeploymentParams defines parameters for RestoreLLMProxyDeployment. type RestoreLLMProxyDeploymentParams struct { - // GatewayId UUID of the gateway (validated against deployment's bound gateway) + // GatewayId Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway) GatewayId string `form:"gatewayId" json:"gatewayId" yaml:"gatewayId"` } // UndeployLLMProxyDeploymentParams defines parameters for UndeployLLMProxyDeployment. type UndeployLLMProxyDeploymentParams struct { - // GatewayId UUID of the gateway (validated against deployment's bound gateway) + // GatewayId Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway) GatewayId string `form:"gatewayId" json:"gatewayId" yaml:"gatewayId"` } // ListMCPProxiesParams defines parameters for ListMCPProxies. type ListMCPProxiesParams struct { - // ProjectId **Project ID** consisting of the **handle** (unique slug identifier) of the Project to filter APIs by. + // ProjectId **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. ProjectId ProjectIdQ `form:"projectId" json:"projectId" yaml:"projectId"` // Limit Maximum number of items to return per page. @@ -2978,7 +2985,7 @@ type ListMCPProxiesParams struct { // GetMCPProxyDeploymentsParams defines parameters for GetMCPProxyDeployments. type GetMCPProxyDeploymentsParams struct { - // GatewayId **Gateway ID** consisting of the **UUID** of the Gateway to filter status by. + // GatewayId **Gateway ID** (handle — unique slug identifier) of the Gateway to filter deployments by. GatewayId *GatewayIdQ `form:"gatewayId,omitempty" json:"gatewayId,omitempty" yaml:"gatewayId,omitempty"` // Status Filter deployments by status (DEPLOYED, UNDEPLOYED, DEPLOYING, UNDEPLOYING, FAILED, or ARCHIVED) @@ -3057,7 +3064,7 @@ type ListProjectsParamsSortOrder string // ListRESTAPIsParams defines parameters for ListRESTAPIs. type ListRESTAPIsParams struct { - // ProjectId **Project ID** consisting of the **handle** (unique slug identifier) of the Project to filter APIs by. + // ProjectId **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. ProjectId ProjectIdQ `form:"projectId" json:"projectId" yaml:"projectId"` // Limit Maximum number of items to return per page. @@ -3084,7 +3091,7 @@ type ListRESTAPIsParamsSortOrder string // GetDeploymentsParams defines parameters for GetDeployments. type GetDeploymentsParams struct { - // GatewayId **Gateway ID** consisting of the **UUID** of the Gateway to filter status by. + // GatewayId **Gateway ID** (handle — unique slug identifier) of the Gateway to filter deployments by. GatewayId *GatewayIdQ `form:"gatewayId,omitempty" json:"gatewayId,omitempty" yaml:"gatewayId,omitempty"` // Status Filter deployments by status (DEPLOYED, UNDEPLOYED, DEPLOYING, UNDEPLOYING, FAILED, or ARCHIVED) diff --git a/platform-api/internal/handler/api.go b/platform-api/internal/handler/api.go index 415716987c..840a0fd200 100644 --- a/platform-api/internal/handler/api.go +++ b/platform-api/internal/handler/api.go @@ -77,6 +77,9 @@ func (h *APIHandler) CreateAPI(w http.ResponseWriter, r *http.Request) error { return apperror.ValidationFailed.New("At least one upstream endpoint (main or sandbox) is required"). WithLogMessage(fmt.Sprintf("no upstream endpoints provided for org %s", orgId)) } + if err := validateUpstreamDefinitions(req.Upstream); err != nil { + return err + } createdBy, err := resolveActorErr(r, h.identity, "create API") if err != nil { @@ -171,6 +174,9 @@ func (h *APIHandler) UpdateAPI(w http.ResponseWriter, r *http.Request) error { if isEmptyUpstreamDefinition(req.Upstream.Main) && (req.Upstream.Sandbox == nil || isEmptyUpstreamDefinition(*req.Upstream.Sandbox)) { return apperror.ValidationFailed.New("At least one upstream endpoint (main or sandbox) is required") } + if err := validateUpstreamDefinitions(req.Upstream); err != nil { + return err + } updatedBy, err := resolveActorErr(r, h.identity, "update API") if err != nil { @@ -290,11 +296,25 @@ func (h *APIHandler) RegisterRoutes(mux *http.ServeMux) { } func isEmptyUpstreamDefinition(definition api.UpstreamDefinition) bool { - if definition.Url != nil && *definition.Url != "" { - return false + return !hasUpstreamURL(definition) && !hasUpstreamRef(definition) +} + +func hasUpstreamURL(definition api.UpstreamDefinition) bool { + return definition.Url != nil && strings.TrimSpace(*definition.Url) != "" +} + +func hasUpstreamRef(definition api.UpstreamDefinition) bool { + return definition.Ref != nil && strings.TrimSpace(*definition.Ref) != "" +} + +// validateUpstreamDefinitions ensures every provided upstream definition specifies +// exactly one of url or ref, as required by the UpstreamDefinition schema. +func validateUpstreamDefinitions(upstream api.Upstream) error { + if hasUpstreamURL(upstream.Main) && hasUpstreamRef(upstream.Main) { + return apperror.ValidationFailed.New("The upstream main must specify either a url or a ref, not both.") } - if definition.Ref != nil && *definition.Ref != "" { - return false + if upstream.Sandbox != nil && hasUpstreamURL(*upstream.Sandbox) && hasUpstreamRef(*upstream.Sandbox) { + return apperror.ValidationFailed.New("The upstream sandbox must specify either a url or a ref, not both.") } - return true + return nil } diff --git a/platform-api/internal/repository/interfaces.go b/platform-api/internal/repository/interfaces.go index 123917b57e..5fb36bd79d 100644 --- a/platform-api/internal/repository/interfaces.go +++ b/platform-api/internal/repository/interfaces.go @@ -43,6 +43,7 @@ type OrganizationRepository interface { type ProjectRepository interface { CreateProject(project *model.Project) error GetProjectByUUID(projectId string) (*model.Project, error) + GetProjectByUUIDAndOrgID(projectId, orgID string) (*model.Project, error) GetProjectByNameAndOrgID(name, orgID string) (*model.Project, error) GetProjectByHandleAndOrgID(handle, orgID string) (*model.Project, error) GetProjectsByOrganizationID(orgID string) ([]*model.Project, error) diff --git a/platform-api/internal/repository/project.go b/platform-api/internal/repository/project.go index b99aca657b..d8e98a37fc 100644 --- a/platform-api/internal/repository/project.go +++ b/platform-api/internal/repository/project.go @@ -78,6 +78,32 @@ func (r *ProjectRepo) GetProjectByUUID(projectId string) (*model.Project, error) return project, nil } +// GetProjectByUUIDAndOrgID retrieves a project by ID scoped to an organization. +// Use this over GetProjectByUUID whenever the caller is serving a tenant request, +// so a UUID belonging to another organization resolves to nothing. +func (r *ProjectRepo) GetProjectByUUIDAndOrgID(projectId, orgID string) (*model.Project, error) { + project := &model.Project{} + query := ` + SELECT uuid, handle, display_name, organization_uuid, description, created_by, created_at, updated_by, updated_at + FROM projects + WHERE uuid = ? AND organization_uuid = ? + ` + var createdBy, updatedBy sql.NullString + err := r.db.QueryRow(r.db.Rebind(query), projectId, orgID).Scan( + &project.ID, &project.Handle, &project.Name, &project.OrganizationID, &project.Description, + &createdBy, &project.CreatedAt, &updatedBy, &project.UpdatedAt, + ) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, err + } + project.CreatedBy = createdBy.String + project.UpdatedBy = updatedBy.String + return project, nil +} + // GetProjectByNameAndOrgID retrieves a project by name within an organization func (r *ProjectRepo) GetProjectByNameAndOrgID(name, orgID string) (*model.Project, error) { project := &model.Project{} diff --git a/platform-api/internal/service/api.go b/platform-api/internal/service/api.go index 3c1e5248f6..46696c3ee1 100644 --- a/platform-api/internal/service/api.go +++ b/platform-api/internal/service/api.go @@ -1011,10 +1011,10 @@ func (s *APIService) isEmptyUpstream(upstream api.Upstream) bool { } func (s *APIService) isEmptyUpstreamDefinition(definition api.UpstreamDefinition) bool { - if definition.Url != nil && *definition.Url != "" { + if definition.Url != nil && strings.TrimSpace(*definition.Url) != "" { return false } - if definition.Ref != nil && *definition.Ref != "" { + if definition.Ref != nil && strings.TrimSpace(*definition.Ref) != "" { return false } return true diff --git a/platform-api/internal/service/llm.go b/platform-api/internal/service/llm.go index 6cc8f3b79a..eece1699a3 100644 --- a/platform-api/internal/service/llm.go +++ b/platform-api/internal/service/llm.go @@ -188,13 +188,54 @@ func NewLLMProxyService( } } -// toProxyAPI converts m via mapProxyModelToAPI and resolves its -// createdBy/updatedBy UUIDs to their raw external identity. -func (s *LLMProxyService) toProxyAPI(m *model.LLMProxy) (*api.LLMProxy, error) { +// resolveProjectHandle maps a stored project UUID to the project handle the +// API exposes as `projectId`. Every id-shaped field in this API is a handle +// (Create/List take a project handle), so responses must return one too — +// clients have no way to resolve a UUID back to a project. Mirrors +// ApplicationService.modelToApplicationResponseUnresolved. +// +// The lookup is scoped to orgUUID so a project UUID belonging to another +// organization never resolves — a stored UUID is not itself proof of tenancy. +// +// projectHandleCache is an optional per-request memo so a listing page doesn't +// re-query the same project once per item; pass nil for single-item responses. +func (s *LLMProxyService) resolveProjectHandle(orgUUID, projectUUID string, projectHandleCache map[string]string) (string, error) { + projectUUID = strings.TrimSpace(projectUUID) + if projectUUID == "" { + return "", nil + } + if handle, ok := projectHandleCache[projectUUID]; ok { + return handle, nil + } + if s.projectRepo == nil { + return "", fmt.Errorf("cannot resolve project handle: project repository unavailable") + } + project, err := s.projectRepo.GetProjectByUUIDAndOrgID(projectUUID, orgUUID) + if err != nil { + return "", fmt.Errorf("failed to resolve project: %w", err) + } + if project == nil { + return "", apperror.ProjectNotFound.New() + } + if projectHandleCache != nil { + projectHandleCache[projectUUID] = project.Handle + } + return project.Handle, nil +} + +// toProxyAPI converts m via mapProxyModelToAPI, resolves its project UUID to +// the project handle, and resolves its createdBy/updatedBy UUIDs to their raw +// external identity. +func (s *LLMProxyService) toProxyAPI(orgUUID string, m *model.LLMProxy) (*api.LLMProxy, error) { resp := mapProxyModelToAPI(m) if resp == nil { return nil, nil } + projectHandle, err := s.resolveProjectHandle(orgUUID, resp.ProjectId, nil) + if err != nil { + return nil, err + } + resp.ProjectId = projectHandle if err := s.identity.ResolveIdentityField(&resp.CreatedBy); err != nil { return nil, err } @@ -1528,7 +1569,7 @@ func (s *LLMProxyService) Create(orgUUID, createdBy string, req *api.LLMProxy) ( if created == nil { return nil, apperror.LLMProxyNotFound.New() } - return s.toProxyAPI(created) + return s.toProxyAPI(orgUUID, created) } func (s *LLMProxyService) List(orgUUID string, projectHandle *string, limit, offset int) (*api.LLMProxyListResponse, error) { @@ -1576,6 +1617,7 @@ func (s *LLMProxyService) List(orgUUID string, projectHandle *string, limit, off } resp.List = make([]api.LLMProxyListItem, 0, len(items)) createdByFields := make([]**string, 0, len(items)) + projectHandles := make(map[string]string) for _, p := range items { id := p.ID name := p.Name @@ -1587,7 +1629,10 @@ func (s *LLMProxyService) List(orgUUID string, projectHandle *string, limit, off contextValue = &v } version := p.Version - projectID := p.ProjectUUID + projectID, err := s.resolveProjectHandle(orgUUID, p.ProjectUUID, projectHandles) + if err != nil { + return nil, err + } provider := p.Configuration.Provider resp.List = append(resp.List, api.LLMProxyListItem{ Id: &id, @@ -1643,6 +1688,7 @@ func (s *LLMProxyService) ListByProvider(orgUUID, providerID string, limit, offs } resp.List = make([]api.LLMProxyListItem, 0, len(items)) createdByFields := make([]**string, 0, len(items)) + projectHandles := make(map[string]string) for _, p := range items { id := p.ID name := p.Name @@ -1654,7 +1700,10 @@ func (s *LLMProxyService) ListByProvider(orgUUID, providerID string, limit, offs contextValue = &v } version := p.Version - projectID := p.ProjectUUID + projectID, err := s.resolveProjectHandle(orgUUID, p.ProjectUUID, projectHandles) + if err != nil { + return nil, err + } provider := p.Configuration.Provider resp.List = append(resp.List, api.LLMProxyListItem{ Id: &id, @@ -1688,7 +1737,7 @@ func (s *LLMProxyService) Get(orgUUID, handle string) (*api.LLMProxy, error) { if m == nil { return nil, apperror.LLMProxyNotFound.New() } - return s.toProxyAPI(m) + return s.toProxyAPI(orgUUID, m) } func (s *LLMProxyService) Update(orgUUID, handle, updatedBy string, req *api.LLMProxy) (*api.LLMProxy, error) { @@ -1833,7 +1882,7 @@ func (s *LLMProxyService) Update(orgUUID, handle, updatedBy string, req *api.LLM return nil, apperror.LLMProxyNotFound.New() } _ = s.auditRepo.Record("UPDATE", existing.UUID, "llm_proxy", orgUUID, updatedBy) - return s.toProxyAPI(updated) + return s.toProxyAPI(orgUUID, updated) } func (s *LLMProxyService) Delete(orgUUID, handle, deletedBy string) error { @@ -1905,10 +1954,24 @@ func isSQLiteUniqueConstraint(err error) bool { } func validateUpstream(u api.Upstream) error { - mainUrl := utils.ValueOrEmpty(u.Main.Url) - mainRef := utils.ValueOrEmpty(u.Main.Ref) - if strings.TrimSpace(mainUrl) == "" && strings.TrimSpace(mainRef) == "" { - return apperror.ValidationFailed.New("The upstream main must specify either a url or a ref.") + if err := validateUpstreamDefinition("main", u.Main); err != nil { + return err + } + // Sandbox is optional, but when present it carries the same either-url-or-ref + // constraint as main. + if u.Sandbox != nil { + return validateUpstreamDefinition("sandbox", *u.Sandbox) + } + return nil +} + +// validateUpstreamDefinition enforces the UpstreamDefinition schema constraint that +// exactly one of url or ref is provided. +func validateUpstreamDefinition(name string, definition api.UpstreamDefinition) error { + hasUrl := strings.TrimSpace(utils.ValueOrEmpty(definition.Url)) != "" + hasRef := strings.TrimSpace(utils.ValueOrEmpty(definition.Ref)) != "" + if hasUrl == hasRef { + return apperror.ValidationFailed.New(fmt.Sprintf("The upstream %s must specify either a url or a ref, not both.", name)) } return nil } diff --git a/platform-api/internal/service/llm_test.go b/platform-api/internal/service/llm_test.go index 5a5b0da2df..e28bc25b0a 100644 --- a/platform-api/internal/service/llm_test.go +++ b/platform-api/internal/service/llm_test.go @@ -1135,6 +1135,13 @@ func (m *mockProjectRepo) GetProjectByUUID(projectID string) (*model.Project, er return m.project, nil } +func (m *mockProjectRepo) GetProjectByUUIDAndOrgID(projectID, orgID string) (*model.Project, error) { + if m.project != nil && m.project.OrganizationID != "" && m.project.OrganizationID != orgID { + return nil, nil + } + return m.project, nil +} + func (m *mockProjectRepo) GetProjectByHandleAndOrgID(handle, orgID string) (*model.Project, error) { return m.project, nil } @@ -1499,7 +1506,8 @@ func TestLLMProxyServiceListByProviderUsesProviderUUID(t *testing.T) { return &model.LLMProvider{UUID: "provider-uuid", ID: providerID}, nil }, } - service := NewLLMProxyService(proxyRepo, providerRepo, nil, nil, nil, nil, slog.Default(), &noopAuditRepo{}, &config.Server{}, newTestIdentityService()) + projectRepo := &mockProjectRepo{project: &model.Project{ID: "project-1", Handle: "test-project", OrganizationID: "org-1"}} + service := NewLLMProxyService(proxyRepo, providerRepo, projectRepo, nil, nil, nil, slog.Default(), &noopAuditRepo{}, &config.Server{}, newTestIdentityService()) resp, err := service.ListByProvider("org-1", "provider-1", 10, 0) if err != nil { @@ -1511,6 +1519,11 @@ func TestLLMProxyServiceListByProviderUsesProviderUUID(t *testing.T) { if resp == nil || resp.Count != 1 || len(resp.List) != 1 { t.Fatalf("expected one proxy in response, got: %#v", resp) } + // projectId must be the project handle, not the stored UUID: clients route + // on handles and cannot resolve a project UUID back to one. + if resp.List[0].ProjectId == nil || *resp.List[0].ProjectId != "test-project" { + t.Fatalf("expected projectId to be the project handle, got: %v", resp.List[0].ProjectId) + } } func TestLLMProxyServiceUpdatePreservesProviderAuthValue(t *testing.T) { @@ -1983,3 +1996,42 @@ func TestLLMProxyServiceUpdateFailsWhenAdditionalProviderNameCollides(t *testing t.Fatalf("expected update to be rejected before persisting, but proxy was updated") } } + +// A project UUID stored on a proxy is not itself proof of tenancy — resolving +// it must be scoped to the caller's organization, so a UUID belonging to another +// org resolves to nothing rather than leaking that org's project handle. +func TestLLMProxyServiceResolveProjectHandleIsOrgScoped(t *testing.T) { + projectRepo := &mockProjectRepo{project: &model.Project{ID: "project-1", Handle: "test-project", OrganizationID: "org-1"}} + service := NewLLMProxyService(nil, nil, projectRepo, nil, nil, nil, slog.Default(), &noopAuditRepo{}, &config.Server{}, newTestIdentityService()) + + handle, err := service.resolveProjectHandle("org-1", "project-1", nil) + if err != nil || handle != "test-project" { + t.Fatalf("expected same-org resolution to succeed, got handle=%q err=%v", handle, err) + } + + if _, err := service.resolveProjectHandle("org-2", "project-1", nil); !apperror.ProjectNotFound.Is(err) { + t.Fatalf("expected ProjectNotFound for a cross-org project uuid, got: %v", err) + } +} + +// The sandbox upstream is optional, but when supplied it carries the same +// exactly-one-of-url-or-ref constraint as main — an unvalidated sandbox would let +// an ambiguous (both) or empty (neither) endpoint through. +func TestValidateUpstreamValidatesSandbox(t *testing.T) { + url := "https://api.example.com" + ref := "openai-default" + main := api.UpstreamDefinition{Url: &url} + + if err := validateUpstream(api.Upstream{Main: main}); err != nil { + t.Fatalf("expected an absent sandbox to be accepted: %v", err) + } + if err := validateUpstream(api.Upstream{Main: main, Sandbox: &api.UpstreamDefinition{Ref: &ref}}); err != nil { + t.Fatalf("expected a ref-only sandbox to be accepted: %v", err) + } + if err := validateUpstream(api.Upstream{Main: main, Sandbox: &api.UpstreamDefinition{Url: &url, Ref: &ref}}); !apperror.ValidationFailed.Is(err) { + t.Fatalf("expected ValidationFailed for a sandbox with both url and ref, got: %v", err) + } + if err := validateUpstream(api.Upstream{Main: main, Sandbox: &api.UpstreamDefinition{}}); !apperror.ValidationFailed.Is(err) { + t.Fatalf("expected ValidationFailed for a sandbox with neither url nor ref, got: %v", err) + } +} diff --git a/platform-api/internal/service/mcp.go b/platform-api/internal/service/mcp.go index f54db31eae..c4d708cbb5 100644 --- a/platform-api/internal/service/mcp.go +++ b/platform-api/internal/service/mcp.go @@ -24,6 +24,7 @@ import ( "errors" "fmt" "log/slog" + "strings" "time" "github.com/wso2/api-platform/platform-api/api" @@ -73,13 +74,55 @@ func NewMCPProxyService(repo repository.MCPProxyRepository, projectRepo reposito } } -// toMCPProxyAPI converts m via mapMCPProxyModelToAPI and resolves its -// createdBy/updatedBy UUIDs to their raw external identity. -func (s *MCPProxyService) toMCPProxyAPI(m *model.MCPProxy) (*api.MCPProxy, error) { +// resolveProjectHandle maps a stored project UUID to the project handle the +// API exposes as `projectId`. Requests identify a project by handle +// (Create/ListByProject both take one), so responses must too — a client has +// no way to resolve a project UUID back to a handle. Mirrors +// LLMProxyService.resolveProjectHandle. +// +// The lookup is scoped to orgUUID so a project UUID belonging to another +// organization never resolves — a stored UUID is not itself proof of tenancy. +// +// projectHandleCache is an optional per-request memo so a listing page doesn't +// re-query the same project once per item; pass nil for single-item responses. +func (s *MCPProxyService) resolveProjectHandle(orgUUID string, projectUUID *string, projectHandleCache map[string]string) (*string, error) { + if projectUUID == nil || strings.TrimSpace(*projectUUID) == "" { + return projectUUID, nil + } + uuid := strings.TrimSpace(*projectUUID) + if handle, ok := projectHandleCache[uuid]; ok { + return &handle, nil + } + if s.projectRepo == nil { + return nil, fmt.Errorf("cannot resolve project handle: project repository unavailable") + } + project, err := s.projectRepo.GetProjectByUUIDAndOrgID(uuid, orgUUID) + if err != nil { + return nil, fmt.Errorf("failed to resolve project: %w", err) + } + if project == nil { + return nil, apperror.ProjectNotFound.New() + } + handle := project.Handle + if projectHandleCache != nil { + projectHandleCache[uuid] = handle + } + return &handle, nil +} + +// toMCPProxyAPI converts m via mapMCPProxyModelToAPI, resolves its project +// UUID to the project handle, and resolves its createdBy/updatedBy UUIDs to +// their raw external identity. +func (s *MCPProxyService) toMCPProxyAPI(orgUUID string, m *model.MCPProxy) (*api.MCPProxy, error) { resp := mapMCPProxyModelToAPI(m) if resp == nil { return nil, nil } + projectHandle, err := s.resolveProjectHandle(orgUUID, resp.ProjectId, nil) + if err != nil { + return nil, err + } + resp.ProjectId = projectHandle if err := s.identity.ResolveIdentityField(&resp.CreatedBy); err != nil { return nil, err } @@ -231,11 +274,17 @@ func (s *MCPProxyService) List(orgUUID string, limit, offset int) (*api.MCPProxy resp.List = make([]api.MCPProxyListItem, 0, len(proxies)) createdByFields := make([]**string, 0, len(proxies)) + projectHandles := make(map[string]string) for _, p := range proxies { item := mapMCPProxyModelToListItem(p) if item == nil { continue } + projectHandle, err := s.resolveProjectHandle(orgUUID, item.ProjectId, projectHandles) + if err != nil { + return nil, err + } + item.ProjectId = projectHandle resp.List = append(resp.List, *item) createdByFields = append(createdByFields, &resp.List[len(resp.List)-1].CreatedBy) } @@ -286,11 +335,17 @@ func (s *MCPProxyService) ListByProject(orgUUID, projectHandle string, limit, of resp.List = make([]api.MCPProxyListItem, 0, len(proxies)) createdByFields := make([]**string, 0, len(proxies)) + projectHandles := make(map[string]string) for _, p := range proxies { item := mapMCPProxyModelToListItem(p) if item == nil { continue } + projectHandle, err := s.resolveProjectHandle(orgUUID, item.ProjectId, projectHandles) + if err != nil { + return nil, err + } + item.ProjectId = projectHandle resp.List = append(resp.List, *item) createdByFields = append(createdByFields, &resp.List[len(resp.List)-1].CreatedBy) } @@ -315,7 +370,7 @@ func (s *MCPProxyService) Get(orgUUID, handle string) (*api.MCPProxy, error) { return nil, apperror.MCPProxyNotFound.New() } - return s.toMCPProxyAPI(m) + return s.toMCPProxyAPI(orgUUID, m) } // Update updates an existing MCP proxy @@ -504,10 +559,24 @@ func (s *MCPProxyService) FetchServerInfo(orgUUID string, req *api.MCPServerInfo var url string var headerName, headerValue string - if req.ProxyId != nil && *req.ProxyId != "" { - if req.Auth != nil { - s.slogger.Warn("Auth override is not allowed when proxyId is provided. Ignoring auth in request and using stored auth from proxy configuration.", "org_id", orgUUID, "proxy_id", *req.ProxyId) - } + hasProxyID := req.ProxyId != nil && *req.ProxyId != "" + hasURL := req.Url != nil && *req.Url != "" + + // Exactly one of url / proxyId selects the target, matching the oneOf constraint on + // MCPServerInfoFetchRequest. Accepting both would leave which one wins implicit. + if hasProxyID && hasURL { + return nil, apperror.ValidationFailed.New("Provide either url or proxyId, not both.") + } + if !hasProxyID && !hasURL { + return nil, apperror.ValidationFailed.New("Either url or proxyId is required.") + } + // Auth is only meaningful for the direct-url flow; in refetch mode the stored auth is + // authoritative, so a caller-supplied override is rejected rather than silently ignored. + if hasProxyID && req.Auth != nil { + return nil, apperror.ValidationFailed.New("The auth field is not allowed when proxyId is provided.") + } + + if hasProxyID { // ProxyId provided - fetch stored configuration (refetch flow) // Auth override is NOT allowed in refetch - use exactly what's stored proxy, err := s.repo.GetByHandle(*req.ProxyId, orgUUID) @@ -547,10 +616,7 @@ func (s *MCPProxyService) FetchServerInfo(orgUUID string, req *api.MCPServerInfo } } } else { - // No proxyId - initial creation flow, url is required - if req.Url == nil || *req.Url == "" { - return nil, apperror.ValidationFailed.New("The url field is required when proxyId is not provided.") - } + // No proxyId - initial creation flow, url selects the target url = *req.Url // Use provided auth (optional for initial fetch) diff --git a/platform-api/internal/service/mcp_test.go b/platform-api/internal/service/mcp_test.go index 01fd668ba2..bbdf6448bc 100644 --- a/platform-api/internal/service/mcp_test.go +++ b/platform-api/internal/service/mcp_test.go @@ -67,6 +67,26 @@ func TestMCPProxyServiceCreateRejectsInvalidPolicyVersion(t *testing.T) { } } +// TestMCPProxyServiceGetReturnsProjectHandle guards the response contract: +// projectId must be the project handle, never the internal project UUID. +// Clients route on handles and have no way to resolve a UUID back to one. +func TestMCPProxyServiceGetReturnsProjectHandle(t *testing.T) { + projectUUID := "550e8400-e29b-41d4-a716-446655440000" + repo := &mockMCPProxyRepository{getByHandleResult: &model.MCPProxy{ + Handle: "mcp-proxy-1", + Name: "Test MCP Proxy", + Version: "v1.0", + ProjectUUID: &projectUUID, + }} + projectRepo := &mockProjectRepo{project: &model.Project{ID: projectUUID, Handle: "test-project", OrganizationID: "org-1"}} + service := NewMCPProxyService(repo, projectRepo, nil, nil, nil, slog.Default(), &noopAuditRepo{}, &config.Server{}, newTestIdentityService()) + + resp, err := service.Get("org-1", "mcp-proxy-1") + require.NoError(t, err) + require.NotNil(t, resp.ProjectId) + assert.Equal(t, "test-project", *resp.ProjectId) +} + func TestMCPProxyServiceUpdateRejectsInvalidPolicyVersion(t *testing.T) { repo := &mockMCPProxyRepository{getByHandleResult: &model.MCPProxy{Handle: "mcp-proxy-1"}} service := NewMCPProxyService(repo, nil, nil, nil, nil, slog.Default(), &noopAuditRepo{}, &config.Server{}, newTestIdentityService()) @@ -140,3 +160,35 @@ func TestFetchServerInfoRefetchUsesStoredURLVerbatim(t *testing.T) { }) } } + +// TestFetchServerInfoRejectsAmbiguousTargets asserts the request-shape rules enforced by +// FetchServerInfo, matching the oneOf constraint on MCPServerInfoFetchRequest: exactly one +// of url/proxyId selects the target, and auth may not accompany proxyId (where the stored +// auth is authoritative) — no branch may silently ignore a supplied field. +func TestFetchServerInfoRejectsAmbiguousTargets(t *testing.T) { + url := "https://mcp.example.com/mcp" + proxyID := "mcp-proxy-1" + header, value := "X-API-Key", "secret" + + tests := []struct { + name string + req *api.MCPServerInfoFetchRequest + }{ + {"both url and proxyId", &api.MCPServerInfoFetchRequest{Url: &url, ProxyId: &proxyID}}, + {"neither url nor proxyId", &api.MCPServerInfoFetchRequest{}}, + {"auth supplied with proxyId", &api.MCPServerInfoFetchRequest{ + ProxyId: &proxyID, + Auth: &api.UpstreamAuth{Header: &header, Value: &value}, + }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + repo := &mockMCPProxyRepository{getByHandleResult: &model.MCPProxy{Handle: proxyID}} + service := NewMCPProxyService(repo, nil, nil, nil, nil, slog.Default(), &noopAuditRepo{}, &config.Server{}, newTestIdentityService()) + + _, err := service.FetchServerInfo("org-1", tt.req) + assert.True(t, apperror.ValidationFailed.Is(err), "expected a validation failure, got: %v", err) + }) + } +} diff --git a/platform-api/internal/utils/common.go b/platform-api/internal/utils/common.go index 20b510e295..bd9eaa7575 100644 --- a/platform-api/internal/utils/common.go +++ b/platform-api/internal/utils/common.go @@ -462,25 +462,16 @@ func ValidateExternalURL(_ context.Context, rawURL string) error { // CheckURLReachability verifies that the given URL is reachable by sending an HTTP HEAD request. // It returns an error if the request fails or cannot complete within the given timeout. func CheckURLReachability(rawURL string, timeout time.Duration) error { - client := &http.Client{ - Timeout: timeout, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - if len(via) >= 5 { - return errors.New("too many redirects") - } - return nil - }, - // This is a one-off diagnostic probe with nothing to gain from keep-alive — - // and everything to lose: some servers send a Transfer-Encoding: chunked - // response to a HEAD request without properly terminating it, and Go's - // transport can still decide the connection is safe to pool for reuse even - // when the body is drained (see the drain below). Disabling keep-alives here - // guarantees this connection is always closed after use, so a malformed - // response to this probe can never leak into and corrupt a later, unrelated - // request (e.g. the MCP initialize/tools/list calls FetchServerInfo makes - // right after this) that happens to reuse the same pooled connection. - Transport: &http.Transport{DisableKeepAlives: true}, - } + // The probed URL is user/tenant-supplied, so the connection goes through the guarded + // dialer: every resolved IP is checked at dial time under the upstream policy + // (link-local/metadata refused; private and in-cluster upstreams allowed, since that is + // what an MCP backend normally is) and every redirect hop is bounded and re-dialed + // through the same guard. The client also disables keep-alives — this is a one-off + // diagnostic probe with nothing to gain from connection reuse, and everything to lose: + // some servers answer a HEAD with an improperly terminated chunked response, and a + // pooled connection could then corrupt a later, unrelated request (e.g. the MCP + // initialize/tools/list calls FetchServerInfo makes right after this). + client := NewUpstreamFetchClient(timeout) req, err := http.NewRequestWithContext(context.Background(), http.MethodHead, rawURL, nil) if err != nil { diff --git a/platform-api/internal/utils/guarded_http_client.go b/platform-api/internal/utils/guarded_http_client.go new file mode 100644 index 0000000000..74d378c3e4 --- /dev/null +++ b/platform-api/internal/utils/guarded_http_client.go @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package utils + +import ( + "context" + "fmt" + "net" + "net/http" + "strings" + "time" +) + +// maxOutboundRedirects caps redirect hops on guarded clients. Every hop is still dialed +// through the guarded dialer, so this only bounds redirect loops. +const maxOutboundRedirects = 5 + +// checkRedirectPolicy builds the CheckRedirect callback shared by every guarded client. It +// bounds redirect loops, keeps the scheme within http/https, and refuses any hop that leaves +// the host of the original request. +// +// The host check is what stops a credential leak: callers pass their own headers on these +// requests (an MCP endpoint's auth header, for instance), and net/http only strips +// Authorization/Cookie-style headers across hosts — a custom header name is forwarded +// verbatim. A malicious or compromised upstream could otherwise answer with a redirect to a +// host it controls and be handed the caller's credential. Same-host redirects are still +// dialed through the guarded dialer, so the address policy continues to apply per hop. +func checkRedirectPolicy(maxRedirects int) func(*http.Request, []*http.Request) error { + return func(req *http.Request, via []*http.Request) error { + if len(via) >= maxRedirects { + return fmt.Errorf("too many redirects") + } + if req.URL.Scheme != "http" && req.URL.Scheme != "https" { + return fmt.Errorf("redirect to a disallowed scheme") + } + // via[0] is the original request; Host carries the port, so a port change counts + // as a different host too. + if len(via) > 0 && !strings.EqualFold(req.URL.Host, via[0].URL.Host) { + return fmt.Errorf("redirect to a different host") + } + return nil + } +} + +// guardedDialContext builds a DialContext that resolves the target host itself, refuses to +// connect when any resolved address fails the supplied policy, and then dials the exact IP +// it just approved. Doing the resolution and the connection in one step is what closes the +// DNS-rebinding window: a name that answers with an allowed address during a pre-check +// cannot answer with a different one at connect time. +// +// The address policy is a parameter because different call sites have genuinely different +// threat models — see isPublicIP (fetching from the public internet) versus +// isAllowedUpstreamIP (reaching a backend the operator deployed). +func guardedDialContext(allow func(net.IP) bool, timeout time.Duration) func(context.Context, string, string) (net.Conn, error) { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, fmt.Errorf("invalid address") + } + + ips, err := net.DefaultResolver.LookupIPAddr(ctx, host) + if err != nil { + return nil, fmt.Errorf("failed to resolve host") + } + if len(ips) == 0 { + return nil, fmt.Errorf("host has no addresses") + } + for _, ip := range ips { + if !allow(ip.IP) { + return nil, fmt.Errorf("host resolves to a disallowed address") + } + } + + dialer := &net.Dialer{Timeout: timeout} + var lastErr error + for _, ip := range ips { + conn, dialErr := dialer.DialContext(ctx, network, net.JoinHostPort(ip.IP.String(), port)) + if dialErr == nil { + return conn, nil + } + lastErr = dialErr + } + if lastErr != nil { + return nil, fmt.Errorf("failed to connect to host") + } + return nil, fmt.Errorf("failed to connect to host") + } +} + +// isAllowedUpstreamIP is the address policy for calls to a backend the operator configured +// — an MCP proxy upstream, for instance. Such a backend is normally *meant* to be private: +// a Kubernetes ClusterIP, a service-DNS name resolving into RFC 1918 space, or a localhost +// port during development. Refusing those would break the ordinary deployment shape, so +// private, ULA, shared-address-space and loopback addresses are all permitted. +// +// What stays refused is the set of addresses that is never a legitimate upstream but is a +// standard SSRF target or a hazard: +// +// - link-local unicast — 169.254.0.0/16 and fe80::/10, which is where the cloud instance +// metadata endpoint (169.254.169.254) lives +// - the unspecified address (0.0.0.0, ::), which the OS reinterprets as "local host" +// - multicast and the IPv4 broadcast address, which are not meaningful HTTP peers +func isAllowedUpstreamIP(ip net.IP) bool { + if ip == nil { + return false + } + if ip.IsLinkLocalUnicast() || + ip.IsLinkLocalMulticast() || + ip.IsMulticast() || + ip.IsUnspecified() || + ip.Equal(net.IPv4bcast) { + return false + } + return true +} + +// upstreamIPIsAllowed is the address check used by NewUpstreamFetchClient. It is a package +// variable only so tests can vary the policy; production code never reassigns it. +var upstreamIPIsAllowed = isAllowedUpstreamIP + +// NewUpstreamFetchClient returns an http.Client for calls to an operator- or +// tenant-configured backend whose URL is not fully trusted (MCP reachability probes, MCP +// JSON-RPC calls). Every connection — including each redirect hop — is dialed through the +// guarded dialer under isAllowedUpstreamIP, so private and in-cluster upstreams work +// normally while link-local/metadata addresses stay unreachable. +// +// For fetching from the public internet, use the stricter isPublicIP-based path in +// FetchOpenAPISpecFromURL instead. +// +// A non-positive timeout falls back to defaultUpstreamFetchTimeout. +func NewUpstreamFetchClient(timeout time.Duration) *http.Client { + if timeout <= 0 { + timeout = defaultUpstreamFetchTimeout + } + return &http.Client{ + Timeout: timeout, + Transport: &http.Transport{ + DialContext: guardedDialContext(func(ip net.IP) bool { + return upstreamIPIsAllowed(ip) + }, timeout), + TLSHandshakeTimeout: timeout, + ResponseHeaderTimeout: timeout, + // A one-off outbound probe/call has nothing to gain from connection reuse, and + // pooling a connection whose response was malformed can corrupt a later request. + DisableKeepAlives: true, + // No proxy: dialing must go through the guarded dialer, not a forward proxy + // that could bypass the address checks. + Proxy: nil, + }, + CheckRedirect: checkRedirectPolicy(maxOutboundRedirects), + } +} + +// defaultUpstreamFetchTimeout bounds a guarded upstream call end to end when the caller +// does not supply its own timeout. +const defaultUpstreamFetchTimeout = 10 * time.Second diff --git a/platform-api/internal/utils/guarded_http_client_test.go b/platform-api/internal/utils/guarded_http_client_test.go new file mode 100644 index 0000000000..ffd2074376 --- /dev/null +++ b/platform-api/internal/utils/guarded_http_client_test.go @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2026, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package utils + +import ( + "net" + "net/http" + "net/http/httptest" + "testing" +) + +// TestIsAllowedUpstreamIP pins the upstream address policy in both directions. An MCP +// upstream is normally a service the operator deployed, so private/in-cluster/loopback +// addresses MUST stay reachable — a regression to a public-only policy would break every +// cluster-internal MCP proxy. The metadata endpoint must stay unreachable regardless. +func TestIsAllowedUpstreamIP(t *testing.T) { + allowed := []string{ + "10.4.2.9", // k8s ClusterIP / RFC 1918 + "172.16.0.5", // RFC 1918 + "192.168.1.20", // RFC 1918 + "100.64.0.1", // RFC 6598 shared address space + "127.0.0.1", // loopback — local development + "::1", // IPv6 loopback + "fd00::1", // IPv6 ULA — in-cluster + "93.184.216.34", // public + "2606:2800:220::", // public IPv6 + } + for _, s := range allowed { + if ip := net.ParseIP(s); !isAllowedUpstreamIP(ip) { + t.Errorf("expected %s to be an allowed upstream address", s) + } + } + + denied := []string{ + "169.254.169.254", // cloud instance metadata + "169.254.1.1", // link-local + "fe80::1", // IPv6 link-local + "0.0.0.0", // unspecified + "::", // IPv6 unspecified + "224.0.0.1", // multicast + "255.255.255.255", // broadcast + } + for _, s := range denied { + if ip := net.ParseIP(s); isAllowedUpstreamIP(ip) { + t.Errorf("expected %s to be a denied upstream address", s) + } + } + + if isAllowedUpstreamIP(nil) { + t.Error("expected a nil address to be denied") + } +} + +// TestUpstreamFetchClientReachesLoopbackBackend is the end-to-end counterpart: the client +// must actually connect to a private/loopback backend, not merely rate the address as +// allowed. This is the regression guard for MCP proxies with in-cluster upstreams. +func TestUpstreamFetchClientReachesLoopbackBackend(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer srv.Close() + + resp, err := NewUpstreamFetchClient(0).Get(srv.URL) + if err != nil { + t.Fatalf("guarded upstream client failed to reach a loopback backend: %v", err) + } + defer resp.Body.Close() //nolint:errcheck + if resp.StatusCode != http.StatusOK { + t.Errorf("expected 200 from the test backend, got %d", resp.StatusCode) + } +} + +// TestUpstreamFetchClientRefusesDeniedAddress confirms the dialer refuses a denied address +// even though the policy is otherwise permissive. +func TestUpstreamFetchClientRefusesDeniedAddress(t *testing.T) { + restore := upstreamIPIsAllowed + upstreamIPIsAllowed = func(net.IP) bool { return false } + defer func() { upstreamIPIsAllowed = restore }() + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + defer srv.Close() + + if _, err := NewUpstreamFetchClient(0).Get(srv.URL); err == nil { + t.Fatal("expected the guarded client to refuse a disallowed address") + } +} + +// TestUpstreamFetchClientRefusesCrossHostRedirect guards the credential-leak case: callers +// pass a custom auth header on MCP calls, and net/http forwards a custom header name across +// hosts, so a redirect off the original host must not be followed. A same-host redirect +// still is. +func TestUpstreamFetchClientRefusesCrossHostRedirect(t *testing.T) { + target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + })) + defer target.Close() + + crossHost := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, target.URL+"/stolen", http.StatusFound) + })) + defer crossHost.Close() + + if _, err := NewUpstreamFetchClient(0).Get(crossHost.URL); err == nil { + t.Fatal("expected the guarded client to refuse a cross-host redirect") + } + + sameHost := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/moved" { + http.Redirect(w, r, "/final", http.StatusFound) + return + } + w.WriteHeader(http.StatusOK) + })) + defer sameHost.Close() + + resp, err := NewUpstreamFetchClient(0).Get(sameHost.URL + "/moved") + if err != nil { + t.Fatalf("expected a same-host redirect to be followed: %v", err) + } + defer resp.Body.Close() //nolint:errcheck + if resp.StatusCode != http.StatusOK { + t.Errorf("expected 200 after a same-host redirect, got %d", resp.StatusCode) + } +} diff --git a/platform-api/internal/utils/mcp.go b/platform-api/internal/utils/mcp.go index ad2da1f96b..d46f6f8518 100644 --- a/platform-api/internal/utils/mcp.go +++ b/platform-api/internal/utils/mcp.go @@ -51,6 +51,9 @@ const ( McpSessionHeader = "mcp-session-id" ) +// mcpRequestTimeout bounds each outbound MCP JSON-RPC call (DNS + connect + TLS + read). +const mcpRequestTimeout = 10 * time.Second + // JsonRPCRequest represents a JSON-RPC request type JsonRPCRequest struct { JSONRPC string `json:"jsonrpc"` @@ -276,9 +279,9 @@ func initializeMCPServer(url string, headerName string, headerValue string) (str httpReq.Header.Set(headerName, headerValue) } } - client := &http.Client{ - Timeout: 10 * time.Second, - } + // The MCP endpoint URL is user/tenant-supplied — dial through the SSRF-guarded client + // so scheme, resolved-IP and redirect restrictions apply to this call too. + client := NewUpstreamFetchClient(mcpRequestTimeout) resp, err := client.Do(httpReq) if err != nil { return "", nil, fmt.Errorf("failed to reach MCP server for initialize: %w", err) @@ -356,9 +359,9 @@ func postJSONRPCWithSession(url string, req any, sessionID string, headerName st if sessionID != "" { httpReq.Header.Set(McpSessionHeader, sessionID) } - client := &http.Client{ - Timeout: 10 * time.Second, - } + // Same SSRF-guarded client as the initialize call: the session/tools/prompts/resources + // requests target the same user-supplied URL and must be dialed under the same guard. + client := NewUpstreamFetchClient(mcpRequestTimeout) resp, err := client.Do(httpReq) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) diff --git a/platform-api/internal/utils/openapi_spec_fetcher.go b/platform-api/internal/utils/openapi_spec_fetcher.go index abd7cb6301..82d05bc61b 100644 --- a/platform-api/internal/utils/openapi_spec_fetcher.go +++ b/platform-api/internal/utils/openapi_spec_fetcher.go @@ -51,7 +51,8 @@ const ( // DNS-rebinding) — loopback, private (RFC 1918 / ULA), link-local (incl. the cloud // metadata endpoint 169.254.169.254), unspecified, multicast and broadcast addresses // are refused. -// - Redirects are bounded and each hop is dialed through the same guarded dialer. +// - Redirects are bounded, may not leave the original host, and each hop is dialed +// through the same guarded dialer. // - The response body is read through an io.LimitReader capped at maxBytes. // - Errors are returned sterile (no internal host/IP detail) so callers can log them // internally without leaking infrastructure information to clients. @@ -87,15 +88,7 @@ func FetchOpenAPISpecFromURL(ctx context.Context, rawURL string, maxBytes int64) // that could bypass the IP checks. Proxy: nil, }, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - if len(via) >= openAPISpecMaxRedirects { - return fmt.Errorf("too many redirects") - } - if req.URL.Scheme != "http" && req.URL.Scheme != "https" { - return fmt.Errorf("redirect to a disallowed scheme") - } - return nil - }, + CheckRedirect: checkRedirectPolicy(openAPISpecMaxRedirects), } req, err := http.NewRequestWithContext(ctx, http.MethodGet, parsed.String(), nil) @@ -134,41 +127,12 @@ func FetchOpenAPISpecFromURL(ctx context.Context, rawURL string, maxBytes int64) var ipIsAllowed = isPublicIP // ssrfSafeDialContext resolves the target host and refuses to connect to any non-public -// address. It performs the resolution itself and dials the resolved IP directly, so a -// DNS name that resolves to a public IP during a pre-check cannot be re-resolved to an -// internal IP at connect time (DNS rebinding). +// address — the right policy here because an OpenAPI spec URL points at a vendor endpoint +// on the public internet. A backend the operator deployed is a different case and uses the +// more permissive isAllowedUpstreamIP policy instead (see NewUpstreamFetchClient). func ssrfSafeDialContext(ctx context.Context, network, addr string) (net.Conn, error) { - host, port, err := net.SplitHostPort(addr) - if err != nil { - return nil, fmt.Errorf("invalid address") - } - - ips, err := net.DefaultResolver.LookupIPAddr(ctx, host) - if err != nil { - return nil, fmt.Errorf("failed to resolve host") - } - if len(ips) == 0 { - return nil, fmt.Errorf("host has no addresses") - } - for _, ip := range ips { - if !ipIsAllowed(ip.IP) { - return nil, fmt.Errorf("host resolves to a disallowed address") - } - } - - dialer := &net.Dialer{Timeout: openAPISpecFetchTimeout} - var lastErr error - for _, ip := range ips { - conn, dialErr := dialer.DialContext(ctx, network, net.JoinHostPort(ip.IP.String(), port)) - if dialErr == nil { - return conn, nil - } - lastErr = dialErr - } - if lastErr != nil { - return nil, fmt.Errorf("failed to connect to host") - } - return nil, fmt.Errorf("failed to connect to host") + dial := guardedDialContext(func(ip net.IP) bool { return ipIsAllowed(ip) }, openAPISpecFetchTimeout) + return dial(ctx, network, addr) } // cgnatRange is RFC 6598 shared address space (carrier-grade NAT): 100.64.0.0/10. diff --git a/platform-api/resources/openapi.yaml b/platform-api/resources/openapi.yaml index e0e3ec713c..33b2af173f 100644 --- a/platform-api/resources/openapi.yaml +++ b/platform-api/resources/openapi.yaml @@ -242,9 +242,9 @@ paths: /projects/{projectId}: get: - summary: Get project by UUID + summary: Get project by ID description: | - Retrieves a specific project by its UUID. Access is validated against the organization + Retrieves a specific project by its ID (handle). Access is validated against the organization in the JWT token to ensure users can only access projects in their organization. operationId: GetProject security: @@ -314,8 +314,8 @@ paths: delete: summary: Delete project description: | - Deletes a specific project by its UUID. Access is validated against the organization - in the JWT token. + Deletes a specific project by its handle (unique slug identifier). Access is validated + against the organization in the JWT token. operationId: DeleteProject security: - OAuth2Security: @@ -420,9 +420,9 @@ paths: /rest-apis/{restApiId}: get: - summary: Get REST API by UUID + summary: Get REST API by ID description: | - Retrieves a specific API by its UUID. Access is validated against the organization + Retrieves a specific API by its ID (handle). Access is validated against the organization in the JWT token. operationId: GetRESTAPI security: @@ -493,7 +493,7 @@ paths: delete: summary: Delete REST API description: | - Deletes a specific API by its UUID. Access is validated against the organization + Deletes a specific API by its ID (handle). Access is validated against the organization in the JWT token. operationId: DeleteRESTAPI security: @@ -780,6 +780,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentDeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -795,7 +798,7 @@ paths: summary: Get deployments for a REST API description: | Retrieves all deployment artifacts for a specific API. The apiId parameter is the API handle (identifier), - not the UUID. Supports filtering by gateway UUID and deployment status. + not the UUID. Supports filtering by gateway handle and deployment status. Access is validated against the organization in the JWT token. operationId: GetDeployments security: @@ -888,7 +891,7 @@ paths: '404': $ref: '#/components/responses/NotFound' '409': - $ref: '#/components/responses/Conflict' + $ref: '#/components/responses/DeploymentActiveConflict' '500': $ref: '#/components/responses/InternalServerError' @@ -935,6 +938,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentUndeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -991,6 +997,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentDeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -1179,6 +1188,9 @@ paths: application/json: schema: $ref: '#/components/schemas/LLMProviderTemplate' + examples: + default: + $ref: '#/components/examples/CopiedTemplateVersion' '400': $ref: '#/components/responses/BadRequest' '401': @@ -1302,7 +1314,8 @@ paths: properties: enabled: type: boolean - example: false + description: Set to true to enable this template version, false to disable it. + example: true responses: '200': description: Updated template version @@ -1620,6 +1633,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentDeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -1635,7 +1651,7 @@ paths: summary: Get deployments for an LLM provider description: | Retrieves all deployment artifacts for a specific LLM provider. The providerId parameter is the - LLM provider handle (identifier), not the UUID. Supports filtering by gateway UUID and deployment status. + LLM provider handle (identifier), not the UUID. Supports filtering by gateway handle and deployment status. Access is validated against the organization in the JWT token. operationId: getLLMProviderDeployments security: @@ -1743,7 +1759,7 @@ paths: '404': $ref: '#/components/responses/NotFound' '409': - $ref: '#/components/responses/Conflict' + $ref: '#/components/responses/DeploymentActiveConflict' '500': $ref: '#/components/responses/InternalServerError' @@ -1784,7 +1800,10 @@ paths: required: true schema: type: string - description: UUID of the gateway (validated against deployment's bound gateway) + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + description: Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway) responses: '200': description: Undeploy initiated successfully. Returns the deployment with initial status UNDEPLOYING. Poll status for final result. @@ -1792,6 +1811,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentUndeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -1842,7 +1864,10 @@ paths: required: true schema: type: string - description: UUID of the gateway (validated against deployment's bound gateway) + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + description: Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway) responses: '200': description: Restore initiated successfully. Returns the deployment with initial status DEPLOYING. Poll status for final result. @@ -1850,6 +1875,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentDeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -2243,6 +2271,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentDeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -2258,7 +2289,7 @@ paths: summary: Get deployments for an LLM proxy description: | Retrieves all deployment artifacts for a specific LLM proxy. The proxyId parameter is the - LLM proxy handle (identifier), not the UUID. Supports filtering by gateway UUID and deployment status. + LLM proxy handle (identifier), not the UUID. Supports filtering by gateway handle and deployment status. Access is validated against the organization in the JWT token. operationId: getLLMProxyDeployments security: @@ -2366,7 +2397,7 @@ paths: '404': $ref: '#/components/responses/NotFound' '409': - $ref: '#/components/responses/Conflict' + $ref: '#/components/responses/DeploymentActiveConflict' '500': $ref: '#/components/responses/InternalServerError' @@ -2407,7 +2438,10 @@ paths: required: true schema: type: string - description: UUID of the gateway (validated against deployment's bound gateway) + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + description: Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway) responses: '200': description: Undeploy initiated successfully. Returns the deployment with initial status UNDEPLOYING. Poll status for final result. @@ -2415,6 +2449,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentUndeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -2465,7 +2502,10 @@ paths: required: true schema: type: string - description: UUID of the gateway (validated against deployment's bound gateway) + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + description: Handle (URL-friendly slug) of the gateway (validated against deployment's bound gateway) responses: '200': description: Restore initiated successfully. Returns the deployment with initial status DEPLOYING. Poll status for final result. @@ -2473,6 +2513,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentDeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -2826,6 +2869,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentDeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -2839,7 +2885,7 @@ paths: summary: Get deployments for an MCP proxy description: | Retrieves all deployment artifacts for a specific MCP proxy. The id parameter is the MCP proxy handle (identifier), - not the UUID. Supports filtering by gateway UUID and deployment status. + not the UUID. Supports filtering by gateway handle and deployment status. Access is validated against the organization in the JWT token. operationId: GetMCPProxyDeployments security: @@ -2945,7 +2991,7 @@ paths: '404': $ref: '#/components/responses/NotFound' '409': - $ref: '#/components/responses/Conflict' + $ref: '#/components/responses/DeploymentActiveConflict' '500': $ref: '#/components/responses/InternalServerError' @@ -2997,6 +3043,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentUndeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -3056,6 +3105,9 @@ paths: application/json: schema: $ref: '#/components/schemas/DeploymentResponse' + examples: + default: + $ref: '#/components/examples/DeploymentDeploying' '400': $ref: '#/components/responses/BadRequest' '401': @@ -3081,7 +3133,7 @@ paths: tags: - MCP Proxies requestBody: - description: Deployment request with gateway ID, base reference, and metadata + description: Target MCP server to introspect — either a direct `url` (with optional `auth`), or a `proxyId` to refetch using a stored proxy configuration. required: true content: application/json: @@ -3180,9 +3232,9 @@ paths: /gateways/{gatewayId}: get: - summary: Get gateway by UUID + summary: Get gateway by ID description: | - Retrieves a specific gateway by its UUID. Access is validated against the organization + Retrieves a specific gateway by its ID (handle). Access is validated against the organization in the JWT token. operationId: GetGateway security: @@ -4279,8 +4331,7 @@ paths: get: summary: Get subscription by ID description: | - Returns a single subscription by ID (scoped to organization). - Query parameter `subscriberId` is required and must match the subscription's subscriber for access control. + Returns a single subscription by ID, scoped to the organization in the access token. operationId: GetSubscription security: - OAuth2Security: @@ -5385,7 +5436,7 @@ components: maxLength: 232 minLength: 1 type: string - example: pizza + example: /pizza version: maxLength: 30 minLength: 1 @@ -6084,8 +6135,8 @@ components: example: "def45678-g901-23hi-j456-789012klmnop" token: type: string - description: Plain-text new authentication token (only exposed once during rotation) - example: "nM8pQ1rT4vW7yZ0bC3eF6hI9kL2nO5qR8tU1wX4zA7cE0fH3jK6mP9sV2yB5dG8i" + description: Plain-text new authentication token (only exposed once during rotation). The example value is a non-functional placeholder. + example: "REDACTED_TOKEN" createdAt: type: string format: date-time @@ -6591,7 +6642,7 @@ components: field: type: string description: Path of the offending field. - example: spec.context + example: message: type: string description: Why the field failed validation. @@ -6722,16 +6773,25 @@ components: oneOf: - required: [ "url" ] - required: [ "ref" ] - description: Upstream endpoint configuration (single target or reference) + description: | + Upstream endpoint configuration. Provide exactly one of `url` (a direct backend URL) or + `ref` (a reference to a predefined upstream definition) — never both. + example: + url: http://prod-backend:5000/api/v2 + auth: + type: api-key + header: X-API-Key + value: my-api-key-value properties: url: type: string format: uri - description: Direct backend URL to route traffic to + description: Direct backend URL to route traffic to. Mutually exclusive with `ref`. example: http://prod-backend:5000/api/v2 ref: type: string - description: Reference to a predefined upstreamDefinition + description: Reference to a predefined upstreamDefinition. Mutually exclusive with `url`. + example: my-shared-upstream auth: $ref: '#/components/schemas/UpstreamAuth' @@ -6823,17 +6883,41 @@ components: description: Resource path pattern this mapping applies to (for example /responses or /chat/*) example: /responses promptTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.inputTokens" completionTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.outputTokens" totalTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.totalTokens" remainingTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: header + identifier: "x-ratelimit-remaining-tokens" requestModel: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.model" responseModel: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.model" LLMProviderTemplateResourceMappings: type: object @@ -6933,17 +7017,41 @@ components: metadata: $ref: '#/components/schemas/LLMProviderTemplateMetadata' promptTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.inputTokens" completionTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.outputTokens" totalTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.totalTokens" remainingTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: header + identifier: "x-ratelimit-remaining-tokens" requestModel: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.model" responseModel: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.model" resourceMappings: $ref: '#/components/schemas/LLMProviderTemplateResourceMappings' createdAt: @@ -7000,17 +7108,41 @@ components: metadata: $ref: '#/components/schemas/LLMProviderTemplateMetadata' promptTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.inputTokens" completionTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.outputTokens" totalTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.usage.totalTokens" remainingTokens: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: header + identifier: "x-ratelimit-remaining-tokens" requestModel: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.model" responseModel: - $ref: '#/components/schemas/ExtractionIdentifier' + allOf: + - $ref: '#/components/schemas/ExtractionIdentifier' + example: + location: payload + identifier: "$.model" resourceMappings: $ref: '#/components/schemas/LLMProviderTemplateResourceMappings' @@ -7449,7 +7581,7 @@ components: example: v1.0 context: type: string - description: Base path for all REST API routes (must start with /, no trailing slash) + description: Base path for all routes exposed by this provider. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default. pattern: '^\/([a-zA-Z0-9_\-.\/]*[^\/])?$' default: / minLength: 1 @@ -7464,7 +7596,7 @@ components: pattern: '^(\*\.|[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$' minLength: 1 maxLength: 253 - example: "api.openai" + example: "api.openai.com" template: type: string description: Template name to use for this LLM Provider @@ -7719,11 +7851,14 @@ components: example: v1.0 projectId: type: string - description: UUID of the project this proxy belongs to - example: 550e8400-e29b-41d4-a716-446655440000 + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + description: Handle (URL-friendly slug) of the project this proxy belongs to + example: default-project context: type: string - description: Base path for all REST API routes (must start with /, no trailing slash) + description: Base path for all routes exposed by this proxy. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default. pattern: '^\/([a-zA-Z0-9_\-.\/]*[^\/])?$' default: / minLength: 1 @@ -7738,7 +7873,7 @@ components: pattern: '^(\*\.|[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$' minLength: 1 maxLength: 253 - example: "api.openai" + example: "api.openai.com" provider: $ref: '#/components/schemas/LLMProxyProvider' additionalProviders: @@ -7834,8 +7969,11 @@ components: example: v1.0 projectId: type: string - description: UUID of the project this proxy belongs to - example: 550e8400-e29b-41d4-a716-446655440000 + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + description: Handle (URL-friendly slug) of the project this proxy belongs to + example: default-project provider: type: string description: Unique id of a deployed llm provider @@ -8001,8 +8139,8 @@ components: example: "production-key" apiKey: type: string - description: The generated API key value (shown only once, 64 hexadecimal characters) - example: "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456" + description: The generated API key value — 64 hexadecimal characters, returned only in this creation response and never retrievable afterwards. The example value is a non-functional placeholder. + example: "REDACTED_API_KEY" CreateLLMProxyAPIKeyRequest: type: object required: @@ -8060,8 +8198,8 @@ components: example: "production-key" apiKey: type: string - description: The generated API key value (shown only once, 64 hexadecimal characters) - example: "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456" + description: The generated API key value — 64 hexadecimal characters, returned only in this creation response and never retrievable afterwards. The example value is a non-functional placeholder. + example: "REDACTED_API_KEY" MCPProxy: type: object @@ -8110,11 +8248,14 @@ components: example: v1.0 projectId: type: string - description: UUID of the project this proxy belongs to - example: 550e8400-e29b-41d4-a716-446655440000 + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + description: Handle (URL-friendly slug) of the project this proxy belongs to + example: default-project context: type: string - description: Base path for all REST API routes (must start with /, no trailing slash) + description: Base path for all routes exposed by this proxy. Must start with / and carry no trailing slash; the single exception is the root path "/", which is the default. pattern: '^\/([a-zA-Z0-9_\-.\/]*[^\/])?$' default: / minLength: 1 @@ -8208,8 +8349,11 @@ components: example: v1.0 projectId: type: string - description: UUID of the project this proxy belongs to - example: 550e8400-e29b-41d4-a716-446655440000 + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + description: Handle (URL-friendly slug) of the project this proxy belongs to + example: default-project status: type: string enum: @@ -8252,27 +8396,37 @@ components: MCPServerInfoFetchRequest: type: object + oneOf: + - required: [ "url" ] + - required: [ "proxyId" ] + not: + required: [ "auth" ] + description: | + Target MCP server to introspect. Provide exactly one of `url` (a direct backend URL, + optionally with `auth`) or `proxyId` (refetch using a stored proxy configuration) — never + both. `auth` must not be sent together with `proxyId`; the stored auth is always used in + the refetch flow. properties: url: type: string format: uri description: | - Endpoint URL of the MCP server to fetch information from. - Required when proxyId is not provided. When proxyId is provided, - the URL from the stored proxy configuration is used. + Endpoint URL of the MCP server to fetch information from. Mutually exclusive + with `proxyId`; exactly one of the two must be provided. example: https://mcp.server.com/mcp proxyId: type: string description: | - MCP proxy handle (identifier) for refresh operations. When provided, - the server fetches URL and auth from the stored proxy configuration. - Auth override is not allowed in refetch mode. + MCP proxy handle (identifier) for refresh operations. When provided, the server + fetches URL and auth from the stored proxy configuration. Mutually exclusive + with `url`; exactly one of the two must be provided. example: my-mcp-proxy auth: $ref: '#/components/schemas/UpstreamAuth' description: | - Authentication configuration for the fetch request. - Only allowed when proxyId is not provided (initial creation flow). + Authentication configuration for the fetch request. Allowed only alongside + `url` (initial creation flow); sending it with `proxyId` is rejected, as the + stored auth is used in the refetch flow. MCPServerInfoFetchResponse: type: object @@ -8335,6 +8489,7 @@ components: value: type: string description: Plaintext secret value — encrypted at rest, never returned in any response + writeOnly: true example: sk-xxx type: type: string @@ -8363,6 +8518,7 @@ components: value: type: string description: New plaintext secret value — re-encrypted at rest + writeOnly: true SecretResponse: type: object @@ -8517,8 +8673,8 @@ components: code: VALIDATION_FAILED message: The request failed validation. errors: - - field: spec.context - message: must start with / + - field: + message: Forbidden: description: Forbidden. The authenticated user does not have permission to access this resource. content: @@ -8540,7 +8696,7 @@ components: code: NOT_FOUND message: The specified resource does not exist. Conflict: - description: Conflict. Specified resource already exists. + description: Conflict. The request conflicts with the current state of the resource. content: application/json: schema: @@ -8548,7 +8704,17 @@ components: example: status: error code: CONFLICT - message: The specified resource already exists. + message: The request conflicts with the current state of the resource. + DeploymentActiveConflict: + description: Conflict. The deployment is still active and must be undeployed before deletion. + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + status: error + code: DEPLOYMENT_ACTIVE + message: Cannot delete an active deployment - undeploy it first. InternalServerError: description: Internal Server Error. content: @@ -8585,6 +8751,44 @@ components: trackingId: 4f1c6f2e-8a4b-4c93-b1de-9f2f6f0c2a11 examples: + CopiedTemplateVersion: + summary: New version created by copying openai-v3-0 + value: + id: openai-v4-0 + groupId: openai + displayName: OpenAI + managedBy: organization + description: Default OpenAI template + createdBy: john.doe + updatedBy: john.doe + readOnly: false + version: v4.0 + isLatest: true + enabled: true + DeploymentDeploying: + summary: Deployment initiated (transitional) + description: Asynchronous operation accepted; poll the deployment until status becomes DEPLOYED or FAILED. + value: + deploymentId: a73c85a1-d857-491e-a6b2-51dce05de7a2 + name: v1.0-production + gatewayId: prod-gateway-01 + status: DEPLOYING + baseDeploymentId: be6d8692-b9de-400e-b6c1-14db50154e27 + metadata: {} + createdAt: 2019-08-24T14:15:22Z + updatedAt: 2019-08-24T14:15:22Z + DeploymentUndeploying: + summary: Undeploy initiated (transitional) + description: Asynchronous operation accepted; poll the deployment until status becomes UNDEPLOYED or FAILED. + value: + deploymentId: a73c85a1-d857-491e-a6b2-51dce05de7a2 + name: v1.0-production + gatewayId: prod-gateway-01 + status: UNDEPLOYING + baseDeploymentId: be6d8692-b9de-400e-b6c1-14db50154e27 + metadata: {} + createdAt: 2019-08-24T14:15:22Z + updatedAt: 2019-08-24T14:15:22Z GatewayNotFoundExample: value: status: error @@ -8601,7 +8805,7 @@ components: in: path required: true description: | - **Project ID** (handle — unique slug identifier) of the Project. + **Project ID** consisting of the **handle** (unique slug identifier) of the Project. schema: type: string example: "default-project" @@ -8662,7 +8866,7 @@ components: in: query required: true description: | - **Project ID** consisting of the **handle** (unique slug identifier) of the Project to filter APIs by. + **Project ID** consisting of the **handle** (unique slug identifier) of the Project whose resources should be returned. schema: type: string example: "default-project" @@ -8672,17 +8876,20 @@ components: in: query required: false description: | - **Gateway ID** consisting of the **UUID** of the Gateway to filter status by. + **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway to filter status by. schema: type: string - example: "987e6543-e21b-45d3-a789-426614174999" + pattern: '^[a-z0-9-]+$' + minLength: 3 + maxLength: 63 + example: "prod-gateway-01" gatewayId: name: gatewayId in: path required: true description: | - **Gateway ID** (handle — unique slug identifier) of the Gateway. + **Gateway ID** consisting of the **handle** (unique slug identifier) of the Gateway. schema: type: string example: "prod-gateway-01" @@ -8805,6 +9012,8 @@ tags: description: Gateway registration and management operations - name: Gateway Tokens description: Gateway token rotation and revocation operations + - name: Gateway Policies + description: Custom gateway policy management operations - name: LLM Providers description: LLM provider management operations - name: LLM Proxies @@ -8813,6 +9022,8 @@ tags: description: LLM provider template management operations - name: LLM Provider Deployments description: LLM provider deployment operations + - name: LLM Proxy Deployments + description: LLM proxy deployment operations - name: API Keys description: API key management operations for REST APIs and LLM Providers - name: MCP Proxies @@ -8821,5 +9032,9 @@ tags: description: MCP proxy deployment operations - name: Deployments description: Deployment operations for various artifact types + - name: SubscriptionPlans + description: Subscription plan management operations — rate-limit tiers that subscriptions are created against + - name: Subscriptions + description: Subscription management operations linking applications to APIs under a subscription plan - name: Secrets description: Encrypted secret management — create, rotate, and delete organization-scoped secrets referenced via {{ secret "name" }} placeholders \ No newline at end of file diff --git a/portals/ai-workspace/src/pages/appShell/appShellPages/quickStart/lllmStepBanner/LLLMStepBanner.tsx b/portals/ai-workspace/src/pages/appShell/appShellPages/quickStart/lllmStepBanner/LLLMStepBanner.tsx index b409e8fff8..f943d6ffec 100644 --- a/portals/ai-workspace/src/pages/appShell/appShellPages/quickStart/lllmStepBanner/LLLMStepBanner.tsx +++ b/portals/ai-workspace/src/pages/appShell/appShellPages/quickStart/lllmStepBanner/LLLMStepBanner.tsx @@ -133,9 +133,13 @@ export default function LLLMStepBanner({ } setStepCompletion({ + // A deployment record alone doesn't mean the provider is live — a + // failed or in-progress deploy must leave the step incomplete. hasDeployments: deploymentsResult.status === 'fulfilled' - ? (deploymentsResult.value.list?.length ?? 0) > 0 + ? (deploymentsResult.value.list ?? []).some( + (deployment) => deployment.status === 'DEPLOYED' + ) : false, hasConsumptions: apiKeysResult.status === 'fulfilled' diff --git a/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ProviderMap/ProviderMapTab.tsx b/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ProviderMap/ProviderMapTab.tsx index 8127e62cb6..3de7720853 100644 --- a/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ProviderMap/ProviderMapTab.tsx +++ b/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ProviderMap/ProviderMapTab.tsx @@ -53,7 +53,13 @@ import type { Gateway } from '../../../../../apis/gatewayTypes'; import type { DeploymentResponse, Proxy } from '../../../../../utils/types'; import { PLATFORM_API_BASE_URL } from '../../../../../config.env'; import { logger } from '../../../../../utils/logger'; -import { buildProjectPath } from '../../../../../utils/projectRouting'; +import { + buildGatewayPath, + buildProxyPath, +} from '../../../../../utils/projectRouting'; +import { useAppAuth } from '../../../../../contexts/AppAuthContext'; +import { SCOPES } from '../../../../../auth/permissions'; +import useAIWorkspaceSnackbar from '../../../../../hooks/aiWorkspaceSnackbar'; import AnthropicLogo from '../../../../../assets/brands/Anthropic.jpg'; import AWSBedrockLogo from '../../../../../assets/brands/AWSBedrock.webp'; @@ -306,13 +312,28 @@ export function ProxyCard({ interface GatewayCardProps { gateway: Gateway; deployment?: DeploymentResponse; + onClick?: () => void; } -export function GatewayCard({ gateway, deployment }: GatewayCardProps) { +export function GatewayCard({ gateway, deployment, onClick }: GatewayCardProps) { const isDeployed = deployment?.status === 'DEPLOYED'; return ( - + { + if (!onClick) return; + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + onClick(); + } + }} + role={onClick ? 'button' : undefined} + tabIndex={onClick ? 0 : undefined} + sx={{ + cursor: onClick ? 'pointer' : undefined, + }} + > ([]); const [proxyDeployments, setProxyDeployments] = useState< @@ -649,27 +674,71 @@ export default function ProviderMapTab() { void loadData(); }, [loadData]); + // A proxy lives inside a project, so there is no meaningful org-level route to + // fall back to: if the project can't be resolved, stay put and say why rather + // than navigating somewhere the proxy isn't. const handleProxyClick = useCallback( (proxyId: string, proxyProjectId?: string) => { - // removed: ProjectBase no longer carries a `handler` alias field, so - // match on `id` only. - const proxyProject = projectsForCurrentOrganization.find( - (project) => project.id === proxyProjectId + if (!currentOrganization) { + logger.error( + `Unable to navigate to proxy ${proxyId} because the current organization is unavailable.` + ); + showSnackbar( + 'Unable to open this proxy right now. Please refresh and try again.', + 'error' + ); + return; + } + + const proxyPath = buildProxyPath( + currentOrganization, + projectsForCurrentOrganization, + proxyId, + proxyProjectId ); - if (!currentOrganization || !proxyProject) { + if (!proxyPath) { logger.error( `Unable to navigate to proxy ${proxyId} because project ${ proxyProjectId ?? '' } is unavailable.` ); + showSnackbar( + "Unable to open this proxy because its project isn't available to you.", + 'error' + ); + return; + } + + navigate(proxyPath); + }, + [ + currentOrganization, + navigate, + projectsForCurrentOrganization, + showSnackbar, + ] + ); + + // Gateways are organization-scoped, so unlike a proxy there is no project to + // resolve. Non-readers get a non-clickable card rather than a click that + // lands on a view they can't load. + const handleGatewayClick = useCallback( + (gatewayId: string) => { + if (!currentOrganization) { + logger.error( + `Unable to navigate to gateway ${gatewayId} because the current organization is unavailable.` + ); + showSnackbar( + 'Unable to open this gateway right now. Please refresh and try again.', + 'error' + ); return; } - const proxyPath = `/proxies/${encodeURIComponent(proxyId)}`; - navigate(buildProjectPath(currentOrganization, proxyProject, proxyPath)); + navigate(buildGatewayPath(currentOrganization, gatewayId)); }, - [currentOrganization, navigate, projectsForCurrentOrganization] + [currentOrganization, navigate, showSnackbar] ); const deploymentsByGateway = deployments.reduce< @@ -765,7 +834,7 @@ export default function ProviderMapTab() { proxy={proxy} deployment={proxyId ? proxyDeployments[proxyId] : undefined} onClick={ - proxyId + proxyId && canViewProxies ? () => handleProxyClick(proxyId, proxy.projectId) : undefined } @@ -810,6 +879,11 @@ export default function ProviderMapTab() { key={gw.id} gateway={gw} deployment={deploymentsByGateway[gw.id]} + onClick={ + canViewGateways + ? () => handleGatewayClick(gw.id) + : undefined + } /> )) )} diff --git a/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ResourceDrawerCards.tsx b/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ResourceDrawerCards.tsx index 5a719d7af1..4296d4eedf 100644 --- a/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ResourceDrawerCards.tsx +++ b/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ResourceDrawerCards.tsx @@ -41,7 +41,10 @@ type ResourceDrawerCardsProps = { gateways: Gateway[]; gatewayDeployments: Record; proxyDeployments: Record; - onProxyClick: (proxyId: string, proxyProjectId?: string) => void; + /** Omitted when the user can't read proxies — cards then render unclickable. */ + onProxyClick?: (proxyId: string, proxyProjectId?: string) => void; + /** Omitted when the user can't read gateways — cards then render unclickable. */ + onGatewayClick?: (gatewayId: string) => void; onCreateProxy?: () => void; onBlockedNavigation?: () => void; }; @@ -52,6 +55,7 @@ export default function ResourceDrawerCards({ gatewayDeployments, proxyDeployments, onProxyClick, + onGatewayClick, onCreateProxy, onBlockedNavigation, }: ResourceDrawerCardsProps) { @@ -222,7 +226,7 @@ export default function ResourceDrawerCards({ proxyId ? proxyDeployments[proxyId] : undefined } onClick={ - proxyId + proxyId && onProxyClick ? () => onProxyClick(proxyId, proxy.projectId) : undefined } @@ -242,6 +246,9 @@ export default function ResourceDrawerCards({ key={gw.id} gateway={gw} deployment={gatewayDeployments[gw.id]} + onClick={ + onGatewayClick ? () => onGatewayClick(gw.id) : undefined + } /> )) ))} diff --git a/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ServiceProviderOverviewTab.tsx b/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ServiceProviderOverviewTab.tsx index b14dcd1945..dc4bd8e4e9 100644 --- a/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ServiceProviderOverviewTab.tsx +++ b/portals/ai-workspace/src/pages/appShell/appShellPages/serviceProvider/ServiceProviderOverviewTab.tsx @@ -66,7 +66,12 @@ import NoData from '../../../../assets/images/NoData.svg'; import { FormattedMessage } from 'react-intl'; import useAIWorkspaceSnackbar from '../../../../hooks/aiWorkspaceSnackbar'; import SwaggerSpecViewer from '../../../../Components/SwaggerSpecViewer'; -import { buildProjectPath } from '../../../../utils/projectRouting'; +import { + buildGatewayPath, + buildProxyPath, +} from '../../../../utils/projectRouting'; +import { useAppAuth } from '../../../../contexts/AppAuthContext'; +import { SCOPES } from '../../../../auth/permissions'; import { formatPrefixedKey, resolveApiKeyAuthDisplay, @@ -136,6 +141,9 @@ export default function ServiceProviderOverviewTab({ const { provider, getProviderAPIKeys } = useLLMProvider(); const { currentOrganization, projectsForCurrentOrganization } = useAppShell(); const navigate = useNavigate(); + const { hasPermission } = useAppAuth(); + const canViewGateways = hasPermission(SCOPES.GATEWAY_READ); + const canViewProxies = hasPermission(SCOPES.LLM_PROXY_READ); const fetchedApiKeysProviderIdRef = useRef(null); const fetchingApiKeysProviderIdRef = useRef(null); const [gateways, setGateways] = useState([]); @@ -590,27 +598,71 @@ export default function ServiceProviderOverviewTab({ } }; + // A proxy lives inside a project, so there is no meaningful org-level route to + // fall back to: if the project can't be resolved, stay put and say why rather + // than navigating somewhere the proxy isn't. const handleProxyClick = useCallback( (proxyId: string, proxyProjectId?: string) => { - // removed: ProjectBase no longer carries a `handler` alias field, so - // match on `id` only. - const proxyProject = projectsForCurrentOrganization.find( - (project) => project.id === proxyProjectId + if (!currentOrganization) { + logger.error( + `Unable to navigate to proxy ${proxyId} because the current organization is unavailable.` + ); + showSnackbar( + 'Unable to open this proxy right now. Please refresh and try again.', + 'error' + ); + return; + } + + const proxyPath = buildProxyPath( + currentOrganization, + projectsForCurrentOrganization, + proxyId, + proxyProjectId ); - if (!currentOrganization || !proxyProject) { + if (!proxyPath) { logger.error( `Unable to navigate to proxy ${proxyId} because project ${ proxyProjectId ?? '' } is unavailable.` ); + showSnackbar( + "Unable to open this proxy because its project isn't available to you.", + 'error' + ); + return; + } + + navigate(proxyPath); + }, + [ + currentOrganization, + navigate, + projectsForCurrentOrganization, + showSnackbar, + ] + ); + + // Gateways are organization-scoped, so unlike a proxy there is no project to + // resolve. Non-readers get a non-clickable card rather than a click that + // lands on a view they can't load. + const handleGatewayClick = useCallback( + (gatewayId: string) => { + if (!currentOrganization) { + logger.error( + `Unable to navigate to gateway ${gatewayId} because the current organization is unavailable.` + ); + showSnackbar( + 'Unable to open this gateway right now. Please refresh and try again.', + 'error' + ); return; } - const proxyPath = `/proxies/${encodeURIComponent(proxyId)}`; - navigate(buildProjectPath(currentOrganization, proxyProject, proxyPath)); + navigate(buildGatewayPath(currentOrganization, gatewayId)); }, - [currentOrganization, navigate, projectsForCurrentOrganization] + [currentOrganization, navigate, showSnackbar] ); const handleDeleteApiKey = async () => { @@ -656,7 +708,8 @@ export default function ServiceProviderOverviewTab({ gateways={gateways} gatewayDeployments={gatewayDeployments} proxyDeployments={proxyDeployments} - onProxyClick={handleProxyClick} + onProxyClick={canViewProxies ? handleProxyClick : undefined} + onGatewayClick={canViewGateways ? handleGatewayClick : undefined} onCreateProxy={onCreateProxy} onBlockedNavigation={onBlockedNavigation} /> diff --git a/portals/ai-workspace/src/utils/projectRouting.ts b/portals/ai-workspace/src/utils/projectRouting.ts index ca708ca881..341b5ba3c8 100644 --- a/portals/ai-workspace/src/utils/projectRouting.ts +++ b/portals/ai-workspace/src/utils/projectRouting.ts @@ -72,6 +72,51 @@ export function buildOrgPath(org: OrgLike | null | undefined, path: string): str return `${base}/${path.replace(/^\/+/, '')}`; } +/** + * Resolves the route for a proxy that may live in any project of the current + * organization. + * + * `proxyProjectId` is the project handle (the same value `ProjectBase.id` + * carries), so the proxy's own project is matched by handle. Returns `null` + * when the proxy names a project outside `projects` — a stale listing, or a + * project the user can't see. Callers must surface that as an error rather + * than navigating somewhere the proxy doesn't live: an org-level fallback + * lands on a page whose project-scoped breadcrumbs and back links are wrong, + * and whose fetch fails anyway when the project is genuinely out of reach. + */ +export function buildProxyPath( + org: OrgLike | null | undefined, + projects: ProjectLike[], + proxyId: string, + proxyProjectId?: string +): string | null { + const proxyProject = proxyProjectId + ? projects.find((project) => project.id === proxyProjectId) + : undefined; + if (!proxyProject) return null; + + return buildProjectPath( + org, + proxyProject, + `/proxies/${encodeURIComponent(proxyId)}` + ); +} + +/** + * Resolves the route for a gateway detail view. + * + * Gateways are organization-scoped, not project-scoped, so this is always an + * org-level path. `gatewayId` is the gateway handle — the same value + * `Gateway.id` carries and the value `ViewGateway` matches its `:gatewayName` + * param against. + */ +export function buildGatewayPath( + org: OrgLike | null | undefined, + gatewayId: string +): string { + return buildOrgPath(org, `/gateways/view/${encodeURIComponent(gatewayId)}`); +} + export function buildProjectPath( org: OrgLike | null | undefined, project: ProjectLike | null | undefined, diff --git a/tools/apidocs/widdershins_templates/authentication.def b/tools/apidocs/widdershins_templates/authentication.def index f913266991..94e5472dca 100644 --- a/tools/apidocs/widdershins_templates/authentication.def +++ b/tools/apidocs/widdershins_templates/authentication.def @@ -1,3 +1,34 @@ +{{ var _schemes = (data.api.components && data.api.components.securitySchemes) || {}; }} +{{ var _reqs = data.security || []; }} +{{ var _isBasic = false; var _isBearer = false; var _scopes = []; }} +{{ for (var _r = 0; _r < _reqs.length; _r++) { + var _req = _reqs[_r]; + for (var _name in _req) { + var _sd = _schemes[_name] || {}; + if (_sd.type === 'http' && (_sd.scheme || '').toLowerCase() === 'basic') { _isBasic = true; } + if (_sd.type === 'http' && (_sd.scheme || '').toLowerCase() === 'bearer') { _isBearer = true; } + if (_sd.type === 'oauth2' || _sd.type === 'openIdConnect') { + _isBearer = true; + var _reqScopes = _req[_name] || []; + for (var _s = 0; _s < _reqScopes.length; _s++) { + if (_scopes.indexOf(_reqScopes[_s]) < 0) { _scopes.push(_reqScopes[_s]); } + } + } + } + } +}} +{{? _isBearer && !_isBasic }} + +{{??}} +{{?}} diff --git a/tools/apidocs/widdershins_templates/code_shell.dot b/tools/apidocs/widdershins_templates/code_shell.dot index 3cce32acf4..e9924929cf 100644 --- a/tools/apidocs/widdershins_templates/code_shell.dot +++ b/tools/apidocs/widdershins_templates/code_shell.dot @@ -1,7 +1,34 @@ {{ var defined_ct = data.allHeaders.find(function(h){ return h.name === 'Content-Type'; }); }} {{ if (defined_ct && data.consumes.indexOf('application/json') >= 0) { defined_ct.exampleValues.object = 'application/json'; } }} {{ var ext = 'json'; if (defined_ct) { var ct = defined_ct.exampleValues.object; if (ct.indexOf('yaml') >= 0) ext = 'yaml'; else if (ct.indexOf('xml') >= 0) ext = 'xml'; else if (ct.indexOf('text') >= 0) ext = 'txt'; } }} +{{ var _schemes = (data.api.components && data.api.components.securitySchemes) || {}; }} +{{ var _reqs = data.operation.security ? data.operation.security : (data.api.security || []); }} +{{ var _isBasic = false; var _isBearer = false; }} +{{ for (var _r = 0; _r < _reqs.length; _r++) { + var _req = _reqs[_r]; + for (var _name in _req) { + var _sd = _schemes[_name] || {}; + if (_sd.type === 'http' && (_sd.scheme || '').toLowerCase() === 'basic') { _isBasic = true; } + if (_sd.type === 'http' && (_sd.scheme || '').toLowerCase() === 'bearer') { _isBearer = true; } + if (_sd.type === 'oauth2' || _sd.type === 'openIdConnect') { _isBearer = true; } + } + } +}} +{{ var _hdrs = data.allHeaders.filter(function(h){ return h.name.toLowerCase() !== 'authorization'; }); }} +{{ var _isMultipart = data.consumes.indexOf('multipart/form-data') >= 0 && data.consumes.indexOf('application/json') < 0; }} +{{ var _mpFields = []; }} +{{ if (_isMultipart) { + var _ex = data.bodyParameter && data.bodyParameter.exampleValues && data.bodyParameter.exampleValues.object; + if (_ex && typeof _ex === 'object') { + for (var _k in _ex) { + var _v = _ex[_k]; + if (_v !== null && _v !== undefined && typeof _v !== 'object') { _mpFields.push(_k + '=' + _v); } + } + } + } +}} curl -X {{=data.methodUpper}} {{=data.url}}{{=data.requiredQueryString}} \ - -u {username}:{password}{{?data.allHeaders.length || data.operation.requestBody}} \{{?}} -{{~data.allHeaders :p:index}} -H '{{=p.name}}: {{=p.exampleValues.object}}'{{?index < data.allHeaders.length-1 || data.operation.requestBody}} \{{?}} -{{~}}{{?data.operation.requestBody}} -d @payload.{{=ext}}{{?}} +{{? _isBearer && !_isBasic }} -H 'Authorization: Bearer {access_token}'{{??}} -u {username}:{password}{{?}}{{?_hdrs.length || data.operation.requestBody}} \{{?}} +{{~_hdrs :p:index}} -H '{{=p.name}}: {{=p.exampleValues.object}}'{{?index < _hdrs.length-1 || data.operation.requestBody}} \{{?}} +{{~}}{{?data.operation.requestBody}}{{?_mpFields.length}}{{~_mpFields :_f:_fi}} -F '{{=_f}}'{{?_fi < _mpFields.length-1}} \{{?}} +{{~}}{{??}} -d @payload.{{=ext}}{{?}}{{?}} diff --git a/tools/apidocs/widdershins_templates/operation.dot b/tools/apidocs/widdershins_templates/operation.dot index 9f519e83de..2a4dd7dd02 100644 --- a/tools/apidocs/widdershins_templates/operation.dot +++ b/tools/apidocs/widdershins_templates/operation.dot @@ -27,7 +27,8 @@ {{? data.operation.description}}{{= data.operation.description }}{{?}} -{{? data.operation.requestBody}} +{{ var _mpOnly = data.consumes && data.consumes.indexOf('multipart/form-data') >= 0 && data.consumes.indexOf('application/json') < 0; }} +{{? data.operation.requestBody && !_mpOnly}} > Payload {{? data.bodyParameter.exampleValues.description }} diff --git a/tools/apidocs/widdershins_templates/responses.def b/tools/apidocs/widdershins_templates/responses.def index ac9679efde..66378a781d 100644 --- a/tools/apidocs/widdershins_templates/responses.def +++ b/tools/apidocs/widdershins_templates/responses.def @@ -6,7 +6,7 @@ {{? data.responseSchemas }} > Example responses - +> {{= data.utils.getResponseExamples(data) }} {{?}}