Files

176 lines
5.3 KiB
YAML
Raw Permalink Normal View History

openapi: 3.0.3
info:
title: Sankofa HW Infra API
version: 0.1.0
servers:
- url: /api/v1
security:
- BearerAuth: []
components:
schemas:
ApiError:
type: object
properties:
error: { type: string, description: Human-readable message }
code: { type: string, enum: [BAD_REQUEST, UNAUTHORIZED, FORBIDDEN, NOT_FOUND, CONFLICT, INTERNAL_ERROR] }
details: { type: object, description: Optional validation or extra data }
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: JWT with optional vendorId for vendor users
IngestionApiKey:
type: apiKey
in: header
name: x-ingestion-api-key
description: Required for POST /ingestion/offers (env INGESTION_API_KEY)
paths:
/health:
get:
summary: Health
security: []
/auth/token:
post:
summary: Get JWT token
description: Exchange email (and optional password) for a JWT with roles and vendorId. No auth required.
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email: { type: string, format: email }
password: { type: string }
responses:
"200":
description: Token and user info
"401":
description: Invalid credentials
/vendors:
get:
summary: List vendors
description: If JWT contains vendorId (vendor user), returns only that vendor.
post:
summary: Create vendor
description: Forbidden for vendor users.
/vendors/{id}:
get:
summary: Get vendor
description: Vendor users may only request their own vendor id.
/offers:
get:
summary: List offers
description: If JWT contains vendorId, returns only that vendor's offers.
post:
summary: Create offer
description: Vendor users' vendorId is forced to their vendor.
/offers/{id}:
get:
summary: Get offer
patch:
summary: Update offer
delete:
summary: Delete offer
/purchase-orders:
get:
summary: List purchase orders
description: If JWT contains vendorId, returns only POs for that vendor.
/purchase-orders/{id}:
get:
summary: Get purchase order
/ingestion/offers:
post:
summary: Ingest offer (scrape or email)
description: Creates an offer with source (scraped|email), source_ref, source_metadata. Secured by x-ingestion-api-key only; no JWT. Use x-org-id for target org.
security:
- IngestionApiKey: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [source, quantity, unit_price]
properties:
source:
type: string
enum: [scraped, email]
source_ref:
type: string
description: URL or email message id
source_metadata:
type: object
vendor_id:
type: string
format: uuid
nullable: true
sku:
type: string
mpn:
type: string
quantity:
type: integer
unit_price:
type: string
incoterms:
type: string
lead_time_days:
type: integer
country_of_origin:
type: string
condition:
type: string
warranty:
type: string
evidence_refs:
type: array
items:
type: object
properties:
key: { type: string }
hash: { type: string }
responses:
"201":
description: Offer created
"401":
description: Invalid or missing x-ingestion-api-key
/capacity/sites/{siteId}:
get:
summary: RU utilization for a site
description: Returns usedRu, totalRu, utilizationPercent for the site (from racks and assigned positions).
parameters:
- name: siteId
in: path
required: true
schema: { type: string, format: uuid }
responses:
"200":
description: Site capacity (usedRu, totalRu, utilizationPercent)
"404":
description: Site not found
/capacity/sites/{siteId}/power:
get:
summary: Power headroom for a site
description: Returns circuitLimitWatts from rack power_feeds; measuredDrawWatts/headroomWatts null until Phase 4.
parameters:
- name: siteId
in: path
required: true
schema: { type: string, format: uuid }
responses:
"200":
description: Power info (circuitLimitWatts, measuredDrawWatts, headroomWatts)
"404":
description: Site not found
/capacity/gpu-inventory:
get:
summary: GPU inventory
description: Returns total, bySite, and byType (part number) counts.
responses:
"200":
description: GPU counts (total, bySite, byType)