API Surface Design#

Detailed API surface for the scafctl REST API server. All endpoints use JSON request/response bodies and follow REST conventions.

Base URL#

http://localhost:8080

Version-prefixed endpoints use /{apiVersion}/ (default: /v1/).

Authentication#

  • Method: Entra OIDC (Azure AD) JWT Bearer tokens
  • Header: Authorization: Bearer <token>
  • Public endpoints: Health probes and metrics bypass authentication
  • Admin endpoints: Require authentication + admin role claim (or localhost-only when auth is disabled)

Response Format#

Success (single resource)#

{
  "fieldA": "value",
  "fieldB": 42
}

Success (collection with pagination)#

{
  "items": [...],
  "pagination": {
    "page": 1,
    "per_page": 100,
    "total_items": 250,
    "total_pages": 3,
    "has_more": true
  }
}

Error (RFC 7807 Problem Details)#

{
  "title": "Bad Request",
  "status": 400,
  "detail": "validation failed: name is required"
}

Naming Conventions#

LayerConventionExample
Config (YAML/JSON)camelCaseapiVersion, shutdownTimeout
API response fieldssnake_caseper_page, total_items, has_more
URL pathslowercase, kebab-case for multi-word/v1/solutions, /v1/admin/reload-config
Query parameterssnake_caseper_page, filter

Endpoints#

Operational (Root Router — No Auth)#

MethodPathDescriptionStatus Codes
GET/API root with HATEOAS links200
GET/healthFull health check with component status200
GET/health/liveLiveness probe200
GET/health/readyReadiness probe200, 503
GET/metricsPrometheus metrics200

Solutions#

MethodPathDescriptionQuery ParamsStatus Codes
POST/v1/solutions/runRun a solution (resolve + execute actions)200, 400, 422
POST/v1/solutions/renderResolve inputs without executing actions200, 400, 422
POST/v1/solutions/lintLint a solution200, 400
POST/v1/solutions/inspectInspect solution structure200, 400
POST/v1/solutions/testValidate solution against provider registry200, 400
POST/v1/solutions/dryrunDry-run a solution (what-if analysis)200, 400, 422

Providers#

MethodPathDescriptionQuery ParamsStatus Codes
GET/v1/providersList providerspage, per_page, filter200, 400
GET/v1/providers/{name}Get provider details200, 404
GET/v1/providers/{name}/schemaGet provider schema200, 404

Eval#

MethodPathDescriptionStatus Codes
POST/v1/eval/celEvaluate CEL expression200, 400
POST/v1/eval/templateEvaluate Go template200, 400

Catalogs#

MethodPathDescriptionQuery ParamsStatus Codes
GET/v1/catalogsList catalogspage, per_page, filter200, 400
GET/v1/catalogs/{name}Get catalog details200, 404
GET/v1/catalogs/{name}/solutionsList catalog solutionspage, per_page200, 404
POST/v1/catalogs/syncSync catalogs (planned)200, 400

Schemas#

MethodPathDescriptionQuery ParamsStatus Codes
GET/v1/schemasList schemaspage, per_page200
GET/v1/schemas/{name}Get schema200, 404
POST/v1/schemas/validateValidate against schema200, 400, 422

Config#

MethodPathDescriptionStatus Codes
GET/v1/configGet current config200
GET/v1/settingsGet runtime settings200

Snapshots#

MethodPathDescriptionQuery ParamsStatus Codes
GET/v1/snapshotsList snapshots (planned)page, per_page200
GET/v1/snapshots/{id}Get snapshot details (planned)200, 404

Explain & Diff#

MethodPathDescriptionStatus Codes
POST/v1/explainExplain a solution200, 400
POST/v1/diffDiff solutions200, 400

Admin#

MethodPathDescriptionAuthorizationStatus Codes
GET/v1/admin/infoServer infoadmin role / localhost200, 403
POST/v1/admin/reload-configHot-reload config (planned)admin role / localhost200, 403
POST/v1/admin/clear-cacheClear caches (planned)admin role / localhost200, 403

Documentation (Auto-Generated by Huma)#

MethodPathDescriptionStatus Codes
GET/v1/docsInteractive API documentation200
GET/v1/openapi.jsonOpenAPI specification (JSON)200

Common Query Parameters#

ParameterTypeDescriptionDefaultConstraints
pageintPage number (1-indexed)11–10000
per_pageintItems per page1001–1000
filterstringCEL filter expressionmax 2000 chars

Global Status Codes#

These status codes can be returned by any endpoint, in addition to endpoint-specific codes:

CodeCondition
401 UnauthorizedMissing or invalid auth token (when auth is enabled)
403 ForbiddenInsufficient role (admin endpoints), or non-loopback with auth disabled
405 Method Not AllowedHTTP method not supported for this resource
413 Request Entity Too LargeRequest body exceeds maxRequestSize (default 10 MB)
429 Too Many RequestsRate limit exceeded (includes Retry-After header)
503 Service UnavailableServer is shutting down (readiness probe only)

Middleware Stack#

The API uses a two-layer middleware architecture. Health probes and metrics bypass the API middleware entirely.

Global Middleware (All Routes)#

OrderMiddlewarePurpose
1Panic RecoveryCatches panics, logs stack trace, returns 500
2Request IDGenerates unique X-Request-ID per request
3Strip SlashesNormalizes trailing slashes in URLs
4Request LoggingStructured log of method, path, status, duration

API Middleware (Business Endpoints)#

OrderMiddlewarePurpose
1CORSCross-Origin Resource Sharing (configurable)
2TimeoutRequest timeout enforcement
3ThrottleMax concurrent connections
4AuthenticationEntra OIDC JWT validation
5Rate LimitingPer-IP sliding window limiter
6Max Body SizeRequest body size validation
7Compressiongzip response compression
8Security HeadersCSP, X-Frame-Options, HSTS, etc.
9MetricsOpenTelemetry metrics collection
10Audit LoggingStructured audit logs with field redaction
11TracingOpenTelemetry distributed tracing

Rate Limiting#

When rate limiting is enabled, every API response includes standard rate limit headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the rate limit window resets
Retry-AfterDuration until retry is allowed (only on 429 responses)

Security#

Security Headers#

All API responses include the following security headers:

HeaderValue
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
Content-Security-Policydefault-src 'none'
X-XSS-Protection0
Referrer-Policystrict-origin-when-cross-origin
Permissions-Policycamera=(), microphone=(), geolocation=()
Strict-Transport-Securitymax-age=63072000; includeSubDomains; preload (TLS only)

TLS Support#

The server supports TLS with configurable certificate and key paths. When TLS is enabled, HSTS headers are automatically added to all responses.

CEL Filter Sandboxing#

User-supplied filter expressions are evaluated with:

  • Cost limit: 10,000 (lower than CLI default of 1,000,000)
  • Read-only: No I/O, no mutations, no filesystem access
  • Max length: 2,000 characters

Audit Log Redaction#

Request bodies in audit logs have sensitive fields redacted:

  • Fields matching password, secret, token, key, credential, authorization[REDACTED]

Admin Authorization#

Auth StateAdmin Access
Entra OIDC enabledRequires admin role in JWT claims
Auth disabledLocalhost-only (non-loopback → 403)

Server Configuration#

The API server is configured via the apiServer section in the scafctl config file:

FieldTypeDefaultDescription
hoststring127.0.0.1Bind address
portint8080Listen port
apiVersionstringv1URL path version prefix
requestTimeoutstring30sPer-request timeout
shutdownTimeoutstring30sGraceful shutdown window
maxConcurrentint100Max concurrent connections
maxRequestSizeint6410485760Max request body size (bytes)
compression.levelint6Gzip compression level
cors.enabledboolfalseEnable CORS
cors.allowedOrigins[]stringAllowed origins
cors.allowedMethods[]stringAllowed HTTP methods
cors.allowedHeaders[]stringAllowed headers
cors.maxAgeintPreflight cache duration (seconds)
tls.enabledboolfalseEnable TLS
tls.certstringPath to TLS certificate
tls.keystringPath to TLS private key
auth.azureOIDC.enabledboolfalseEnable Entra OIDC auth
auth.azureOIDC.tenantIdstringAzure AD tenant ID
auth.azureOIDC.clientIdstringAzure AD client ID
rateLimit.global.maxRequestsintMax requests per window
rateLimit.global.windowstringRate limit window duration
audit.enabledboolfalseEnable audit logging
tracing.enabledboolfalseEnable OpenTelemetry tracing