Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/devportal-integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Developer Portal Integration Test

on:
workflow_dispatch:
pull_request:
branches:
- main
paths:
- 'portals/developer-portal/**'
- '.github/workflows/devportal-integration-test.yml'

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

permissions:
contents: read

jobs:
rest-api-test:
name: REST API tests (${{ matrix.db }})
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
db: [sqlite, postgres]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build developer-portal image
run: make -C portals/developer-portal build

- name: Run REST API integration tests (${{ matrix.db }})
run: |
if [ "${{ matrix.db }}" = "postgres" ]; then
make -C portals/developer-portal/it test-rest-api-postgres
else
make -C portals/developer-portal/it test-rest-api
fi

- name: Upload test reports
uses: actions/upload-artifact@v4
if: always()
with:
name: rest-api-test-reports-${{ matrix.db }}
path: portals/developer-portal/it/reports/
retention-days: 7

- name: Debug on failure - dump container logs
if: failure()
run: |
echo "=== Docker containers ==="
docker ps -a

ui-test:
name: UI E2E tests (Cypress)
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Build developer-portal image
run: make -C portals/developer-portal build

- name: Run Cypress integration tests
run: make -C portals/developer-portal it

- name: Upload Cypress reports
uses: actions/upload-artifact@v4
if: always()
with:
name: cypress-reports
path: portals/developer-portal/it/reports/
retention-days: 7

- name: Debug on failure - dump container logs
if: failure()
run: |
echo "=== Docker containers ==="
docker ps -a
6 changes: 5 additions & 1 deletion docs/rest-apis/devportal/api-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ This operation requires <strong>Basic Auth</strong> authentication.
"apiWorkflowDefinition": "string",
"markdownContent": "string",
"createdAt": "May 7, 2026",
"updatedAt": "string"
"updatedAt": "string",
"createdBy": "string",
"updatedBy": "string"
}
],
"pagination": {
Expand Down Expand Up @@ -171,6 +173,8 @@ Status Code **200**
|»» markdownContent|string¦null|false|none|none|
|»» createdAt|string|false|none|none|
|»» updatedAt|string¦null|false|none|none|
|»» createdBy|string¦null|false|none|none|
|»» updatedBy|string¦null|false|none|none|
|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.|
|»» total|integer|true|none|Total number of records matching the query.|
|»» limit|integer|true|none|Maximum number of records returned in this response.|
Expand Down
12 changes: 6 additions & 6 deletions docs/rest-apis/devportal/apis.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ curl -X POST https://localhost:3000/api/v0.9/apis \

```

Creates Developer Portal API metadata from either a full API artifact ZIP, an API metadata YAML file (`api.yaml` / `devportal.yaml` / `mcp.yaml`), or an `apiMetadata` JSON string. An API definition file is required unless supplied by the artifact ZIP. The YAML `spec` block accepts: `displayName`, `version`, `description`, `type`, `status`, `agentVisibility`, `tags`, `labels`, `referenceId`, `endpoints` (sandboxUrl, productionUrl), `businessInformation` (owners), and `subscriptionPlans`. The service also stores labels, subscription plan mappings, image metadata, and schema definitions for GraphQL APIs when provided. MCP servers must be created via `POST /api/v0.9/mcp-servers` instead — a request whose resolved `type` is `MCP` is rejected with `400`.
Creates Developer Portal API metadata from either a full API artifact ZIP, an API metadata YAML file (`api.yaml` / `devportal.yaml` / `mcp.yaml`), or an `apiMetadata` JSON string. An API definition file is required unless supplied by the artifact ZIP. The YAML `spec` block accepts: `displayName`, `version`, `description`, `type`, `status`, `agentVisibility`, `tags`, `labels`, `referenceId`, `endpoints` (sandboxUrl, productionUrl), `businessInformation` (owners), and `subscriptionPlans`. The service also stores labels, subscription plan mappings, image metadata, and schema definitions for GraphQL APIs when provided. Via the JSON `apiMetadata` field, `type` is required — an omitted type is rejected with `400` (via YAML, an omitted `spec.type` defaults to `REST`). MCP servers must be created via `POST /api/v0.9/mcp-servers` instead — a request whose resolved `type` is `MCP` is rejected with `400`.
`subscriptionPlans` links existing org-level plans to this API by name — it does not create plans. In YAML it is a string array (`["Gold", "Silver"]`). In the JSON `apiMetadata` field it is an object array where only `id` is used (`[{"id":"Gold"}]`); extra fields such as `planId`, `displayName`, or `requestCount` are ignored.

> Payload
Expand Down Expand Up @@ -315,7 +315,7 @@ Status Code **200**
|»»» version|string|false|none|none|
|»»» status|string|false|none|API lifecycle status.|
|»»» description|string|false|none|none|
|»»» type|string|false|none|none|
|»»» type|string|false|none|The stored/returned type constant (src/utils/constants.js API_TYPE) — distinct from the request-time keyword accepted on create/update (see `type` in ApiMetadataMultipartBody: REST, SOAP, MCP, WS, WEBSUB, GRAPHQL). REST maps to `RestApi` and WEBSUB maps to `WebSubApi`; the rest are returned unchanged.|
Comment thread
coderabbitai[bot] marked this conversation as resolved.
|»»» referenceId|string¦null|false|none|External reference ID. Present when the API was created from a `devportal.yaml` artifact whose `spec` block sets `referenceId` — the create response echoes the parsed YAML back.|
|»»» agentVisibility|string|false|none|none|
|»»» addedLabels|[string]|false|none|none|
Expand Down Expand Up @@ -389,11 +389,11 @@ Status Code **200**
|---|---|
|status|PUBLISHED|
|status|DEPRECATED|
|type|REST|
|type|RestApi|
|type|SOAP|
|type|MCP|
|type|Mcp|
|type|WS|
|type|WEBSUB|
|type|WebSubApi|
|type|GRAPHQL|
|agentVisibility|VISIBLE|
|agentVisibility|HIDDEN|
Expand Down Expand Up @@ -566,7 +566,7 @@ curl -X PUT https://localhost:3000/api/v0.9/apis/{apiId} \

```

Updates Developer Portal API metadata and its stored definition. Accepts the same YAML spec fields and `apiMetadata` JSON format as the create operation. The update flow can also adjust label mappings, subscription plan mappings, schema definitions, and image metadata. Status changes to unpublished are rejected when active subscriptions exist.
Updates Developer Portal API metadata and its stored definition. Accepts the same YAML spec fields and `apiMetadata` JSON format as the create operation. The update flow can also adjust label mappings, subscription plan mappings, schema definitions, and image metadata. Status changes to unpublished are rejected when active subscriptions exist. `type` is required (see the create operation) and is immutable — it must match the API's existing type; a different value is rejected with `409`.

> Payload

Expand Down
12 changes: 6 additions & 6 deletions docs/rest-apis/devportal/mcp-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ curl -X POST https://localhost:3000/api/v0.9/mcp-servers \

```

Creates Developer Portal MCP server metadata. Mirrors `POST /api/v0.9/apis` — same artifact ZIP, YAML (`api.yaml` / `devportal.yaml` / `mcp.yaml`), and `apiMetadata` JSON input formats — but the created record is always typed `MCP`, regardless of what `type` is supplied.
Creates Developer Portal MCP server metadata. Mirrors `POST /api/v0.9/apis` — same artifact ZIP, YAML (`api.yaml` / `devportal.yaml` / `mcp.yaml`), and `apiMetadata` JSON input formats — but the created record is always typed `MCP`. Via the JSON `apiMetadata` field, `type` must be explicitly `MCP`; an omitted type or any other value is rejected with a 400 (use `POST /api/v0.9/apis` instead).

> Payload

Expand Down Expand Up @@ -314,7 +314,7 @@ Status Code **200**
|»»» version|string|false|none|none|
|»»» status|string|false|none|API lifecycle status.|
|»»» description|string|false|none|none|
|»»» type|string|false|none|none|
|»»» type|string|false|none|The stored/returned type constant (src/utils/constants.js API_TYPE) — distinct from the request-time keyword accepted on create/update (see `type` in ApiMetadataMultipartBody: REST, SOAP, MCP, WS, WEBSUB, GRAPHQL). REST maps to `RestApi` and WEBSUB maps to `WebSubApi`; the rest are returned unchanged.|
|»»» referenceId|string¦null|false|none|External reference ID. Present when the API was created from a `devportal.yaml` artifact whose `spec` block sets `referenceId` — the create response echoes the parsed YAML back.|
|»»» agentVisibility|string|false|none|none|
|»»» addedLabels|[string]|false|none|none|
Expand Down Expand Up @@ -388,11 +388,11 @@ Status Code **200**
|---|---|
|status|PUBLISHED|
|status|DEPRECATED|
|type|REST|
|type|RestApi|
|type|SOAP|
|type|MCP|
|type|Mcp|
|type|WS|
|type|WEBSUB|
|type|WebSubApi|
|type|GRAPHQL|
|agentVisibility|VISIBLE|
|agentVisibility|HIDDEN|
Expand Down Expand Up @@ -561,7 +561,7 @@ curl -X PUT https://localhost:3000/api/v0.9/mcp-servers/{mcpServerId} \

```

Updates Developer Portal MCP server metadata and its stored definition. Mirrors `PUT /api/v0.9/apis/{apiId}`.
Updates Developer Portal MCP server metadata and its stored definition. Mirrors `PUT /api/v0.9/apis/{apiId}`, including `type` being required and immutable — it must stay `MCP`; any other value is rejected with `400`, via the same resolveTypeOrReject check `POST /mcp-servers` uses.

> Payload

Expand Down
18 changes: 14 additions & 4 deletions docs/rest-apis/devportal/organizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ This operation requires <strong>Basic Auth</strong> authentication.
"cpRefId": "string",
"configuration": {
"devportalMode": "DEFAULT"
}
},
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
```

Expand Down Expand Up @@ -224,7 +226,9 @@ This operation requires <strong>Basic Auth</strong> authentication.
"cpRefId": "string",
"configuration": {
"devportalMode": "DEFAULT"
}
},
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
],
"pagination": {
Expand Down Expand Up @@ -268,6 +272,8 @@ Status Code **200**
|»» cpRefId|string¦null|false|none|Control Plane reference ID. Included in outbound webhook event payloads so subscribers can correlate this organization with its Control Plane (Platform API) counterpart. Not used for authentication or org resolution.|
|»» configuration|object|false|none|Organization portal configuration. Always includes `devportalMode`; may contain additional free-form keys set by the caller.|
|»»» devportalMode|string|false|none|Controls the mode of the developer portal.|
|»» createdAt|string(date-time)¦null|false|none|none|
|»» updatedAt|string(date-time)¦null|false|none|none|
|» pagination|[Pagination](schemas.md#schemapagination)|false|none|Standard pagination metadata returned with collection responses.|
|»» total|integer|true|none|Total number of records matching the query.|
|»» limit|integer|true|none|Maximum number of records returned in this response.|
Expand Down Expand Up @@ -361,7 +367,9 @@ This operation requires <strong>Basic Auth</strong> authentication.
"cpRefId": "string",
"configuration": {
"devportalMode": "DEFAULT"
}
},
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
```

Expand Down Expand Up @@ -493,7 +501,9 @@ This operation requires <strong>Basic Auth</strong> authentication.
"cpRefId": "string",
"configuration": {
"devportalMode": "DEFAULT"
}
},
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}
```

Expand Down
26 changes: 17 additions & 9 deletions docs/rest-apis/devportal/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,9 @@ Ad hoc error shape used by the Subscriptions and API Keys handlers, which build
"cpRefId": "string",
"configuration": {
"devportalMode": "DEFAULT"
}
},
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
}

```
Expand All @@ -276,6 +278,8 @@ Ad hoc error shape used by the Subscriptions and API Keys handlers, which build
|cpRefId|string¦null|false|none|Control Plane reference ID. Included in outbound webhook event payloads so subscribers can correlate this organization with its Control Plane (Platform API) counterpart. Not used for authentication or org resolution.|
|configuration|object|false|none|Organization portal configuration. Always includes `devportalMode`; may contain additional free-form keys set by the caller.|
|» devportalMode|string|false|none|Controls the mode of the developer portal.|
|createdAt|string(date-time)¦null|false|none|none|
|updatedAt|string(date-time)¦null|false|none|none|

#### Enumerated Values

Expand Down Expand Up @@ -348,7 +352,7 @@ Ad hoc error shape used by the Subscriptions and API Keys handlers, which build
"version": "string",
"status": "PUBLISHED",
"description": "string",
"type": "REST",
"type": "RestApi",
"referenceId": "string",
"agentVisibility": "VISIBLE",
"addedLabels": [
Expand Down Expand Up @@ -439,7 +443,7 @@ and
"version": "string",
"status": "PUBLISHED",
"description": "string",
"type": "REST",
"type": "RestApi",
"referenceId": "string",
"agentVisibility": "VISIBLE",
"addedLabels": [
Expand Down Expand Up @@ -542,7 +546,7 @@ and
"version": "string",
"status": "PUBLISHED",
"description": "string",
"type": "REST",
"type": "RestApi",
"referenceId": "string",
"agentVisibility": "VISIBLE",
"addedLabels": [
Expand Down Expand Up @@ -583,7 +587,7 @@ Fields are returned at the root of ApiMetadataResponse / ApiMetadataCreateRespon
|version|string|false|none|none|
|status|string|false|none|API lifecycle status.|
|description|string|false|none|none|
|type|string|false|none|none|
|type|string|false|none|The stored/returned type constant (src/utils/constants.js API_TYPE) — distinct from the request-time keyword accepted on create/update (see `type` in ApiMetadataMultipartBody: REST, SOAP, MCP, WS, WEBSUB, GRAPHQL). REST maps to `RestApi` and WEBSUB maps to `WebSubApi`; the rest are returned unchanged.|
Comment thread
coderabbitai[bot] marked this conversation as resolved.
|referenceId|string¦null|false|none|External reference ID. Present when the API was created from a `devportal.yaml` artifact whose `spec` block sets `referenceId` — the create response echoes the parsed YAML back.|
|agentVisibility|string|false|none|none|
|addedLabels|[string]|false|none|none|
Expand All @@ -599,11 +603,11 @@ Fields are returned at the root of ApiMetadataResponse / ApiMetadataCreateRespon
|---|---|
|status|PUBLISHED|
|status|DEPRECATED|
|type|REST|
|type|RestApi|
|type|SOAP|
|type|MCP|
|type|Mcp|
|type|WS|
|type|WEBSUB|
|type|WebSubApi|
|type|GRAPHQL|
|agentVisibility|VISIBLE|
|agentVisibility|HIDDEN|
Expand Down Expand Up @@ -1891,7 +1895,9 @@ Access token response proxied from the key manager's token endpoint. Field names
"apiWorkflowDefinition": "string",
"markdownContent": "string",
"createdAt": "May 7, 2026",
"updatedAt": "string"
"updatedAt": "string",
"createdBy": "string",
"updatedBy": "string"
}

```
Expand All @@ -1911,6 +1917,8 @@ Access token response proxied from the key manager's token endpoint. Field names
|markdownContent|string¦null|false|none|none|
|createdAt|string|false|none|none|
|updatedAt|string¦null|false|none|none|
|createdBy|string¦null|false|none|none|
|updatedBy|string¦null|false|none|none|

#### Enumerated Values

Expand Down
3 changes: 3 additions & 0 deletions portals/developer-portal/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,6 @@ resources/mock

.env
configs/config-platform-api.toml

# Test reports
it/reports/*
17 changes: 13 additions & 4 deletions portals/developer-portal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,19 @@ What happens automatically on first start:
### Test

```bash
# Run Cypress integration tests headlessly inside Docker
# Run the Cypress UI E2E suite headlessly inside Docker
make it

# Run the REST API suite (Jest + Supertest)
make -C it test-rest-api

# Open Cypress interactive UI — requires the portal running locally first
make it-open
```

For integration test details, see [it/README.md](it/README.md).
Both suites also run on pull requests via the
[Developer Portal Integration Test](../../.github/workflows/devportal-integration-test.yml)
GitHub Actions workflow. For integration test details, see [it/README.md](it/README.md).

### Clean

Expand Down Expand Up @@ -106,9 +111,13 @@ Run `make help` to see the full list. Summary:

| Target | Description |
|--------|-------------|
| `make it` | Run Cypress integration tests against SQLite (headless, in Docker) |
| `make it-postgres` | Run Cypress integration tests against PostgreSQL (headless, in Docker) |
| `make it` | Run the Cypress UI E2E suite against SQLite (headless, in Docker) |
| `make it-postgres` | Run the Cypress UI E2E suite against PostgreSQL (headless, in Docker) |
| `make it-open` | Open Cypress interactive UI (requires the portal running locally) |
| `make -C it test-rest-api` | Run the REST API suite (Jest + Supertest) against SQLite |
| `make -C it test-rest-api-postgres` | Run the REST API suite against PostgreSQL |

See [it/README.md](it/README.md) for the full list of test commands and suite details.

### Database

Expand Down
Loading
Loading