Stage 7 · Master
Self-Service Platform API & CLI
Platform API Design Principles
Intent-driven APIs over infrastructure APIs. Async operations, idempotency, versioning, and error handling for great DX.
Intent-Driven vs Infrastructure APIs
Infrastructure APIs expose raw resources (K8s Deployment, AWS RDS, GCP VPC). Intent-driven APIs express what the developer wants to achieve. The platform translates intent to infrastructure.
| Infrastructure API | Intent-Driven API |
|---|---|
| POST /deployments (K8s Deployment YAML) | POST /services (name, language, capabilities) |
| PUT /rds-instances (instance class, storage) | POST /databases (type: postgresql, plan: prod) |
| PATCH /network-policies (CIDR, ports) | POST /services/{name}/networking (ingress: true, auth: oauth2) |
| Developer must know K8s, AWS, GCP | Developer expresses business intent |
| Changes break when infra changes | Platform absorbs infra changes |
| No validation of intent | Validation at API layer |
The Platform API is the primary product interface. It should be designed with the same care as a public API: consistent naming, comprehensive docs, SDKs, versioning, deprecation policy, and SLOs.
Async Operations
Provisioning takes time (DB: 3-5 min, Cluster: 10-20 min). Synchronous APIs timeout, block CI/CD, and create poor UX. Use async pattern: return operation ID immediately, provide status endpoint, support webhooks/polling.
# Request
POST /api/v1/services
Content-Type: application/json
Authorization: Bearer <token>
{
"name": "payment-api",
"team": "payments",
"language": "go",
"capabilities": ["postgresql", "kafka"]
}
# Response (202 Accepted)
HTTP/1.1 202 Accepted
Content-Type: application/json
Location: /api/v1/operations/op-abc123
{
"operationId": "op-abc123",
"status": "pending",
"resourceType": "service",
"resourceName": "payment-api",
"startedAt": "2024-01-15T10:30:00Z",
"estimatedCompletion": "2024-01-15T10:35:00Z",
"_links": {
"self": "/api/v1/operations/op-abc123",
"resource": "/api/v1/services/payment-api"
}
}
# Poll for status
GET /api/v1/operations/op-abc123
# Response (200 OK)
{
"operationId": "op-abc123",
"status": "succeeded",
"resourceType": "service",
"resourceName": "payment-api",
"startedAt": "2024-01-15T10:30:00Z",
"completedAt": "2024-01-15T10:33:42Z",
"result": {
"serviceUrl": "https://payment-api.platform.example.com",
"githubRepo": "https://github.com/myorg/payment-api",
"argocdApp": "payments-payment-api"
}
}
Async pattern: 202 Accepted with operation ID, Location header, status endpoint, result on completion.
Idempotency
Network failures, retries, and duplicate clicks happen. All mutating operations MUST be idempotent. Use idempotency keys (client-generated UUID) for POST/PUT/PATCH/DELETE.
POST /api/v1/services
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json
{ "name": "payment-api", "team": "payments", ... }
# First request: 202 Accepted, creates service
# Retry with same key: 202 Accepted, returns SAME operation ID
# Different key with same name: 409 Conflict (name taken)
Idempotency-Key header ensures safe retries. Server stores key → operation mapping for 24h.
Versioning Strategy
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL Path | /api/v1/services | Explicit, cacheable, easy to route | URL changes on version bump |
| Header | Accept: application/vnd.platform.v1+json | Clean URLs, multiple versions per endpoint | Less visible, harder to debug |
| Query Param | /api/services?version=1 | Simple | Not RESTful, caching issues |
Use URL path versioning (/api/v1/, /api/v2/). Support max 2 versions simultaneously. Deprecation: 6-month notice, Deprecation header, Sunset header, migration guide in docs.
Error Handling
{
"type": "https://platform.example.com/errors/validation-failed",
"title": "Validation Failed",
"status": 400,
"detail": "Request validation failed",
"instance": "/api/v1/services",
"errors": [
{
"field": "capabilities[0]",
"code": "INVALID_VALUE",
"message": "Unknown capability: 'mongodb'. Valid: postgresql, redis, kafka"
},
{
"field": "name",
"code": "PATTERN_MISMATCH",
"message": "Must be lowercase, hyphenated, 3-63 chars"
}
],
"requestId": "req-abc123"
}
RFC 7807 Problem Details: type (docs link), title, status, detail, instance, errors array with field-level codes.
Pagination & Filtering
GET /api/v1/services?team=payments&capability=postgresql&page=2&page_size=20&sort=-created_at
# Response
{
"data": [...],
"pagination": {
"page": 2,
"page_size": 20,
"total_pages": 5,
"total_items": 97,
"has_next": true,
"has_prev": true
},
"_links": {
"self": "/api/v1/services?team=payments&page=2&page_size=20",
"first": "/api/v1/services?team=payments&page=1&page_size=20",
"last": "/api/v1/services?team=payments&page=5&page_size=20",
"next": "/api/v1/services?team=payments&page=3&page_size=20",
"prev": "/api/v1/services?team=payments&page=1&page_size=20"
}
}
Cursor-based pagination for large datasets. Filter by indexed fields. Sort by created_at, updated_at, name.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.