Alter Product Public API integration

Public API is designed for server-to-server integrations with storefronts, commerce backends, WordPress/WooCommerce plugins and external production workflows.

Authentication and base URL

https://alterproduct.com/public-api/v1

Create API credentials in the e-commerce settings panel. The Access Token is shown once, so store it immediately in your backend secret storage.

Keep the Access Key and Access Token on your server. Authenticated endpoints reject browser-origin calls that include Origin or Referer headers.

Credentials can be scoped. Use GET /auth/check to verify the active storefront, plan capabilities and scopes returned for the credential.

x-alter-access-key: YOUR_API_KEY
x-alter-access-token: YOUR_API_TOKEN
ParameterRequiredDetails
x-alter-access-keyyesPublic credential identifier.
x-alter-access-tokenyesSecret token paired with the access key.
x-alter-client-fingerprintnoOptional stable fingerprint for embed session rate limiting.
Authorizationruntime onlyBearer token returned by POST /embed/session, used by /runtime/bootstrap.

Connection test

Use the auth check endpoint before enabling synchronization or embed features in a live integration.

GET https://alterproduct.com/public-api/v1/auth/check

Example request (fetch)

const response = await fetch('https://alterproduct.com/public-api/v1/auth/check', {
  method: 'GET',
  headers: {
    'x-alter-access-key': process.env.ALTER_ACCESS_KEY,
    'x-alter-access-token': process.env.ALTER_ACCESS_TOKEN
  }
});

const payload = await response.json();

if (!response.ok) {
  throw new Error(payload?.code || payload?.error || `Alter API ${response.status}`);
}

console.log(payload);

Example response

{
  "ok": true,
  "message": "success",
  "storefrontId": 12,
  "userOwnerId": 34,
  "credentialId": 56,
  "scopes": ["orders:read", "orders:write", "products:read"],
  "plan": {
    "requiredPlan": "Business",
    "currentPlanName": "Business",
    "eligible": true,
    "runtimeFlags": {
      "viewer": true,
      "configurator": true,
      "customizer": true
    },
    "limits": {
      "activeRuntimeBindingsLimit": 100,
      "monthlyReassignmentLimit": 1000,
      "monthlyEmbedTokenLimit": 50000
    }
  }
}

The helper below is used by the remaining examples. It is plain fetch and can run in Node.js 18+ or any server runtime that provides fetch.

const ALTER_API_BASE = 'https://alterproduct.com/public-api/v1';

const authHeaders = {
  'x-alter-access-key': process.env.ALTER_ACCESS_KEY,
  'x-alter-access-token': process.env.ALTER_ACCESS_TOKEN
};

async function alterFetch(path, options = {}) {
  const response = await fetch(`${ALTER_API_BASE}${path}`, {
    ...options,
    headers: {
      ...authHeaders,
      ...(options.body ? { 'Content-Type': 'application/json' } : {}),
      ...options.headers
    }
  });

  const payload = await response.json().catch(() => null);

  if (!response.ok) {
    throw new Error(payload?.code || payload?.error || `Alter API ${response.status}`);
  }

  return payload;
}

Endpoint overview

The table below mirrors the public routes mounted in backend-public-api/app.js. Paths are shown with the public proxy prefix used by external integrations.

MethodEndpointDescriptionAccess
GET/public-api/healthzService health check.public
GET/public-api/v1/auth/checkValidates credentials and returns storefront, scopes and plan capabilities.any authenticated credential
GET/public-api/v1/customer-ordersReturns a paginated and filterable list of customer orders.orders:read
GET/public-api/v1/customer-orders/:idReturns a single customer order with configured product items.orders:read
POST/public-api/v1/customer-orders/batchReturns up to 100 orders by ID.orders:read
PATCH/public-api/v1/customer-orders/:id/statusUpdates the order status.orders:write
PATCH/public-api/v1/customer-orders/:orderId/quantityUpdates selected order detail quantities.orders:write
PATCH/public-api/v1/customer-orders/:orderId/quantity/allSets one quantity for every item in an order.orders:write
DELETE/public-api/v1/customer-orders/:idDeletes a customer order owned by the storefront owner.orders:write
GET/public-api/v1/productsReturns storefront products/designs with embed availability and media URLs.products:read
GET/public-api/v1/products/:idReturns one storefront product/design.products:read
POST/public-api/v1/embed/sessionIssues a short-lived JWT for designer/viewer/configurator/customizer runtimes.embed:session:create
GET/public-api/v1/runtime/bootstrapResolves runtime context from an embed JWT.Bearer embed token
GET/public-api/v1/assetsLists asset catalog items for the requested type.any authenticated credential
GET/public-api/v1/assets/:type/:assetIdReturns an asset manifest with downloadable file roles.any authenticated credential
GET/public-api/v1/assets/:type/:assetId/files/:roleDownloads an asset file by role.any authenticated credential
GET/public-api/v1/design-importsLists importable Alter-hosted designs.authenticated credential, Business plan required
GET/public-api/v1/design-imports/:idReturns a design import payload and file descriptors.authenticated credential, Business plan required
GET/public-api/v1/design-imports/:id/files/:fileIdDownloads a file from a design import descriptor.authenticated credential, Business plan required
GET/public-api/v1/file/public/products/:productId/:sizeReturns a public product preview. Size must be small.png, medium.png or big.png.public
GET/public-api/v1/file/protected/:keyReturns a protected file by storage key.signed URL or files:read
GET/public-api/v1/fontsReturns all available fonts.public
GET/public-api/v1/currenciesReturns all currencies.public
POST/public-api/v1/runtime-bindings/sync-from-wordpressCreates or updates runtime bindings from WordPress product mappings.any authenticated credential
PATCH/public-api/v1/runtime-bindings/:idPatches a runtime binding.any authenticated credential
POST/public-api/v1/runtime-bindings/:id/activateActivates a runtime binding.any authenticated credential
POST/public-api/v1/runtime-bindings/:id/deactivateDeactivates a runtime binding.any authenticated credential
POST/public-api/v1/wp-connect/exchangeExchanges a WordPress auto-connect handoff code for API credentials.one-time handoff code

Customer orders

Customer order endpoints let an external store read configured line items, update quantities, move an order through fulfillment statuses and remove abandoned orders.

ParameterRequiredDetails
namenoSearches design name and numeric order ID.
category_idnoFilters by product category ID.
order_statusnoOne of the allowed order statuses.
offsetnoDefault 0. Must be >= 0.
limitnoDefault 9 for this controller, maximum 50.
order_bynoid, created_at or design_name.
directionnoASC or DESC.

Example request (fetch)

const params = new URLSearchParams({
  limit: '20',
  offset: '0',
  order_status: 'shopping_cart',
  order_by: 'created_at',
  direction: 'DESC'
});

const orders = await alterFetch(`/customer-orders?${params.toString()}`);

const order = await alterFetch('/customer-orders/123');

const batch = await alterFetch('/customer-orders/batch', {
  method: 'POST',
  body: JSON.stringify({
    customerOrderIds: [123, 124, 125]
  })
});

Allowed values

StatusDescription
shopping_cartCart flow; customer can still edit the configuration.
editableOrder remains editable by the customer.
paidOrder is paid and ready for fulfillment.
processingOrder is in fulfillment.
completedOrder has been fulfilled.
cancelledOrder was cancelled.

Example request (fetch)

await alterFetch('/customer-orders/123/status', {
  method: 'PATCH',
  body: JSON.stringify({
    status: 'processing'
  })
});

await alterFetch('/customer-orders/123/quantity', {
  method: 'PATCH',
  body: JSON.stringify({
    items: [
      { orderDetailId: 987, quantity: 3 }
    ]
  })
});

await alterFetch('/customer-orders/123/quantity/all', {
  method: 'PATCH',
  body: JSON.stringify({
    quantity: 2
  })
});

await alterFetch('/customer-orders/123', {
  method: 'DELETE'
});

Example response

{
  "order": {
    "id": 123,
    "customizerId": 381,
    "orderStatus": "shopping_cart",
    "createdAt": "2026-05-28T10:15:00.000Z",
    "customizerOrderURL": "https://alterproduct.com/app/customizer/381/123",
    "productItems": [
      {
        "id": 987,
        "model3d": { "id": 381 },
        "size": {
          "id": 395,
          "name": { "pl": "M", "en": "M" },
          "measureSize": null
        },
        "material": {
          "id": 2,
          "name": { "pl": "Bawełna", "en": "Cotton" }
        },
        "printType": {
          "id": 1,
          "name": { "pl": "DTG", "en": "DTG" }
        },
        "color": {
          "id": 418,
          "name": { "pl": "Domyślny", "en": "Default" },
          "hex": "#ffffff"
        },
        "variant": {
          "id": 531,
          "metadata": null,
          "stockQuantity": 25
        },
        "unitPrice": { "value": 12.5, "currency": "EUR" },
        "totalPrice": { "value": 37.5, "currency": "EUR" },
        "quantity": 3
      }
    ],
    "customizerName": "Men's T-Shirt",
    "productGroup": {
      "id": 4,
      "name": { "pl": "Koszulka", "en": "T-Shirt" }
    },
    "totalPrice": { "value": 37.5, "currency": "EUR" }
  }
}

Storefront products

Product endpoints return storefront designs that can be embedded as viewer, configurator or customizer experiences.

ParameterRequiredDetails
namenoSearches product/design name.
customizernotrue or false.
offsetnoDefault 0. Must be >= 0.
limitnoDefault 9, maximum 50.
order_bynoid, name or created_at.
directionnoASC or DESC.

Example request (fetch)

const params = new URLSearchParams({
  limit: '20',
  offset: '0',
  name: 't-shirt',
  customizer: 'true',
  order_by: 'created_at',
  direction: 'DESC'
});

const products = await alterFetch(`/products?${params.toString()}`);
const product = await alterFetch('/products/381');

Example response

{
  "products": {
    "items": [
      {
        "id": 381,
        "name": "Men's T-Shirt",
        "createdAt": "2026-01-03T23:55:05.000Z",
        "productId": 4,
        "media": {
          "img": {
            "big": "https://alterproduct.com/public-api/v1/file/public/products/4/big.png",
            "medium": "https://alterproduct.com/public-api/v1/file/public/products/4/medium.png",
            "small": "https://alterproduct.com/public-api/v1/file/public/products/4/small.png"
          },
          "mockups": []
        },
        "storefrontProduct": {
          "id": 89,
          "idUserDesign": 381,
          "shareAccess": "public",
          "isCustomizer": 1
        },
        "runtimeBindings": [
          {
            "id": 42,
            "runtimeType": "customizer",
            "status": "active",
            "externalProductId": "wc_123"
          }
        ],
        "embeddable": {
          "viewer": true,
          "configurator": true,
          "customizer": true
        }
      }
    ],
    "total": 1
  }
}

Embed sessions and runtime bootstrap

Create a short-lived embed token from your server, pass it to the iframe/runtime, then let the runtime call bootstrap with a Bearer token.

ParameterRequiredDetails
runtimeBindingIdrecommendedPreferred identifier for active runtime bindings.
toolrequired without runtimeBindingIddesigner, viewer, configurator or customizer.
originyesOrigin where the embed is rendered, for example https://yourstore.com.
designIdone identifierAlter Product design ID. Do not combine with orderId.
orderIdone identifierCustomizer order ID. Only valid for customizer.
cartKey + cartModenoCustomizer-only cart context. cartMode is view or edit.

Example request (fetch)

const session = await alterFetch('/embed/session', {
  method: 'POST',
  headers: {
    'x-alter-client-fingerprint': '9f1b7a5e4b3c2d1f9f1b7a5e4b3c2d1f'
  },
  body: JSON.stringify({
    runtimeBindingId: 42,
    origin: 'https://yourstore.com'
  })
});

const bootstrapResponse = await fetch('https://alterproduct.com/public-api/v1/runtime/bootstrap', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${session.token}`
  }
});

const bootstrap = await bootstrapResponse.json();
console.log({ session, bootstrap });

Notes

await alterFetch('/embed/session', {
  method: 'POST',
  body: JSON.stringify({
    tool: 'customizer',
    origin: 'https://yourstore.com',
    orderId: 123
  })
});

await alterFetch('/embed/session', {
  method: 'POST',
  body: JSON.stringify({
    tool: 'viewer',
    origin: 'https://yourstore.com',
    designId: 381
  })
});

Example response

{
  "token": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjEifQ...",
  "expiresIn": 900,
  "kid": "1",
  "mode": "design",
  "runtimeBindingId": 42,
  "runtimeType": "customizer"
}

Runtime bootstrap

{
  "runtimeBindingId": 42,
  "designId": 381,
  "productId": "wc_123",
  "runtimeType": "customizer",
  "storageMode": "wordpress_local",
  "manifestUrl": "https://yourstore.com/wp-content/uploads/alter/381/manifest.json",
  "assetBaseUrl": "https://yourstore.com/wp-content/uploads/alter/381/",
  "manifestHash": "a3b1...",
  "planCapabilities": {
    "viewer": true,
    "configurator": true,
    "customizer": true
  },
  "cartKey": null,
  "cartMode": null,
  "orderId": null
}

Asset catalog

The asset catalog exposes product source assets, backgrounds, environments, graphic library items, design templates and mockup assets. List endpoints return lightweight descriptors; detail endpoints include file manifests.

TypeDescription
productsBase product assets, previews, 3D models, material and texture descriptors.
backgroundsStatic viewer backgrounds.
environmentsEnvironment maps and preview images.
image_libraryGraphic library assets, including storefront-scoped graphics.
design_templatesDesign template previews and layer file references. Supports product_id filter.
mockupsMockup generator assets, backgrounds and overlay maps. Supports product_id filter.

Example request (fetch)

const assets = await alterFetch('/assets?' + new URLSearchParams({
  type: 'products',
  limit: '20',
  offset: '0',
  search: 'mug'
}));

const details = await alterFetch('/assets/products/4');

const fileResponse = await fetch(
  'https://alterproduct.com/public-api/v1/assets/products/4/files/preview_medium',
  {
    headers: authHeaders
  }
);

const fileBlob = await fileResponse.blob();

Example response

{
  "type": "products",
  "items": [
    {
      "assetType": "products",
      "assetId": "4",
      "title": "Mug 450ml",
      "slug": "product-4",
      "description": "Base product 4",
      "primaryRole": "preview_big",
      "fileCount": 8,
      "remoteVersion": "1.0",
      "thumbnail": {
        "role": "preview_small",
        "fileName": "product-4-preview-small.png",
        "mime": "image/png",
        "downloadPath": "/v1/assets/products/4/files/preview_small"
      },
      "metadata": {
        "productCategoryId": 2,
        "productModelCount": 1,
        "isDedicated": false
      }
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0
}

Design imports

Design imports expose Alter-hosted designs and their files for external production or migration flows. Business plan eligibility is checked by the API.

ParameterRequiredDetails
searchnoSearches design title or ID.
offsetnoDefault 0.
limitnoDefault 20, maximum 100.

Example request (fetch)

const imports = await alterFetch('/design-imports?' + new URLSearchParams({
  limit: '20',
  offset: '0',
  search: 'mug'
}));

const details = await alterFetch('/design-imports/381');

const fileId = details.files[0].id;
const fileResponse = await fetch(
  `https://alterproduct.com/public-api/v1/design-imports/381/files/${fileId}`,
  {
    headers: authHeaders
  }
);

const fileBlob = await fileResponse.blob();

Example response

{
  "eligible": true,
  "requiredPlan": "Business",
  "currentPlanName": "Business",
  "designs": [
    {
      "id": 381,
      "title": "Men's T-Shirt",
      "createdAt": "2026-01-03T23:55:05.000Z",
      "sourceStorefrontId": 12,
      "productId": 4,
      "productName": {
        "pl": "Koszulka",
        "en": "T-Shirt"
      },
      "storageMode": "alter",
      "runtimeStatus": {
        "designer": true,
        "viewer": true,
        "configurator": true,
        "customizer": true
      },
      "thumbnail": {
        "kind": "design-mockup",
        "fileId": "7df7...",
        "downloadPath": "/v1/design-imports/381/files/7df7..."
      }
    }
  ],
  "total": 1
}

Files, fonts and currencies

Public preview files are browser-friendly. Protected files require a signed URL or an API credential with files:read. Fonts and currencies are public read endpoints.

EndpointAccessDetails
/file/public/products/:productId/small.pngpublicSmall product preview.
/file/public/products/:productId/medium.pngpublicMedium product preview.
/file/public/products/:productId/big.pngpublicLarge product preview.
/file/protected/:keysigned URL or files:readProtected object storage file.
/fontspublicArray of font records.
/currenciespublicArray of currency records.

Example request (fetch)

const publicPreview = await fetch(
  'https://alterproduct.com/public-api/v1/file/public/products/4/medium.png'
);

const protectedFile = await fetch(
  'https://alterproduct.com/public-api/v1/file/protected/user_34/381/design/mockup-large.webp',
  {
    headers: authHeaders
  }
);

const fonts = await fetch('https://alterproduct.com/public-api/v1/fonts').then((res) => res.json());
const currencies = await fetch('https://alterproduct.com/public-api/v1/currencies').then((res) => res.json());

Example response

[
  {
    "id": 1,
    "family": "Inter",
    "source": "google",
    "category": "sans-serif",
    "variants": ["regular", "600", "700"],
    "subsets": ["latin"],
    "version": "v19",
    "menu": "Inter",
    "files": {
      "regular": "https://..."
    }
  }
]
[
  {
    "id": 1,
    "code": "EUR",
    "name": "Euro",
    "symbol": "€",
    "decimalPlaces": 2
  }
]

Runtime bindings

Runtime bindings connect external commerce products to Alter Product designs and runtime types. They are primarily used by WordPress/WooCommerce integrations and advanced storefront backends.

ParameterRequiredDetails
designIdnoAlter Product design ID owned by the storefront.
externalProductIdyes for syncExternal product ID, for example a WooCommerce product ID.
runtimeTypeyes for syncviewer, configurator or customizer.
statusnodraft, active, inactive, archived or legacy_active.
legacyStorefrontProductIdnoOptional legacy mapping ID.
legacyBindingMetanoOptional JSON metadata, for example manifestHash.

Example request (fetch)

await alterFetch('/runtime-bindings/sync-from-wordpress', {
  method: 'POST',
  body: JSON.stringify({
    bindings: [
      {
        externalProductId: 'wc_123',
        runtimeType: 'customizer',
        status: 'active',
        designId: 381,
        legacyBindingMeta: {
          manifestHash: 'a3b1...'
        }
      }
    ]
  })
});

await alterFetch('/runtime-bindings/42', {
  method: 'PATCH',
  body: JSON.stringify({
    status: 'inactive'
  })
});

await alterFetch('/runtime-bindings/42/activate', { method: 'POST' });
await alterFetch('/runtime-bindings/42/deactivate', { method: 'POST' });

wordpress_local

await alterFetch('/runtime-bindings/sync-from-wordpress', {
  method: 'POST',
  body: JSON.stringify({
    bindings: [
      {
        externalProductId: 'wc_123',
        runtimeType: 'viewer',
        status: 'active',
        externalDesign: {
          externalDesignKey: 'wp-design-381',
          productId: 4,
          title: 'WooCommerce local design',
          manifestUrl: 'https://yourstore.com/wp-content/uploads/alter/381/manifest.json',
          assetBaseUrl: 'https://yourstore.com/wp-content/uploads/alter/381/',
          manifestHash: 'a3b1...',
          sourceMeta: {
            pluginVersion: '1.2.0'
          }
        }
      }
    ]
  })
});

Example response

{
  "message": "runtimeBinding.syncCompleted",
  "runtimeBindings": [
    {
      "id": 42,
      "designId": 381,
      "externalProductId": "wc_123",
      "runtimeType": "customizer",
      "status": "active"
    }
  ]
}

WordPress connect exchange

The WordPress connect exchange endpoint consumes a one-time handoff code and returns API credentials to the plugin. It is not a general-purpose credential creation endpoint.

Example request (fetch)

const response = await fetch('https://alterproduct.com/public-api/v1/wp-connect/exchange', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    code: 'ONE_TIME_HANDOFF_CODE',
    storeUrl: 'https://yourstore.com/',
    siteOrigin: 'https://yourstore.com',
    codeVerifier: 'PKCE_CODE_VERIFIER_32_TO_128_CHARS'
  })
});

const credentials = await response.json();

Example response

{
  "message": "wpConnect.exchange.ok",
  "accessKey": "generated-access-key",
  "accessToken": "generated-access-token",
  "storefrontId": 12
}

Errors and rate limits

Most controller errors are normalized to a code response. Authentication middleware and rate limiters can return an error response instead.

// Controller error
{
  "code": "assetCatalog.invalidType"
}

// Auth middleware or rate limit
{
  "error": "Unauthorized"
}

{
  "error": "Too Many Requests"
}
TypeLimitWindow
Global600 requests60 seconds
GET /auth/check60 requests60 seconds
Orders read/products read300 requests60 seconds
Orders write/embed sessions/runtime bindings120 requests60 seconds
Assets/design imports read180 requests60 seconds
Fonts300 requests60 seconds
WP connect exchange30 requests60 seconds