API Contract
This document captures the runtime contract for the HTTP and gRPC surfaces: versioning, envelopes, error codes, rate limits, pagination, and idempotency expectations.Base URLs & Versioning
- HTTP base path:
/v1(example:http://localhost:8080/v1/tenants/{tenant_id}/orders). - OpenAPI:
/api-docs/openapi.jsonand interactive docs at/docs/(alias/swagger-ui/). - Version headers: responses include
x-api-version(current1.0.0) andx-api-supported-versions(minimum supported version). Requests may optionally sendx-api-version; unsupported versions return400. - Deprecation:
deprecation/sunsetheaders are wired in the middleware for future sunset notices (none active now).
Authentication
- HTTP (tenant):
x-stateset-api-key: <tenant-key>per request. - HTTP (operator / dashboard):
x-stateset-admin-key: <platform-admin-key>is restricted to platform-admin/v1/admin/*calls and org-bound operator-safe tenant reads inside the WorkOS-authenticated dashboard proxy. - gRPC: same key via metadata header
x-stateset-api-keyon port50051.
Webhook Authentication
- Shopify webhook ingress (
/v1/tenants/{tenant_id}/webhooks/shopify) usesx-shopify-hmac-sha256. - Amazon SNS webhook ingress (
/v1/tenants/{tenant_id}/webhooks/amazon) uses SNS RSA signature verification. - TikTok webhook ingress (
/v1/tenants/{tenant_id}/webhooks/tiktok) uses HMAC SHA-256 signature verification fromx-tts-signature/x-tiktok-signature. - Walmart webhook ingress (
/v1/tenants/{tenant_id}/webhooks/walmart) uses HMAC SHA-256 signature verification fromx-walmart-signature. - eBay webhook ingress (
/v1/tenants/{tenant_id}/webhooks/ebay) uses HMAC SHA-256 signature verification fromx-ebay-signature. - Etsy webhook ingress (
/v1/tenants/{tenant_id}/webhooks/etsy) uses HMAC SHA-256 signature verification fromx-etsy-signature. - WooCommerce webhook ingress (
/v1/tenants/{tenant_id}/webhooks/woocommerce) uses HMAC SHA-256 signature verification fromx-wc-webhook-signature. - ACP webhook ingress (
/v1/tenants/{tenant_id}/webhooks/acp/orders) uses HMAC SHA-256 with:x-acp-timestamp: Unix seconds/milliseconds or RFC3339 timestampx-acp-signature: HMAC of"{timestamp}.{raw_body}"(hex or base64;v1=/sha256=prefixes supported)
- Shopify secret is read from
{TENANT_ID}_SHOPIFY_WEBHOOK_SECRET(for tenantacme:ACME_SHOPIFY_WEBHOOK_SECRET). - TikTok secret is read from
{TENANT_ID}_TIKTOK_WEBHOOK_SECRET(for tenantacme:ACME_TIKTOK_WEBHOOK_SECRET). - Walmart secret is read from
{TENANT_ID}_WALMART_WEBHOOK_SECRET. - eBay secret is read from
{TENANT_ID}_EBAY_WEBHOOK_SECRET. - Etsy secret is read from
{TENANT_ID}_ETSY_WEBHOOK_SECRET. - WooCommerce secret is read from
{TENANT_ID}_WOOCOMMERCE_WEBHOOK_SECRET. - ACP secret is read from
{TENANT_ID}_ACP_WEBHOOK_SECRET(for tenantacme:ACME_ACP_WEBHOOK_SECRET). - Unsigned ACP webhooks are rejected unless
STATESET_ALLOW_UNSIGNED_ACP_WEBHOOKS=true.
Response Envelopes & Correlation
- Success shape:
- Error shape:
- Every response also carries
x-request-id; it matchesmeta.requestIdand can be used for support tickets and trace correlation.
Error Codes
Rate Limiting
- Keyed by client IP via
tower_governor. Bucket: burst500, refill one token every100ms(~10 requests/second sustained). - On
429responses:- Headers:
x-ratelimit-limit,x-ratelimit-remaining,x-ratelimit-after,Retry-After(seconds),x-request-id. - Body: standard error envelope with
code=rate_limit_exceededanddetails.retryAfterSeconds.
- Headers:
- Per-tenant concurrency is additionally shaped by semaphores for each integration to keep within vendor quotas.
Idempotency & Retries
POST /v1/tenants/{tenant_id}/ordersis idempotent onshopify_order_id; if the order already exists in StateSet the existingstateset_order_idis returned without creating a duplicate.POST /v1/tenants/{tenant_id}/orders/batchaccepts up to200orders and returns per-item success/failure details; setstopOnError=trueto fail-fast (otherwise it continues through all entries).- Batch imports support preview mode with
dryRun=true(request body) or?dry_run=true(query param). In dry-run mode the API validates each item, reportsaction(would_create/would_reuse_existing/failed), and performs zero writes. POST /v1/tenants/{tenant_id}/orders/backfill/shopify/{shopify_order_id}is an operator repair endpoint: it fetches the order snapshot from Shopify and persists it locally when missing. It returns201when it inserted a new row and200when the order already existed locally.- All write endpoints (
POST/PUT/PATCH/DELETE) also accept anidempotency-keyheader. Successful responses are cached for 24h and replayed on retry withidempotency-replayed: true; if a matching request is still running, the server returns409 operation_in_progress. When PostgreSQL is enabled, idempotency state is shared across replicas; otherwise it falls back to process-local memory. - Sync triggers (
/sync/*) enqueue background jobs; use/v1/tenants/{tenant_id}/jobs/{job_id}to poll instead of holding the original request open. Triggering twice will create two jobs, so prefer client-side de-duplication when retrying. GET /v1/tenants/{tenant_id}/jobs/throughputreturns time-bucketed job analytics (created/finished/succeeded/failed) for dashboard charts. Query params:hours(default24) andbucket_minutes(default60).- Downstream calls use exponential backoff (see
src/resilience.rs) and classify retryable upstream errors (timeouts, 5xx, 429) automatically. - Webhook operator tooling supports both single replay (
POST /v1/tenants/{tenant_id}/webhook-events/{webhook_id}/replay) and bulk replay (POST /v1/tenants/{tenant_id}/webhook-events/replay?state=failed&limit=50). - Bulk replay supports additional safety controls:
- exact topic filtering via
topic - preview mode via
dry_run=true(returns selected ids without queueing) - source-level throttling via
max_per_source=<n>
- exact topic filtering via
- Targeted replay is available via
POST /v1/tenants/{tenant_id}/webhook-events/replay-by-idwith JSON body (webhookIds, optionaldryRun).
Auditability
- Bulk and operator repair actions emit
data_accessaudit events (resource types:orders_batch_import,orders_batch_import_preview,orders_backfill_shopify,webhook_events_bulk_replay,webhook_event_replay). - Audit entries are best-effort persisted to the database (when configured) and always logged to structured application logs.
x-stateset-user-emailanduser-agentheaders are captured when present for operator attribution.- Admin audit queries support
resource_typefiltering:GET /v1/admin/audit-logs?event_type=data_access&resource_type=orders_batch_import.
Admin Onboarding Readiness
GET /v1/admin/tenants/{tenant_id}/readinessreturns a scored onboarding report for customer go-live checks.GET /v1/admin/tenants/{tenant_id}/onboarding-planreturns prioritized action items derived from the readiness report.PATCH /v1/admin/tenants/{tenant_id}/onboarding-plan/actions/{action_id}persists operator workflow state for each action (pending,in_progress,done,blocked) with optional notes.PATCH /v1/admin/tenants/{tenant_id}/onboarding-plan/actionssupports bulk workflow updates for multiple actions in a single request.GET /v1/admin/tenants/{tenant_id}/onboarding-plan/actions/historyreturns append-only action history with actor/timestamp context.PATCH /v1/admin/tenants/{tenant_id}/onboarding-profileupserts tenant onboarding metadata (ownerEmail,ownerName,goLiveTargetAt,notes).GET /v1/admin/onboarding/portfolioreturns a cross-tenant onboarding pipeline summary (scores, risk, blockers, workflow counts, next action, owner/go-live metadata). Supports optional filters viareadinessStatus,integration,hasBlockers,minRiskScore,minBlockedActionAgeHours,ownerAssigned, andoverdueGoLive; sorting viasortBy(risk|score|tenant_id) +sortOrder(asc|desc); includes summary counters/thresholds forhighRiskTenants,staleBlockerTenants,unassignedOwnerTenants, andoverdueGoLiveTenants; useincludeIntegrationTests=trueto run live health checks in the aggregation response.- The response includes:
score(0-100) andgrade(A+..F)overallStatus(ready,attention_required,not_ready)- required check results (tenant runtime, integration config, channel/destination coverage, auth readiness, Shopify webhook secret, connectivity)
- per-integration connectivity test results
- guide readiness for Shopify -> NetSuite and Shopify -> DCL with missing requirements
Pagination & Filtering
GET /v1/tenants/{tenant_id}/orderssupportspage(default1),page_size(default50, max100),status,created_after,created_before,order_number,shopify_order_id,tags.- Response uses
PaginatedResponse<T>:itemspluspaginationmetadata (page,pageSize,totalItems,totalPages,hasNext,hasPrevious).
Examples
- Create order (idempotent on Shopify order id):
- Create orders in batch (up to 200 per request):
- Error response (validation):
422with body shown above andx-request-idfor correlation.