API Design
10.1 REST API Endpoints
openapi: 3.0.0
info:
title: Cloud Compute Arbitrage API
version: 1.0.0
description: API for managing spot instance workloads across multiple cloud providers
servers:
- url: https://api.computearbitrage.com/v1
security:
- bearerAuth: []
paths:
# Workloads
/workloads:
post:
summary: Create a new workload
operationId: createWorkload
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateWorkloadRequest'
responses:
'202':
description: Workload creation initiated
content:
application/json:
schema:
$ref: '#/components/schemas/WorkloadResponse'
get:
summary: List all workloads
operationId: listWorkloads
parameters:
- name: status
in: query
schema:
type: string
enum: [PENDING, PROVISIONING, RUNNING, STOPPING, TERMINATED, FAILED]
- name: limit
in: query
schema:
type: integer
default: 50
- name: cursor
in: query
schema:
type: string
responses:
'200':
description: List of workloads
content:
application/json:
schema:
$ref: '#/components/schemas/WorkloadListResponse'
/workloads/{workloadId}:
get:
summary: Get workload details
operationId: getWorkload
parameters:
- name: workloadId
in: path
required: true
schema:
type: string
responses:
'200':
description: Workload details
content:
application/json:
schema:
$ref: '#/components/schemas/WorkloadDetailResponse'
delete:
summary: Terminate a workload
operationId: terminateWorkload
parameters:
- name: workloadId
in: path
required: true
schema:
type: string
responses:
'202':
description: Termination initiated
/workloads/{workloadId}/endpoint:
get:
summary: Get workload connection endpoint
operationId: getWorkloadEndpoint
parameters:
- name: workloadId
in: path
required: true
schema:
type: string
responses:
'200':
description: Connection endpoint details
content:
application/json:
schema:
type: object
properties:
http:
type: string
example: "wk-abc123.workloads.computearbitrage.com"
tcp:
type: string
example: "tcp-wk-abc123.workloads.computearbitrage.com"
status:
type: string
enum: [HEALTHY, UNHEALTHY, UNKNOWN]
# Pricing
/pricing/estimate:
post:
summary: Get price estimate for workload requirements
operationId: estimatePrice
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/WorkloadRequirements'
responses:
'200':
description: Price estimate
content:
application/json:
schema:
$ref: '#/components/schemas/PriceEstimate'
/pricing/current:
get:
summary: Get current spot prices
operationId: getCurrentPrices
parameters:
- name: provider
in: query
schema:
type: string
enum: [aws, azure, gcp]
- name: region
in: query
schema:
type: string
- name: instanceType
in: query
schema:
type: string
responses:
'200':
description: Current prices
content:
application/json:
schema:
$ref: '#/components/schemas/SpotPriceList'
# Tokens
/tokens:
post:
summary: Create API token
operationId: createToken
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTokenRequest'
responses:
'201':
description: Token created
content:
application/json:
schema:
$ref: '#/components/schemas/TokenResponse'
get:
summary: List API tokens
operationId: listTokens
responses:
'200':
description: List of tokens
content:
application/json:
schema:
$ref: '#/components/schemas/TokenListResponse'
/tokens/{tokenId}:
delete:
summary: Revoke API token
operationId: revokeToken
parameters:
- name: tokenId
in: path
required: true
schema:
type: string
responses:
'204':
description: Token revoked
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
CreateWorkloadRequest:
type: object
required:
- name
- requirements
- config
properties:
name:
type: string
example: "monte-carlo-simulation-batch-1"
requirements:
$ref: '#/components/schemas/WorkloadRequirements'
config:
$ref: '#/components/schemas/WorkloadConfig'
metadata:
type: object
additionalProperties:
type: string
WorkloadRequirements:
type: object
required:
- minCPU
- minMemoryGB
properties:
minCPU:
type: integer
minimum: 1
example: 8
minMemoryGB:
type: number
minimum: 1
example: 32
gpuCount:
type: integer
minimum: 0
example: 1
gpuType:
type: string
enum: [nvidia-t4, nvidia-a100, nvidia-v100]
preferredRegions:
type: array
items:
type: string
example: ["us-east-1", "eu-west-1"]
maxPricePerHour:
type: number
example: 0.50
maxInterruptionRate:
type: number
minimum: 0
maximum: 1
example: 0.1
WorkloadConfig:
type: object
required:
- imageId
properties:
imageId:
type: string
example: "ami-0123456789abcdef0"
healthCheckPort:
type: integer
default: 8080
healthCheckPath:
type: string
default: "/health"
environmentVariables:
type: object
additionalProperties:
type: string
startupScript:
type: string
WorkloadResponse:
type: object
properties:
id:
type: string
example: "wk-abc123def456"
name:
type: string
status:
type: string
enum: [PENDING, PROVISIONING, RUNNING, STOPPING, TERMINATED, FAILED]
endpoint:
type: string
example: "wk-abc123def456.workloads.computearbitrage.com"
createdAt:
type: string
format: date-time
PriceEstimate:
type: object
properties:
recommended:
$ref: '#/components/schemas/PlacementOption'
alternatives:
type: array
items:
$ref: '#/components/schemas/PlacementOption'
PlacementOption:
type: object
properties:
provider:
type: string
enum: [aws, azure, gcp]
region:
type: string
instanceType:
type: string
pricePerHour:
type: number
interruptionRate:
type: number
availabilityScore:
type: number
10.2 API Implementation
// api/src/routes/workloads.ts
import { Hono } from 'hono';
import { authMiddleware, requirePermission } from '../middleware/auth';
const app = new Hono<{ Bindings: Env }>();
app.use('*', authMiddleware);
app.post('/', requirePermission('workloads:create'), async (c) => {
const body = await c.req.json<CreateWorkloadRequest>();
const customerId = c.get('customerId');
// Validate request
const validation = validateWorkloadRequest(body);
if (!validation.valid) {
return c.json({ error: validation.errors }, 400);
}
// Generate workload ID
const workloadId = `wk-${generateId()}`;
// Create workload record
await c.env.DB.prepare(`
INSERT INTO workloads (id, customer_id, name, status, requirements, config, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).bind(
workloadId,
customerId,
body.name,
'PENDING',
JSON.stringify(body.requirements),
JSON.stringify(body.config),
Date.now()
).run();
// Start provisioning workflow
const workflow = await c.env.PROVISION_WORKFLOW.create({
id: workloadId,
params: {
customerId,
workloadId,
requirements: body.requirements,
config: body.config,
},
});
// Audit log
await c.env.EVENTS_QUEUE.send({
type: 'WORKLOAD_CREATED',
customerId,
workloadId,
timestamp: Date.now(),
});
return c.json({
id: workloadId,
name: body.name,
status: 'PENDING',
endpoint: `${workloadId}.workloads.computearbitrage.com`,
createdAt: new Date().toISOString(),
}, 202);
});
app.get('/:workloadId', requirePermission('workloads:read'), async (c) => {
const workloadId = c.req.param('workloadId');
const customerId = c.get('customerId');
const workload = await c.env.DB.prepare(`
SELECT w.*, wr.endpoint, wr.provider, wr.region, wi.instance_type, wi.spot_price
FROM workloads w
LEFT JOIN workload_routing wr ON w.id = wr.workload_id
LEFT JOIN workload_instances wi ON w.id = wi.workload_id AND wi.status = 'RUNNING'
WHERE w.id = ? AND w.customer_id = ?
`).bind(workloadId, customerId).first();
if (!workload) {
return c.json({ error: 'Workload not found' }, 404);
}
return c.json({
id: workload.id,
name: workload.name,
status: workload.status,
endpoint: workload.endpoint ? `${workload.id}.workloads.computearbitrage.com` : null,
requirements: JSON.parse(workload.requirements),
config: JSON.parse(workload.config),
placement: workload.provider ? {
provider: workload.provider,
region: workload.region,
instanceType: workload.instance_type,
pricePerHour: workload.spot_price,
} : null,
createdAt: new Date(workload.created_at).toISOString(),
startedAt: workload.started_at ? new Date(workload.started_at).toISOString() : null,
terminatedAt: workload.terminated_at ? new Date(workload.terminated_at).toISOString() : null,
});
});
app.delete('/:workloadId', requirePermission('workloads:delete'), async (c) => {
const workloadId = c.req.param('workloadId');
const customerId = c.get('customerId');
// Verify ownership
const workload = await c.env.DB.prepare(
'SELECT * FROM workloads WHERE id = ? AND customer_id = ?'
).bind(workloadId, customerId).first();
if (!workload) {
return c.json({ error: 'Workload not found' }, 404);
}
if (workload.status === 'TERMINATED') {
return c.json({ error: 'Workload already terminated' }, 400);
}
// Start termination workflow
await c.env.TERMINATE_WORKFLOW.create({
id: `terminate-${workloadId}`,
params: {
workloadId,
reason: 'USER_REQUESTED',
},
});
return c.json({ message: 'Termination initiated' }, 202);
});
export default app;