On this page

Web UI API Reference

Everything you can do by clicking through the Web UI, you can also do programmatically. The UI is built on the same REST endpoints documented here, which makes them a convenient surface for scripting your admin tasks.

Authentication

The Web UI uses cookie-based authentication. Set WebUi__AdminApiKey to enable.

Login

http
POST /ui/api/auth
Content-Type: application/json

{
  "apiKey": "your-admin-api-key"
}

Response:

json
{
  "ok": true,
  "expires": "2025-01-01T00:00:00Z"
}

Sets the portway_auth cookie.


Endpoints

GET /ui/api/overview

Dashboard overview data.

http
GET /ui/api/overview
Authorization: Bearer {token}

Response:

json
{
  "version": "1.0.0+build.123",
  "uptime_seconds": 3600,
  "endpoints": {
    "sql": 5,
    "proxy": 3,
    "static": 2,
    "files": 1,
    "webhooks": 2
  },
  "environments": ["dev", "test", "prod"],
  "server_name": "localhost"
}

GET /ui/api/endpoints

All configured endpoints grouped by type.

http
GET /ui/api/endpoints
Authorization: Bearer {token}

Response:

json
{
  "sql": [
    {
      "name": "Products",
      "namespace": "Catalog",
      "path": "endpoints/SQL/Catalog/Products/entity.json",
      "schema": "dbo",
      "primary_key": "ProductId",
      "allowed_columns": ["ProductId", "Name", "Price"]
    }
  ],
  "proxy": [...],
  "static": [...],
  "files": [...],
  "webhooks": [...]
}

POST /ui/api/endpoints/

Checks an endpoint configuration without saving it, using the same rules the loader applies at startup. This is a convenient pre-flight before writing an entity.json, whether by hand or from automation.

http
POST /ui/api/endpoints/sql/validate
Content-Type: application/json
X-CSRF-Token: {csrf}

{ "content": { "DatabaseObjectName": "Products", "DatabaseSchema": "dbo" } }

Response:

json
{ "valid": true, "errors": [] }

The content field accepts either a JSON object or a string containing JSON. Validation covers JSON syntax, the per-type required fields (DatabaseObjectName for SQL and Webhook, Url and Methods for Proxy and Composite), and the namespace naming rules. A failed check returns valid: false with each problem listed in errors; the endpoint on disk is never touched.

GET /ui/api/environments

Environment configuration.

http
GET /ui/api/environments
Authorization: Bearer {token}

Response:

json
{
  "server_name": "localhost",
  "allowed_environments": ["dev", "test", "prod"],
  "environments": {
    "dev": { "connection_string": "..." },
    "prod": { "connection_string": "..." }
  }
}

PATCH /ui/api/environments

Update environment configuration.

http
PATCH /ui/api/environments
Authorization: Bearer {token}
Content-Type: application/json

{
  "allowed_environments": ["dev", "staging", "prod"],
  "environments": {
    "dev": { "connection_string": "Server=dev;..." },
    "prod": { "connection_string": "Server=prod;..." }
  }
}

GET /ui/api/settings

Full application settings dump.

http
GET /ui/api/settings
Authorization: Bearer {token}

Response:

json
{
  "rate_limiting": {
    "enabled": true,
    "ip_limit": 100,
    "ip_window_seconds": 60,
    "token_limit": 100,
    "token_window_seconds": 60
  },
  "caching": {
    "enabled": true,
    "provider": "Memory",
    "default_duration_seconds": 300
  },
  "sql_pooling": {
    "enabled": true,
    "min_pool_size": 5,
    "max_pool_size": 100
  },
  "logging": {
    "min_level": "Information",
    "sinks": ["Console", "File"]
  }
}

GET /ui/api/tokens

List API tokens.

http
GET /ui/api/tokens
Authorization: Bearer {token}
http
GET /ui/api/tokens?include_revoked=true
Authorization: Bearer {token}

Response:

json
[
  {
    "id": 1,
    "username": "api-user",
    "description": "Production API",
    "created_at": "2025-01-01 00:00:00",
    "expires_at": null,
    "revoked_at": null,
    "allowed_scopes": "*",
    "allowed_environments": "*",
    "is_active": true
  }
]

POST /ui/api/tokens

Create a new token.

http
POST /ui/api/tokens
Authorization: Bearer {token}
Content-Type: application/json

{
  "username": "new-user",
  "description": "Read-only access",
  "allowed_scopes": "read",
  "allowed_environments": "dev,test",
  "expires_in_days": 90
}

Response:

json
{
  "ok": true,
  "token": {
    "token": "pw_abc123...",
    "username": "new-user",
    "expires_at": "2025-04-01T00:00:00Z"
  }
}

PUT /ui/api/tokens/{id}

Update token properties.

http
PUT /ui/api/tokens/1
Authorization: Bearer {token}
Content-Type: application/json

{
  "allowed_scopes": "read,write",
  "allowed_environments": "dev,test,prod",
  "description": "Updated description",
  "expires_at": "2025-06-01T00:00:00Z"
}

DELETE /ui/api/tokens/{id}

Revoke a token.

http
DELETE /ui/api/tokens/1
Authorization: Bearer {token}

Response:

json
{
  "ok": true
}

POST /ui/api/tokens/

Rotate (revoke and recreate) a token.

http
POST /ui/api/tokens/1/rotate
Authorization: Bearer {token}

Response:

json
{
  "ok": true,
  "token": {
    "token": "pw_xyz789...",
    "username": "api-user"
  }
}

POST /ui/api/tokens/

Unarchive a revoked token.

http
POST /ui/api/tokens/1/unarchive
Authorization: Bearer {token}

GET /ui/api/tokens/

Get token audit log.

http
GET /ui/api/tokens/1/audit
Authorization: Bearer {token}

Response:

json
[
  {
    "operation": "Created",
    "timestamp": "2025-01-01 00:00:00",
    "details": "Token created",
    "ip_address": "192.168.1.1",
    "user_agent": "Portway/1.0"
  },
  {
    "operation": "Revoked",
    "timestamp": "2025-01-15 00:00:00",
    "details": "Revoked by admin",
    "ip_address": "192.168.1.1",
    "user_agent": "Portway/1.0"
  }
]

GET /ui/api/logs

Browse application logs.

http
GET /ui/api/logs?page=1&limit=50
Authorization: Bearer {token}

Query Parameters: | Parameter | Default | Description | |-----------|---------|-------------| | page | 1 | Page number | | limit | 50 | Items per page (max 100) | | level | - | Filter by level (Debug, Information, Warning, Error) | | search | - | Search in message |

Response:

json
{
  "page": 1,
  "total_pages": 10,
  "logs": [
    {
      "timestamp": "2025-01-01T10:30:00.000Z",
      "level": "Information",
      "message": "Application started",
      "source": "Program"
    }
  ]
}

GET /ui/api/events

Server-Sent Events stream for real-time updates.

http
GET /ui/api/events

Events:

json
event: health
data: {"status":"Healthy"}
json
event: endpoint_reload
data: {"type":"sql","count":5}

SSE Events

The UI subscribes to real-time updates via Server-Sent Events.

Event Types

Event Data Description
health {status: "Healthy|Degraded|Unhealthy"} Health status change
endpoint_reload {type, count} Endpoints reloaded
token_created {id, username} New token created
token_revoked {id, username} Token revoked

Error Responses

401 Unauthorized

json
{
  "error": "Unauthorized",
  "message": "Valid authentication required"
}

403 Forbidden

json
{
  "error": "Access denied",
  "clientIp": "192.168.1.100",
  "requestedPath": "/ui/dashboard"
}

404 Not Found

json
{
  "error": "Not found",
  "message": "Resource does not exist"
}

409 Conflict

json
{
  "error": "Conflict",
  "message": "Token with this username already exists"
}

Rate Limiting

UI API endpoints are subject to rate limiting. Returns 429 Too Many Requests when exceeded.

Last updated: 2026-07-23