Pengine Store API

Quickstart

From an API key to your first order in five steps.

Use this guide to verify your credentials, read a product, create an order, and track it through shipping. Each request is part of a typical production integration.

Get your store ID and API key

Open your custom store in the Pengine app, enable the Store API, and copy the store ID and API key. You need both values for every request.

export PENGINE_STORE_ID="ovy3u54frpsl4sd963aja0h1"
export PENGINE_API_KEY="pgn_qfL0dZrwqXYuwvlt4nYlB0ytL78Uv4PU"

The key is a server-side secret. It reads every product on the store and creates orders you are billed for, so keep it out of browsers and public repositories.

Check that it works

The status endpoint returns 200 only when the key matches the store in the path. Use it in your deployment checks.

curl https://api.pengine.io/store-api/2026-08/stores/$PENGINE_STORE_ID/status \
  -H "X-API-Key: $PENGINE_API_KEY"
200 OK
{
  "status": "ok",
  "message": "Pengine Store API is up and running!",
  "version": "2026-08",
  "store": { "id": "ovy3u54frpsl4sd963aja0h1" },
  "checkedAt": "2026-08-19T12:37:20.829Z"
}

A wrong key, a wrong store, or a mismatched pair is a 401:

401 Unauthorized
{ "message": "Invalid API key", "error": "Unauthorized", "statusCode": 401 }

Read your products

Product responses include the variants, SKUs, prices, colors, sizes, and mockup images needed to render a storefront.

curl "https://api.pengine.io/store-api/2026-08/stores/$PENGINE_STORE_ID/products?filter[status]=active&limit=20" \
  -H "X-API-Key: $PENGINE_API_KEY"
200 OK, trimmed
{
  "items": [
    {
      "id": "saryo4m9p6ki3u1558m6h56j",
      "name": "Midweight Hoodie",
      "status": "active",
      "colors": ["Black", "White"],
      "sizes": ["S", "M", "XL"],
      "variants": [
        {
          "id": "ak88bwfjjqn5cjt5jaczmn1n",
          "sku": "PGN70872789915240044115",
          "color": "Black",
          "size": "S",
          "price": 166.8,
          "cost": 130.7,
          "isDiscontinued": false,
          "position": 1
        }
      ],
      "images": [
        {
          "id": "zkk2lkpxredez88sw0ljgwia",
          "color": "Black",
          "image": {
            "id": "kl1v18gmjnd420uziof6ecv0",
            "url": "https://cdn.pengine.io/files/kl1v18gmjnd420uziof6ecv0/Midweight-Hoodie-Front-Black.png"
          },
          "position": 1
        }
      ]
    }
  ],
  "totalItems": 1,
  "startItem": 1,
  "endItem": 1,
  "totalPages": 1,
  "query": { "page": 1, "limit": 20, "filter": { "status": ["active"] }, "sort": { "createdAt": "desc" } }
}

Every status is returned

Without a filter you get drafts and archived products too. This API is server to server, so it hands your backend every product and lets you control what the public sees. Only active and unlisted products can be ordered.

Create an order

Each line item identifies a variant by productVariantId or sku and sets a quantity. Set externalOrderId to your order number so retries cannot create duplicate orders.

curl -X POST https://api.pengine.io/store-api/2026-08/stores/$PENGINE_STORE_ID/orders \
  -H "X-API-Key: $PENGINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externalOrderId": "web-10482",
    "lineItems": [
      { "productVariantId": "ak88bwfjjqn5cjt5jaczmn1n", "quantity": 2 }
    ],
    "shippingAddress": {
      "firstName": "Ada",
      "lastName": "Lovelace",
      "email": "ada@example.com",
      "phone": "+12025550176",
      "address": {
        "streetAddress": "1600 Pennsylvania Avenue NW",
        "apartmentOrSuite": "Suite 4",
        "cityOrTown": "Washington",
        "stateOrProvince": "DC",
        "zipOrPostalCode": "20500",
        "country": "US"
      }
    }
  }'
201 Created, trimmed
{
  "id": "p948dnb4qbo2d86i2mx67svp",
  "name": "#127505",
  "externalOrderId": "web-10482",
  "status": "pending",
  "fulfillmentStatus": "pending",
  "paymentStatus": "due",
  "totalQuantity": 2,
  "subtotalCost": 261.4,
  "shippingCost": 17.85,
  "totalCost": 279.25,
  "orderedAt": "2026-08-19T12:37:20.896Z",
  "createdAt": "2026-08-19T12:37:20.897Z"
}

Store the returned id with your own order record. Later API responses and webhooks refer to this ID.

A 409 means you already sent this externalOrderId. Fetch the existing order instead of creating another. See Errors for the retry rules.

The costs are yours, not your buyer's

Everything named cost on an order is what Pengine bills you, shipping included. What your customer paid belongs to your storefront and does not appear in the Store API.

Track the order

An order starts with a pending status. Fetch it at any time to check its progress:

curl https://api.pengine.io/store-api/2026-08/stores/$PENGINE_STORE_ID/orders/p948dnb4qbo2d86i2mx67svp \
  -H "X-API-Key: $PENGINE_API_KEY"

Once it ships, trackingInfo appears on the same response:

{
  "status": "fulfilled",
  "fulfillmentStatus": "fulfilled",
  "trackingInfo": {
    "carrier": "USPS",
    "trackingNumber": "9400100000000000000000",
    "trackingUrl": "https://tools.usps.com/go/TrackConfirmAction?tLabels=9400100000000000000000"
  },
  "fulfilledAt": "2026-08-22T16:11:04.113Z"
}

Register an orders_update webhook and refetch only the order that changed. See Webhooks.

Next

On this page