Skip to main content
Customei exposes a small set of app proxy endpoints designed to be called from the storefront (not the admin). They’re the opposite of the GraphQL API: public-facing, unauthenticated from the caller’s perspective, and automatically HMAC-signed by Shopify when routed through the /apps/pod/* proxy path. These endpoints exist to let the storefront runtime do things that a pure-client-side widget can’t: stage customer uploads to durable storage, look up fee variant IDs, geocode customer addresses, and a few other storefront-specific needs.

How Shopify app proxies work in 60 seconds

Shopify exposes a magic URL prefix on every store:
https://<your-shop>.myshopify.com/apps/pod/<path>
Any request to /apps/pod/<path> is forwarded by Shopify to Customei with:
  • The shop domain in a header (so Customei knows which tenant to serve).
  • An HMAC signature computed by Shopify over the request — Customei verifies it to ensure the request actually came through a real Shopify storefront.
This gives you a way to call Customei from storefront JS without exposing a Customei domain to the browser, and with strong guarantees that calls can’t be spoofed by random internet traffic.
You don’t usually call these endpoints yourself — the Customei storefront widget does. They’re documented here so you understand what’s happening, and so you can call them directly if you’re building custom storefront tooling.

Available endpoints

EndpointPurpose
POST /apps/pod/upload-stageGet a presigned URL to upload a customer file to S3.
GET /apps/pod/fee-variantLook up the fee-product variant ID for a given price add-on.
POST /apps/pod/image-processRun server-side image processing (e.g. background removal).
GET /apps/pod/geocodeReverse-geocode a lat/lng pair.
GET /apps/pod/mapbox/*Proxy requests to Mapbox (tiles, geocoding) using a server-side token.
Each of these is designed for a specific storefront feature. They’re not a general-purpose API — for that, use GraphQL.

Upload stage

POST /apps/pod/upload-stage Called by the storefront runtime when a customer picks a file in an image upload option. It takes the file’s name and type, asks Customei for a presigned S3 URL, and returns it — the client then uploads the file directly to S3 in a second step.

Request

POST /apps/pod/upload-stage HTTP/1.1
Content-Type: application/json

{
  "filename": "my-photo.jpg",
  "contentType": "image/jpeg",
  "size": 1572864
}
Customei validates:
  • Filename — sanitized to remove path separators and unsafe characters.
  • Extension — must be in an allowlist of accepted types: images (png, jpg, jpeg, webp, gif, bmp, tiff, svg), documents (txt, doc, docx, pdf, xlsx, csv), audio (mp3, wav, wma), video (mp4, mov, qt, avi, rmvb), and archives (zip, rar).
  • Content type — must match the extension’s expected MIME type.
  • Size — capped per option-set configuration.

Response

{
  "uploadUrl": "https://customei-uploads.s3.amazonaws.com/staged/...",
  "assetKey": "staged/acc_01H8.../asset_01H8...",
  "expiresAt": "2026-04-13T10:35:00.000Z"
}
  • uploadUrl is a presigned PUT URL valid for a few minutes. Upload the file directly to this URL with a PUT request and the raw file bytes as the body.
  • assetKey is the Customei reference for the staged asset — use it in subsequent API calls (e.g. setting the upload on a line item).

Why two-step?

Two-step uploads keep Customei’s servers off the hot path for file bytes. The file never touches Customei — it goes client → S3 — which keeps upload latency low and our origin bandwidth bounded.

Fee variant lookup

GET /apps/pod/fee-variant?amount=500&currency=USD Used on non-Plus stores where Customei uses the Fee Variant pricing strategy. The storefront widget calls this to find the variant ID of the hidden fee product that matches a specific add-on amount, so it can add that variant to the cart alongside the real product.

Request

Query parameters:
  • amount — the add-on amount in the shop’s smallest currency unit (e.g. cents for USD).
  • currency — the currency code (USD, EUR, etc.).

Response

{
  "variantId": "gid://shopify/ProductVariant/4081718493184",
  "title": "$5.00 add-on",
  "price": "5.00"
}
If no variant exists for the requested amount, Customei creates one on demand (fee variants are generated lazily) and returns the new variant ID. This is a rare code path — most common prices are pre-provisioned.

Image processing

POST /apps/pod/image-process Runs server-side image operations on a staged asset. Currently supports:
  • Background removal (for customer photo uploads).
  • Auto-crop to a target aspect ratio.
  • Color-space normalization.
Each operation counts as a compute job and uses account credits. The response returns the URL of the processed output and the job status.

Geocode

GET /apps/pod/geocode?lat=37.7749&lng=-122.4194 Reverse-geocodes a lat/lng pair to a human-readable address. Used by the Map and Star map option field types when a customer picks a location from a map. Returns a standard geocoding response (formatted address, administrative divisions, country code). Backed by Customei’s server-side geocoding provider, which is why this can’t be called directly from the browser without the signed proxy path.

Mapbox proxy

GET /apps/pod/mapbox/<path> A thin pass-through to the Mapbox API using Customei’s server-side Mapbox token. Used by option fields that render map tiles or call Mapbox geocoding directly. The storefront widget constructs these URLs; you generally don’t need to call them yourself.

Verifying the Shopify signature

Customei verifies Shopify’s HMAC signature on every app-proxy request automatically. You don’t need to sign anything in the caller — Shopify adds the signature as it forwards the request. What you do need to handle if you’re building a custom storefront integration:
  • Always use URLs starting with /apps/pod/ on the customer’s shop domain — never hit Customei’s origin directly from storefront JS. The origin is not reachable without the signature.
  • Don’t try to forge the signature yourself. Go through Shopify’s proxy.

When to call these yourself vs use the widget

  • Use the widget for 99% of storefront personalization needs. It handles upload staging, fee variant lookups, and mapping out of the box.
  • Call directly only if you’re building a custom widget or a non-widget integration — e.g. a quote form that collects a file upload without the full personalization UI. In those cases, use upload-stage to get a presigned URL, upload the file to S3, and then reference the assetKey in your GraphQL mutations.

Next