{ "openapi": "3.1.0", "info": { "title": "Procurement Ingest API", "version": "1.0.0", "license": { "name": "Proprietary (Sea Haven Industries, internal)" }, "description": "Read API over the procurement-ingest pipelines (work orders and purchase orders). The outbound SHOC work-order webhook feed is documented under **webhooks** below.\n\nData endpoints use AWS IAM SigV4; the `/docs` and `/openapi.json` routes use a shared token. Listings are unordered, cursor-paginated scans. Endpoints tagged **x-planned** are phase 2 and currently answer `501`.\n\nThis API replaces SHOC's retired SyncController DynamoDB scan as the reconciliation and backfill path. Full detail: the repo README and `docs/shoc-webhook-contract.md`." }, "servers": [ { "url": "https://procurement-api.seahaven.com", "description": "seahaven-prod (011934824531) custom domain — recommended." }, { "url": "https://mvul1efda2.execute-api.us-east-1.amazonaws.com/prod", "description": "seahaven-prod (011934824531)." } ], "security": [ { "sigv4": [] } ], "tags": [ { "name": "Work Orders", "description": "Work orders ingested from APM emails (WorkOrders and WorkOrderComments tables)." }, { "name": "Purchase Orders", "description": "Coupa purchase orders ingested from PO emails (purchase-orders table)." }, { "name": "Verified Sites", "description": "Amazon site directory maintained by the PO site extractor (verified-sites table)." }, { "name": "Docs", "description": "This documentation page and the raw OpenAPI document (token-gated)." }, { "name": "Outbound Webhooks", "description": "Events pushed by workorder-shoc-emitter to the configured SHOC endpoint." } ], "x-tagGroups": [ { "name": "Read API", "tags": ["Work Orders", "Purchase Orders", "Verified Sites"] }, { "name": "Meta", "tags": ["Docs"] }, { "name": "SHOC Feed", "tags": ["Outbound Webhooks"] } ], "paths": { "/work-orders": { "get": { "operationId": "get-work-orders", "tags": ["Work Orders"], "summary": "List work orders (unordered, paginated)", "parameters": [ { "$ref": "#/components/parameters/Limit" }, { "$ref": "#/components/parameters/Cursor" } ], "responses": { "200": { "description": "One page of work orders.", "content": { "application/json": { "schema": { "type": "object", "required": ["items", "next_cursor"], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/WorkOrder" } }, "next_cursor": { "$ref": "#/components/schemas/NextCursor" } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "403": { "$ref": "#/components/responses/Forbidden" } } } }, "/work-orders/{workOrderId}": { "get": { "operationId": "get-work-order", "tags": ["Work Orders"], "summary": "Get one work order", "parameters": [ { "$ref": "#/components/parameters/WorkOrderId" } ], "responses": { "200": { "description": "The work order.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WorkOrder" } } } }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" } } }, "patch": { "x-planned": true, "operationId": "patch-work-order", "tags": ["Work Orders"], "summary": "PLANNED (phase 2): update dispatch fields on a work order", "description": "Not implemented - returns 501. Phase-2 write-back for SHOC dispatch workflow (status/assignment). Writes will stamp `write_origin: shoc-write-api` so the outbound webhook never echoes SHOC's own writes back at it. Ships with its own IAM diff and cross-family review.", "parameters": [ { "$ref": "#/components/parameters/WorkOrderId" } ], "responses": { "403": { "$ref": "#/components/responses/Forbidden" }, "501": { "$ref": "#/components/responses/NotImplemented" } } } }, "/work-orders/{workOrderId}/comments": { "get": { "operationId": "get-work-order-comments", "tags": ["Work Orders"], "summary": "List comments/events for a work order (paginated)", "parameters": [ { "$ref": "#/components/parameters/WorkOrderId" }, { "$ref": "#/components/parameters/Limit" }, { "$ref": "#/components/parameters/Cursor" } ], "responses": { "200": { "description": "One page of comments (all source-email event records: comments, updates, cancellations).", "content": { "application/json": { "schema": { "type": "object", "required": ["items", "next_cursor"], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/WorkOrderComment" } }, "next_cursor": { "$ref": "#/components/schemas/NextCursor" } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "403": { "$ref": "#/components/responses/Forbidden" } } }, "post": { "x-planned": true, "operationId": "post-work-order-comment", "tags": ["Work Orders"], "summary": "PLANNED (phase 2): append a SHOC-authored comment", "description": "Not implemented - returns 501. Phase-2 write-back: SHOC dispatch notes land in WorkOrderComments with `write_origin: shoc-write-api` (append-only; no field conflicts with the email pipeline). Ships with its own IAM diff and cross-family review.", "parameters": [ { "$ref": "#/components/parameters/WorkOrderId" } ], "responses": { "403": { "$ref": "#/components/responses/Forbidden" }, "501": { "$ref": "#/components/responses/NotImplemented" } } } }, "/purchase-orders": { "get": { "operationId": "get-purchase-orders", "tags": ["Purchase Orders"], "summary": "List purchase orders (unordered, paginated)", "parameters": [ { "$ref": "#/components/parameters/Limit" }, { "$ref": "#/components/parameters/Cursor" } ], "responses": { "200": { "description": "One page of purchase orders.", "content": { "application/json": { "schema": { "type": "object", "required": ["items", "next_cursor"], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/PurchaseOrder" } }, "next_cursor": { "$ref": "#/components/schemas/NextCursor" } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "403": { "$ref": "#/components/responses/Forbidden" } } } }, "/purchase-orders/{poNumber}": { "get": { "operationId": "get-purchase-order", "tags": ["Purchase Orders"], "summary": "Get one purchase order", "parameters": [ { "name": "poNumber", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Coupa PO number, e.g. `2D-22030794`.", "example": "2D-22030794" } ], "responses": { "200": { "description": "The purchase order.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PurchaseOrder" } } } }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" } } } }, "/verified-sites": { "get": { "operationId": "get-verified-sites", "tags": ["Verified Sites"], "summary": "List verified Amazon sites (unordered, paginated)", "parameters": [ { "$ref": "#/components/parameters/Limit" }, { "$ref": "#/components/parameters/Cursor" } ], "responses": { "200": { "description": "One page of verified sites.", "content": { "application/json": { "schema": { "type": "object", "required": ["items", "next_cursor"], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/VerifiedSite" } }, "next_cursor": { "$ref": "#/components/schemas/NextCursor" } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "403": { "$ref": "#/components/responses/Forbidden" } } } }, "/verified-sites/{siteCode}": { "get": { "operationId": "get-verified-site", "tags": ["Verified Sites"], "summary": "Get one verified site", "parameters": [ { "name": "siteCode", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Amazon site code, e.g. `JFK8`.", "example": "JFK8" } ], "responses": { "200": { "description": "The verified site.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VerifiedSite" } } } }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" } } } }, "/docs": { "get": { "operationId": "get-docs", "tags": ["Docs"], "summary": "This documentation page (token-gated)", "security": [ { "docsToken": [] } ], "parameters": [ { "name": "token", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Browser convenience: the docs token as a query parameter (browsers can't set headers on navigation). Prefer the X-Auth-Token header from tooling.", "example": "your-docs-token" } ], "responses": { "200": { "description": "Self-contained HTML reference page.", "content": { "text/html": {} } }, "401": { "$ref": "#/components/responses/Unauthorized" } } } }, "/openapi.json": { "get": { "operationId": "get-openapi-spec", "tags": ["Docs"], "summary": "This spec (token-gated)", "security": [ { "docsToken": [] } ], "responses": { "200": { "description": "The OpenAPI 3.1 document.", "content": { "application/json": {} } }, "401": { "$ref": "#/components/responses/Unauthorized" } } } } }, "webhooks": { "work_order.created": { "post": { "operationId": "post-work-order-created", "tags": ["Outbound Webhooks"], "summary": "Outbound: a work order was created", "description": "Sent by `workorder-shoc-emitter` (seahaven-prod) to the configured SHOC endpoint. Full contract incl. HMAC verification, ordering, and retry semantics: `docs/shoc-webhook-contract.md` (Rev 2026-07-23). Requests carry `X-SH-Timestamp`, `X-SH-Key-Id`, and `X-SH-Signature: v1=hex(HMAC_SHA256(secret, \"{timestamp}.{raw_body}\"))`; verify over the raw body, constant-time, +/-300s window, fail closed.", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WorkOrderEventEnvelope" } } } }, "responses": { "2XX": { "description": "Delivered. Respond fast (<10s); accept-and-enqueue if processing is slow. 429/5xx/timeouts are retried in order for 24h; other 4xx park immediately." } } } }, "work_order.updated": { "post": { "operationId": "post-work-order-updated", "tags": ["Outbound Webhooks"], "summary": "Outbound: a work order changed", "description": "Same envelope and semantics as work_order.created; `data` is the full current state, not a diff.", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WorkOrderEventEnvelope" } } } }, "responses": { "2XX": { "description": "Delivered." } } } }, "work_order.cancelled": { "post": { "operationId": "post-work-order-cancelled", "tags": ["Outbound Webhooks"], "summary": "Outbound: a work order transitioned to cancelled", "description": "A specialization of work_order.updated (same body) emitted when `wo_status` transitions to `cancelled`.", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WorkOrderEventEnvelope" } } } }, "responses": { "2XX": { "description": "Delivered." } } } }, "work_order.comment_added": { "post": { "operationId": "post-work-order-comment-added", "tags": ["Outbound Webhooks"], "summary": "Outbound: a comment/event record was ingested", "description": "One per source email (comments, updates, and cancellation event records). Dedupe on `delivery_id` or `data.comment_id`. May occasionally arrive before the work order's created event - upsert a skeleton work order and let the state event backfill it.", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CommentEventEnvelope" } } } }, "responses": { "2XX": { "description": "Delivered." } } } } }, "components": { "securitySchemes": { "sigv4": { "type": "apiKey", "name": "Authorization", "in": "header", "x-amazon-apigateway-authtype": "awsSigv4", "description": "AWS IAM SigV4 (service execute-api, region us-east-1). Cross-account callers must be allowed by the API resource policy; Postman signs natively via Authorization type 'AWS Signature'." }, "docsToken": { "type": "apiKey", "name": "X-Auth-Token", "in": "header", "description": "Shared docs token (secret procurement-ingest/web-ui-auth-token). Docs routes only." } }, "parameters": { "Limit": { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 500, "default": 100 }, "description": "Page size; values outside 1-500 are clamped.", "example": 100 }, "Cursor": { "name": "cursor", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Opaque pagination cursor from the previous page's `next_cursor`. Malformed cursors return 400.", "example": "eyJ3b3JrX29yZGVyX2lkIjogIjExMTQ0NTgwNzMwIn0" }, "WorkOrderId": { "name": "workOrderId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[0-9]+$" }, "description": "Numeric APM work-order id, e.g. `11144580730`.", "example": "11144580730" } }, "responses": { "BadRequest": { "description": "Malformed cursor or limit.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "Unauthorized": { "description": "Missing or wrong docs token.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "Forbidden": { "description": "SigV4 auth failed or the caller is not allowed by the API resource policy (returned by API Gateway, not the Lambda)." }, "NotFound": { "description": "No record with that id.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "NotImplemented": { "description": "Planned phase-2 endpoint; not implemented yet.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } }, "schemas": { "Error": { "type": "object", "required": ["error"], "properties": { "error": { "type": "string" } } }, "NextCursor": { "type": ["string", "null"], "description": "Pass as `cursor` to fetch the next page; `null` means this is the last page." }, "WorkOrder": { "type": "object", "description": "Mirrors the WorkOrders DynamoDB item (PK work_order_id). Fields absent from the source email are absent or null.", "required": ["work_order_id"], "properties": { "work_order_id": { "type": "string", "pattern": "^[0-9]+$" }, "wo_status": { "type": ["string", "null"], "enum": ["new", "assigned", "in_progress", "on_hold", "completed", "cancelled", "unknown", null], "description": "The `unknown` value is genuinely emitted - map it explicitly." }, "description": { "type": ["string", "null"] }, "customer": { "type": ["string", "null"] }, "site_code": { "type": ["string", "null"] }, "building": { "type": ["string", "null"] }, "address": { "type": ["string", "null"] }, "severity": { "type": ["string", "null"] }, "priority": { "type": ["string", "null"] }, "assigned_to": { "type": ["string", "null"] }, "date_reported": { "type": ["string", "null"] }, "scheduled_start": { "type": ["string", "null"] }, "due_date": { "type": ["string", "null"] }, "record_type": { "type": ["string", "null"], "enum": ["new_work_order", "update", "comment", "cancellation", null], "description": "Type of the most recent source email." }, "created_at": { "type": ["string", "null"], "description": "ISO 8601 with UTC offset." }, "updated_at": { "type": ["string", "null"], "description": "ISO 8601 with UTC offset." }, "source_email_s3_key": { "type": ["string", "null"] } }, "additionalProperties": true }, "WorkOrderComment": { "type": "object", "description": "Mirrors the WorkOrderComments DynamoDB item (PK work_order_id, SK comment_id). One row per source email event.", "required": ["work_order_id", "comment_id"], "properties": { "work_order_id": { "type": "string" }, "comment_id": { "type": "string", "description": "Format: `{work_order_id}#{time|nocomment}#{sha256(s3_key)[:12]}` - unique per source email, stable across retries; a dedupe key." }, "record_type": { "type": ["string", "null"], "enum": ["new_work_order", "update", "comment", "cancellation", null] }, "commenter": { "type": ["string", "null"] }, "text": { "type": ["string", "null"] }, "created_at": { "type": ["string", "null"], "description": "Display timestamp (comment time when the source email carried one)." }, "ingested_at": { "type": ["string", "null"], "description": "When the pipeline wrote the row; ISO 8601 with UTC offset." }, "source_email_s3_key": { "type": ["string", "null"] } }, "additionalProperties": true }, "PurchaseOrder": { "type": "object", "description": "Mirrors the purchase-orders DynamoDB item (PK po_number). Shape follows the Coupa extraction schema; older records may lack newer fields.", "required": ["po_number"], "properties": { "po_number": { "type": "string" }, "po_status": { "type": ["string", "null"] }, "email_type": { "type": ["string", "null"] }, "source_system": { "type": ["string", "null"] }, "submitted_by": { "type": ["string", "null"] }, "on_behalf_of": { "type": ["string", "null"] }, "order_date": { "type": ["string", "null"] }, "revision_date": { "type": ["string", "null"] }, "payment_terms": { "type": ["string", "null"] }, "requisition_number": { "type": ["string", "null"] }, "department": { "type": ["string", "null"] }, "view_order_url": { "type": ["string", "null"] }, "supplier": { "type": ["object", "null"], "properties": { "name": { "type": ["string", "null"] } }, "additionalProperties": true }, "site_code": { "type": ["string", "null"] }, "ship_to": { "type": ["object", "null"], "properties": { "name": { "type": ["string", "null"] }, "address": { "type": ["string", "null"] }, "street": { "type": ["string", "null"] }, "city": { "type": ["string", "null"] }, "state": { "type": ["string", "null"] }, "zip": { "type": ["string", "null"] }, "location_code": { "type": ["string", "null"] }, "attn": { "type": ["string", "null"] } }, "additionalProperties": true }, "total_amount": { "type": ["number", "null"], "description": "Stored as Decimal; serialized as a JSON number." }, "currency": { "type": ["string", "null"] }, "fiscal_year": { "type": ["string", "null"] }, "trade": { "type": ["string", "null"] }, "coupa_category": { "type": ["string", "null"] }, "line_items": { "type": ["array", "null"], "items": { "type": "object", "properties": { "description": { "type": ["string", "null"] }, "quantity": { "type": ["number", "null"] }, "unit": { "type": ["string", "null"] }, "price": { "type": ["number", "null"] }, "amount": { "type": ["number", "null"] }, "currency": { "type": ["string", "null"] }, "need_by": { "type": ["string", "null"] }, "category": { "type": ["string", "null"] }, "account_code": { "type": ["string", "null"] }, "period": { "type": ["string", "null"] } }, "additionalProperties": true } }, "cancelled_at": { "type": ["string", "null"] }, "processed_at": { "type": ["string", "null"] }, "raw_s3_key": { "type": ["string", "null"] }, "data_source": { "type": ["string", "null"] } }, "additionalProperties": true }, "VerifiedSite": { "type": "object", "description": "Mirrors the verified-sites DynamoDB item (PK siteCode), maintained by the PO site extractor.", "required": ["siteCode"], "properties": { "siteCode": { "type": "string" }, "address": { "type": ["string", "null"] }, "city": { "type": ["string", "null"] }, "state": { "type": ["string", "null"] }, "zip": { "type": ["string", "null"] }, "fullAddress": { "type": ["string", "null"] }, "locationCode": { "type": ["string", "null"] }, "poCount": { "type": ["integer", "null"], "description": "Count of POs that referenced this site." }, "sourcePOs": { "type": ["array", "null"], "items": { "type": "string" }, "description": "PO numbers that referenced this site (string set; serialized sorted)." } }, "additionalProperties": true }, "WebhookEnvelope": { "type": "object", "description": "Common webhook envelope (contract section 3). Headers: X-SH-Timestamp (unix seconds), X-SH-Key-Id, X-SH-Signature (v1=hex HMAC-SHA256 over '{timestamp}.{raw_body}').", "required": ["schema_version", "delivery_id", "event_type", "occurred_at", "source", "replay", "data"], "properties": { "schema_version": { "type": "integer", "description": "Reject versions you don't know." }, "delivery_id": { "type": "string", "description": "Unique per source event, stable across producer retries - the idempotency key." }, "event_type": { "type": "string", "enum": ["work_order.created", "work_order.updated", "work_order.cancelled", "work_order.comment_added"] }, "occurred_at": { "type": "string", "description": "ISO 8601 with UTC offset." }, "source": { "type": "string", "const": "procurement-ingest/workorder-shoc-emitter" }, "replay": { "type": "boolean", "description": "true when re-sent by the operator replay tool; same idempotency rules." }, "data": { "type": "object" } } }, "WorkOrderEventEnvelope": { "allOf": [ { "$ref": "#/components/schemas/WebhookEnvelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/WorkOrderEventData" } } } ] }, "CommentEventEnvelope": { "allOf": [ { "$ref": "#/components/schemas/WebhookEnvelope" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/CommentEventData" } } } ] }, "WorkOrderEventData": { "type": "object", "description": "Full current work-order state (not a diff); contract section 4.1. Same fields as WorkOrder minus source_email_s3_key.", "required": ["work_order_id"], "properties": { "work_order_id": { "type": "string", "pattern": "^[0-9]+$" }, "wo_status": { "type": ["string", "null"], "enum": ["new", "assigned", "in_progress", "on_hold", "completed", "cancelled", "unknown", null] }, "description": { "type": ["string", "null"] }, "customer": { "type": ["string", "null"] }, "site_code": { "type": ["string", "null"] }, "building": { "type": ["string", "null"] }, "address": { "type": ["string", "null"] }, "severity": { "type": ["string", "null"] }, "priority": { "type": ["string", "null"] }, "assigned_to": { "type": ["string", "null"] }, "date_reported": { "type": ["string", "null"] }, "scheduled_start": { "type": ["string", "null"] }, "due_date": { "type": ["string", "null"] }, "record_type": { "type": ["string", "null"], "enum": ["new_work_order", "update", "comment", "cancellation", null] }, "created_at": { "type": ["string", "null"] }, "updated_at": { "type": ["string", "null"] }, "write_origin": { "type": ["string", "null"], "description": "Forward-compat (phase 2): present on records written through the write-back API; the emitter skips those, so receivers should tolerate but never see it." } } }, "CommentEventData": { "type": "object", "description": "Contract section 4.2. Same fields as WorkOrderComment minus source_email_s3_key.", "required": ["work_order_id", "comment_id"], "properties": { "work_order_id": { "type": "string" }, "comment_id": { "type": "string" }, "record_type": { "type": ["string", "null"] }, "commenter": { "type": ["string", "null"] }, "text": { "type": ["string", "null"] }, "created_at": { "type": ["string", "null"] }, "ingested_at": { "type": ["string", "null"] } } } } } }