Aller au contenu principal

API Reference

The complete REST surface your plugin uses to read and write restaurant data. For worked examples, see Plugin Data API.

Base URL​

https://api.ssppos.com/api/plugin/v1

Sandbox and production share this URL. Your pik_ key determines which data plane you reach — no environment branch needed in your code.

Authentication​

X-Plugin-Api-Key: pik_a3f200001234567890abcdef...

Inbound webhook forwarding additionally requires an HMAC signature:

X-Plugin-Api-Key: pik_...
X-SSP-Signature: <hex HMAC-SHA256 of the raw body>

Full detail: Authentication.

Rate limits​

BucketLimitScope
Plugin Data API — every plugin/v1 route60 requests/minutePer installation
Inbound webhooks — additional ceiling600 requests/minutePer installation
Developer API (ssp_dev_ on /graphql)60 requests/minutePer key
Setup-token exchange20 requests/minutePer IP
Sandbox operator JWT30 requests/minutePer IP

A 429 carries Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers, plus a retry_after field in the body.

The 60/min limit applies to inbound webhooks too

The two buckets are not independent. The 60/min installation limit is enforced on every plugin/v1 route, including POST /webhooks/external; the 600/min figure is a second, higher ceiling checked after it. In practice:

  • 60/min is your real inbound webhook ceiling, not 600.
  • Forwarded webhooks consume your data-API budget. A burst of provider callbacks eats into the same 60 requests you need for reads and writes.

Queue and pace your forwarding rather than relaying provider callbacks one-for-one, and honour Retry-After on the 429.


Endpoints​

22 endpoints across eight groups.

Health & installation​

MethodPathSummary
GET/healthReturns {"status":"healthy","installation_id":…,"timestamp":…} and records a health check. Note: this is the endpoint you call on SSP — not the /health SSP polls on your service
GET/installationYour installation: plugin, organization, location, config, enabled_features
POST/installation/configMerge keys into your installation's config (unsent keys are preserved)

Orders​

MethodPathSummary
GET/ordersList orders — filters: location_id, status, since, limit (max 100, default 50)
GET/orders/{orderId}Full order: items, customer, payments
POST/ordersCreate an order. Requires orders:create
PUT/orders/{orderId}Transition status. Accepts a numeric ID or a unique_orderid

Payments​

MethodPathSummary
POST/orders/{orderId}/paymentsRecord a capture. Requires payments:capture
MethodPathSummary
GET/menuList items — filters: location_id, category_id, available_only (defaults to true)
GET/menu/{itemId}Item detail
PATCH/menu/{itemId}Update is_available, price (0–9999.99), preparation_time (0–480 min)
POST/menu/bulk-availabilityBulk availability update, max 100 items, partial success

Locations​

MethodPathSummary
GET/locationsAll locations visible to this installation
GET/locations/{locationId}Location detail

Customers​

MethodPathSummary
GET/customersList — filters: location_id, search (name/email/phone), since, limit
GET/customers/{id}Detail, with visible order count and recent order IDs

Inventory​

MethodPathSummary
GET/inventoryIngredients with stock — filters: location_id, ingredient_id, low_stock, limit
GET/inventory/{ingredientId}Detail with per-location stock, SKUs, last 20 movements
PATCH/inventory/{ingredientId}Update metadata (name, sku, unit, cost_per_unit, supplier, allergens, metadata)
POST/inventory/{ingredientId}/movementsAppend a stock movement

Inbound webhooks​

MethodPathSummary
POST/webhooks/externalForward a provider event to SSP. Requires X-SSP-Signature
GET/webhooks/external/{logId}Poll the status of a submitted event

Response shapes​

Success​

Collections:

{
"data": [ … ],
"meta": { "count": 5, "limit": 50 }
}

Single resources return {"data": { … }} — including GET /orders/{id} and GET /installation. The one exception is GET /health, which returns its object at the top level.

Error​

{
"error": "validation_error",
"message": "Human-readable description",
"details": { "field": ["what was wrong"] }
}

error is a machine-readable snake_case code — branch on it, not on the message. details appears only on validation failures.

Order, menu-item and location 404s break that rule

Those endpoints return a human sentence in the error field and no message key at all:

{ "error": "Order not found" }

So error === 'not_found' is false for GET /orders/{id}, GET /menu/{itemId} and GET /locations/{locationId}. Treat any 404 as not-found regardless of body, and only parse error as a code on other statuses. Customers and inventory do return not_found properly.


Error codes​

Authentication and access​

CodeHTTPMeaning
missing_api_key401No X-Plugin-Api-Key header
invalid_api_key_format401Not pik_ + 64 hex (68 chars)
invalid_api_key401No installation matches
installation_inactive403Installation status isn't active
plugin_listing_unavailable403Production only — listing isn't approved/active
sandbox_not_ready401Sandbox isn't active
sandbox_resetting409Reset in flight — retry shortly
sandbox_misconfigured500Sandbox schema problem — contact support
rate_limit_exceeded429Over the bucket limit

Requests​

CodeHTTPMeaning
validation_error400 / 422Body failed validation (details carries the field errors)
not_found404Not present, or not visible to this installation. Customers and inventory only — see the note below
feature_not_enabled403Missing capability (e.g. orders:create)
location_forbidden403Location outside this installation's scope

Orders​

CodeHTTPMeaning
invalid_transition422Target unreachable from the current state — response includes allowed_next_states
customization_location_mismatch422Customization template belongs to a different location
customization_rule_violation422Violates the item's customization rules (min/max selections)
create_failed500Order creation failed and was rolled back

Payments​

CodeHTTPMeaning
currency_mismatch422Doesn't match the order's location currency
overpayment422Exceeds the remaining balance
subscription_locked403Blocked by the organization's subscription state
capture_failed500Capture failed and was rolled back

Inbound webhooks​

CodeHTTPMeaning
invalid_signature401X-SSP-Signature missing or wrong
unknown_event_type400Not a catalog event, not external.*, no wildcard match

Order state machine​

PUT /orders/{orderId} drives a coarse, plugin-facing state machine.

FromAllowed targets
openin_progress, cancelled
in_progressready, cancelled
readycompleted, cancelled
completedpaid
paid, cancelledterminal

cancellation_reason (max 2000 chars) is required when the target is cancelled. metadata, if supplied, is shallow-merged.

Plugin vocabulary vs internal statuses​

SSP's internal status set is finer-grained than the plugin one. The mapping:

Plugin statusInternal statuses that read as itWriting it sets
openCREATEDCREATED
in_progressCOOKINGCOOKING
readyPREPARED, SERVEDSERVED
completedCOMPLETEDCOMPLETED
paidPAIDPAID
cancelledCANCELLEDCANCELLED

The mapping is deliberately many-to-one and does not round-trip: an order in PREPARED reads as ready, and writing ready back moves it to SERVED. That's the intended direction of travel.

Three surfaces, two vocabularies
  • REST response bodies (GET /orders, GET /orders/{id}) return the plugin vocabulary.
  • The ?status= filter on GET /orders matches the internal column — pass CREATED, not open.
  • Webhook payloads (order.created, order.status_changed) carry internal values.

Normalise on your side rather than assuming one vocabulary throughout.

Internal-only states are not plugin-reachable

Orders in EDITABLE, PENDING, PROCESSING, FAILED, VOIDED, REFUNDED, PARTIALLY_REFUNDED, ABANDONED, PARTIALLY_PAID, or AWAITING_AUTHENTICATION have no plugin-facing equivalent, so any PUT against them returns 422 invalid_transition.


Capability gating​

Two write endpoints require a capability in your plugin's supported_features:

CapabilityGates
orders:createPOST /orders
payments:capturePOST /orders/{orderId}/payments

Without it: 403 feature_not_enabled. Scaffold plugins never carry these — declare them when you create the plugin from your project.


Idempotency​

Three endpoints support safe replay. In every case a duplicate returns 200 with the original record rather than an error — matching Stripe and Razorpay convention.

EndpointKeyWindow
POST /ordersexternal_order_id in the body, or the X-Idempotency-Key headerPer organization
POST /orders/{id}/paymentsprovider_transaction_idPer organization
POST /webhooks/externalidempotency_key in the body (max 128 chars)24 hours, per installation

A first-time create returns 201; a replay returns 200. Branch on the status code, or on the duplicate: true flag that webhook replays carry.


Field limits​

ConstraintValue
Items per created order100
Quantity per item1–999
Customizations per item50
Bulk availability items100
List limit1–100 (default 50)
cancellation_reason2000 chars
kitchen_instructions2000 chars
idempotency_key128 chars

OpenAPI specification​

A generated OpenAPI 3.0 spec covers every endpoint with typed request and response schemas, security schemes, and error shapes.

  • Browse it interactively in the Developer Portal's API Reference screen, which renders the live spec.
  • Import it into Postman, Insomnia, or Swagger Editor to generate a client.

The portal also provides a sandbox console that issues real, allowlisted calls against your own sandbox with your pik_ key.

Next steps​