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;