Shopify Order Lookup with CustomGPT.ai API

What you’ll build

A server‑side workflow that securely fetches live order data from your Shopify store and injects a minimal, ephemeral order snapshot into your CustomGPT.ai agent at answer time. The agent can then answer “Where is my order?”, “What’s my tracking number?”, etc., using only that user’s data.
No customer PII is uploaded to the agent’s knowledge base—it’s passed per‑request as custom_context.

Know more about custom_context here


When to use this pattern

Use this pattern if you need the agent to answer questions about individual orders or customers and you don’t want to permanently index private data. For general store FAQs, policies, or product content, use standard Sources (sitemaps/files). For order status, use ephemeral context via the Conversations or Chat‑Completions APIs.


Architecture (high level)

  1. Shopify Custom App – Installed in your store; provides an Admin API access token. You’ll query the GraphQL Admin API server‑side. (Shopify)

  2. Secure middleware – A small server that:

    • Confirms the requester’s identity (e.g., logged‑in customer email or order number).

    • Fetches the matching order(s) from Shopify.

    • Builds a short, non‑sensitive order summary.

    • Calls CustomGPT.ai with the user’s prompt plus that order summary as custom_context.

  3. CustomGPT.ai – Answers using your agent’s instructions and the ephemeral context. You can use the Conversations Messages endpoint (multipart form with custom_context) or the OpenAI‑format Chat‑Completions endpoint.


Prerequisites

  • Shopify store where you can create a custom app and obtain an Admin API access token. (Shopify)

  • CustomGPT.ai agent and an API token (Bearer) to call the API. All endpoints require Authorization: Bearer <token>.

  • A server environment (Node.js, Python, etc.) to host the middleware.


Step 1 — Create the Shopify custom app & token

  1. In the store admin, go to Settings → Apps and sales channels → Develop apps.

  2. Create appConfigure Admin API scopes:

    • Required: read_orders
    • Optional: read_all_orders if you must access orders older than 60 days (requires additional access request). (Shopify)
  3. Install the app to the store to generate the Admin API access token. Copy the token and store it securely (server‑side). (Shopify)


Notes

  • Shopify limits order access to the last ~60 days unless you have read_all_orders. (Shopify)
  • If you access customer PII (name, email, address), your app may need Protected Customer Data approval depending on fields and data use. (Shopify)

Step 2 — Set up a secure middleware

Never call the Shopify Admin API from the browser. Your middleware should:

  • Accept the customer’s question and a trusted identifier (e.g., the logged‑in customer’s email from Shopify Liquid or a verified order number).

  • Query Shopify’s GraphQL Admin API with the Admin API access token using the header X‑Shopify‑Access‑Token.

  • Build a minimal order snapshot (statuses, dates, tracking URL/number, masked address).

  • Call CustomGPT.ai with the user’s prompt and attach the snapshot as an ephemeral custom_context. (Shopify)


Step 3 — Fetch orders from Shopify (GraphQL Admin API)

Endpoint POST https://{SHOP_DOMAIN}.myshopify.com/admin/api/{VERSION}/graphql.json

Headers: Content-Type: application/json, X-Shopify-Access-Token: {ADMIN_API_TOKEN}. (Shopify)

Example query (lookup by order name #1234 or by customer email):

query ($q: String!) {
  orders(first: 1, query: $q) {
    edges {
      node {
        id
        name
        createdAt
        displayFinancialStatus
        displayFulfillmentStatus
        totalPriceSet { shopMoney { amount currencyCode } }
        customer { email displayName }
        shippingAddress { name address1 city provinceCode countryCode zip }
        fulfillments(first: 5) {
          createdAt
          trackingInfo { number url company }
          status
        }
      }
    }
  }
}

Typical query strings:

See Shopify’s orders GraphQL reference for supported filters and fields. (Shopify)

Older orders: by default you’ll only see ~60 days. Request read_all_orders to query historical orders. (Shopify)


Step 4 — Ask CustomGPT.ai with ephemeral context

You have two equivalent ways to pass the live order snapshot to the agent.

Option A — Conversations API (recommended for multi‑turn)

  1. Create a conversation
curl -X POST "https://app.customgpt.ai/api/v1/projects/{PROJECT_ID}/conversations" \
  -H "Authorization: Bearer $CUSTOMGPT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Order support" }'

Returns a session_id.

  1. Send a message with custom_context (multipart/form-data)
curl -X POST \
  "https://app.customgpt.ai/api/v1/projects/{PROJECT_ID}/conversations/{SESSION_ID}/messages?external_id=#1234&lang=en" \
  -H "Authorization: Bearer $CUSTOMGPT_TOKEN" \
  -F 'prompt=Where is my order #1234?' \
  -F 'response_source=default' \
  -F 'custom_context=Order: #1234
Placed: 2025-10-01T13:05:00Z
Payment: PAID
Fulfillment: IN_TRANSIT
Total: 42.90 USD
Ship to: A. Example, Portland OR 97205 US
Tracking: CarrierX #1Z999... https://track.example/1Z999'

The Messages endpoint supports custom_context and optional external_id, with real‑time streaming if needed.

Why this is safe: custom_context is not stored as a Source; it’s evaluated for the response and retained only as part of the conversation record according to your agent settings (see retention below).

Option B — OpenAI‑format Chat‑Completions (stateless)

Send the prompt and paste the order snapshot directly into messages.

curl -X POST \
  "https://app.customgpt.ai/api/v1/projects/{PROJECT_ID}/chat/completions" \
  -H "Authorization: Bearer $CUSTOMGPT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {"role":"system","content":"You are Kabrita support. Answer using the provided order snapshot only."},
      {"role":"user","content":"Where is my order #1234?"},
      {"role":"user","content":"Order snapshot:\nOrder: #1234\nPlaced: 2025-10-01T13:05:00Z\nPayment: PAID\nFulfillment: IN_TRANSIT\nTracking: CarrierX #1Z999... https://track.example/1Z999"}
    ],
    "lang": "en",
    "external_id": "#1234",
    "stream": false
  }'

OpenAPI path is /api/v1/projects/{projectId}/chat/completions. (Some Postman collections also include the hyphenated alias /chat-completions.)


End‑to‑end code samples

Python / Node (GraphQL + Conversations API)

import os, requests, json
from textwrap import dedent

SHOP_DOMAIN = os.getenv("SHOP_DOMAIN")             # e.g., "kabrita"
SHOP_API_VERSION = "2025-10"                       # pin a current version
SHOP_ADMIN_TOKEN = os.getenv("SHOP_ADMIN_TOKEN")   # Admin API access token

CUSTOMGPT_PROJECT_ID = os.getenv("CUSTOMGPT_PROJECT_ID")
CUSTOMGPT_TOKEN = os.getenv("CUSTOMGPT_TOKEN")

ORDERS_QUERY = dedent("""
query($q: String!) {
  orders(first: 1, query: $q) {
    edges {
      node {
        name createdAt displayFinancialStatus displayFulfillmentStatus
        totalPriceSet { shopMoney { amount currencyCode } }
        customer { email displayName }
        shippingAddress { name address1 city provinceCode countryCode zip }
        fulfillments(first: 5) {
          createdAt status trackingInfo { number url company }
        }
      }
    }
  }
}
""")

def shopify_graphql(q: str):
    url = f"https://{SHOP_DOMAIN}.myshopify.com/admin/api/{SHOP_API_VERSION}/graphql.json"
    headers = {
        "Content-Type": "application/json",
        "X-Shopify-Access-Token": SHOP_ADMIN_TOKEN
    }
    resp = requests.post(url, headers=headers, json={"query": ORDERS_QUERY, "variables": {"q": q}})
    resp.raise_for_status()
    data = resp.json()
    if "errors" in data:
        raise RuntimeError(data["errors"])
    return data["data"]

def order_snapshot(q: str) -> str:
    data = shopify_graphql(q)
    edges = data["orders"]["edges"]
    if not edges: 
        return ""
    o = edges[0]["node"]
    total = o["totalPriceSet"]["shopMoney"]
    lines = [
        f"Order: {o['name']}",
        f"Placed: {o['createdAt']}",
        f"Payment: {o['displayFinancialStatus']}",
        f"Fulfillment: {o['displayFulfillmentStatus']}",
        f"Total: {total['amount']} {total['currencyCode']}",
        f"Customer: {o['customer']['displayName']} <{o['customer']['email']}>",
    ]
    sa = o.get("shippingAddress")
    if sa:
        lines += [ "Ship to:",
                   f"  {sa['name']}",
                   f"  {sa['address1']}, {sa['city']} {sa['provinceCode']} {sa['zip']}, {sa['countryCode']}" ]
    if o.get("fulfillments"):
        lines.append("Fulfillments:")
        for f in o["fulfillments"]:
            ti = (f.get("trackingInfo") or [])
            t = ti[0] if ti else {}
            line = f"  {f['status']} at {f['createdAt']}"
            if t.get("number") or t.get("url"):
                line += f" — Tracking {t.get('company') or ''} #{t.get('number') or ''} {t.get('url') or ''}".strip()
            lines.append(line)
    return "\n".join(lines)

def create_conversation():
    url = f"https://app.customgpt.ai/api/v1/projects/{CUSTOMGPT_PROJECT_ID}/conversations"
    headers = {"Authorization": f"Bearer {CUSTOMGPT_TOKEN}", "Content-Type": "application/json"}
    r = requests.post(url, headers=headers, json={"name":"Order support"})
    r.raise_for_status()
    return r.json()["data"]["session_id"]

def send_with_context(session_id: str, prompt: str, context: str, ext_id: str=None):
    url = f"https://app.customgpt.ai/api/v1/projects/{CUSTOMGPT_PROJECT_ID}/conversations/{session_id}/messages"
    if ext_id: url += f"?external_id={ext_id}&lang=en"
    hdrs = {"Authorization": f"Bearer {CUSTOMGPT_TOKEN}"}
    files = {
        "prompt": (None, prompt),
        "response_source": (None, "default"),
        "custom_context": (None, context)
    }
    r = requests.post(url, headers=hdrs, files=files)
    r.raise_for_status()
    return r.json()["data"]["answer"]

if __name__ == "__main__":
    q = 'name:"#1234"'          # or e.g., 'email:"[email protected]"'
    context = order_snapshot(q)
    if not context:
        print("No matching order.")
    else:
        sid = create_conversation()
        ans = send_with_context(sid, "Where is my order #1234?", context, ext_id="#1234")
        print(ans)
import express from "express";
import fetch from "node-fetch";

const app = express();
app.use(express.json());

const {
  SHOP_DOMAIN,
  SHOP_ADMIN_TOKEN,
  SHOP_API_VERSION = "2025-10",
  CUSTOMGPT_PROJECT_ID,
  CUSTOMGPT_TOKEN
} = process.env;

app.post("/order-status", async (req, res) => {
  const { email, orderName, question } = req.body; // identity comes from your logged-in session
  const q = orderName ? `name:"${orderName}"` : `email:"${email}"`;

  const shopResp = await fetch(`https://${SHOP_DOMAIN}.myshopify.com/admin/api/${SHOP_API_VERSION}/graphql.json`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Shopify-Access-Token": SHOP_ADMIN_TOKEN
    },
    body: JSON.stringify({
      query: /* GraphQL */ `
        query($q:String!) {
          orders(first:1, query:$q) {
            edges { node {
              name createdAt displayFinancialStatus displayFulfillmentStatus
              totalPriceSet { shopMoney { amount currencyCode } }
              fulfillments(first:5) { createdAt status trackingInfo { number url company } }
            }}
          }
        }`,
      variables: { q }
    })
  });
  const data = await shopResp.json();
  const edges = data?.data?.orders?.edges || [];
  if (!edges.length) return res.status(404).json({ message: "Order not found" });

  const o = edges[0].node;
  const total = o.totalPriceSet.shopMoney;
  const snapshot = [
    `Order: ${o.name}`,
    `Placed: ${o.createdAt}`,
    `Payment: ${o.displayFinancialStatus}`,
    `Fulfillment: ${o.displayFulfillmentStatus}`,
    `Total: ${total.amount} ${total.currencyCode}`
  ].join("\n");

  // Create conversation
  const conv = await fetch(`https://app.customgpt.ai/api/v1/projects/${CUSTOMGPT_PROJECT_ID}/conversations`, {
    method: "POST",
    headers: { "Authorization": `Bearer ${CUSTOMGPT_TOKEN}`, "Content-Type": "application/json" },
    body: JSON.stringify({ name: "Order support" })
  }).then(r => r.json());
  const sessionId = conv.data.session_id;

  // Send message with custom_context (multipart)
  const form = new FormData();
  form.append("prompt", question || `Where is my order ${o.name}?`);
  form.append("response_source", "default");
  form.append("custom_context", snapshot);

  const msg = await fetch(
    `https://app.customgpt.ai/api/v1/projects/${CUSTOMGPT_PROJECT_ID}/conversations/${sessionId}/messages?external_id=${encodeURIComponent(o.name)}&lang=en`,
    { method: "POST", headers: { "Authorization": `Bearer ${CUSTOMGPT_TOKEN}` }, body: form }
  ).then(r => r.json());

  res.json({ answer: msg.data.answer });
});

app.listen(3000);

  • Shopify GraphQL Admin API reference and token generation steps. (Shopify)

  • CustomGPT Conversations endpoints and custom_context parameter.

Agent settings you’ll likely adjust

  • Persona instructions (tone, escalation rules) and response source (recommend leaving as default to prefer your content + provided context).

  • Retention: set a shorter conversation retention period if any PII is present in conversations (e.g., conversation_retention_period, conversation_retention_days).

  • Inline citations: optional; remember your live Shopify snapshot isn’t a long‑term Source.


Security & privacy checklist

  • Server‑side only: Keep the Shopify Admin API access token on the server; never expose it client‑side. (Shopify)

  • Minimize PII: Build a small snapshot (status, dates, tracking link). Avoid sending full addresses when not needed.

  • Scopes: Request only what you need (read_orders; add read_all_orders only if you truly need older history). (Shopify)

  • Protected customer data: If you handle names, emails, addresses, follow Shopify’s Protected Customer Data requirements. (Shopify)

  • Retention: Use shorter conversation retention if conversations may contain PII.


API details at a glance

  • Auth: Authorization: Bearer {CUSTOMGPT_TOKEN} for all CustomGPT endpoints.

  • Create conversation: POST /api/v1/projects/{projectId}/conversations (JSON).

  • Send message (Conversations):
    POST /api/v1/projects/{projectId}/conversations/{sessionId}/messages (multipart form)
    Fields include: prompt, response_source (default|own_content|openai_content), custom_context, optional file.

  • Send message (OpenAI format):
    OpenAPI path: POST /api/v1/projects/{projectId}/chat/completions (JSON). Postman collections may also show /chat-completions. Use whichever is enabled for your account/version.

  • Streaming: both message endpoints support a stream flag for Server‑Sent Events.


Limitations & gotchas

  • Shopify 60‑day order window: Without read_all_orders, you’ll only see ~60 days of orders. Plan for that approval if your support team needs older history. (Shopify)

  • Payload sizes: The Conversations messages endpoint accepts an optional file (max ~5MB; common formats like pdf/doc/txt/jpg/webp) but don’t use this for order PII—prefer plain‑text snapshots.

  • Two message APIs: Conversations (multipart with custom_context) vs. Chat‑Completions (OpenAI JSON). Choose one per integration to avoid confusion.

  • Rate limits: Both Shopify and CustomGPT.ai rate‑limit requests; cache short‑lived snapshots when safe (e.g., same order within a minute) and avoid querying Shopify multiple times per user turn.


Troubleshooting

  • “Access denied for orders field” – Verify your custom app’s scopes include read_orders (and read_all_orders if needed), and that you installed the app after configuring scopes so the token includes them. (Shopify Community)

  • No order found – Ensure you’re querying by the correct field (name:"#1234" vs email:"[email protected]"). Some stores prefix order names; match exactly. (Shopify)

  • PII compliance – If you include email/address in the snapshot, confirm you meet Shopify’s protected data requirements. (Shopify)


Appendix — Minimal cURL recipes

Create conversation

curl -X POST "https://app.customgpt.ai/api/v1/projects/{PROJECT_ID}/conversations" \
  -H "Authorization: Bearer $CUSTOMGPT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Order support" }'

Send message with ephemeral context

curl -X POST "https://app.customgpt.ai/api/v1/projects/{PROJECT_ID}/conversations/{SESSION_ID}/messages" \
  -H "Authorization: Bearer $CUSTOMGPT_TOKEN" \
  -F 'prompt=Where is my order #1234?' \
  -F 'response_source=default' \
  -F 'custom_context=Order: #1234 ...'

Fields include prompt, response_source, and **custom_context*.

OpenAI‑format (single shot)

curl -X POST "https://app.customgpt.ai/api/v1/projects/{PROJECT_ID}/chat/completions" \
  -H "Authorization: Bearer $CUSTOMGPT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "messages":[{"role":"user","content":"..."}], "lang":"en" }'

References

  • CustomGPT.ai API (auth, conversations, messages, chat‑completions, settings).

  • Shopify — Generate Admin API access tokens, GraphQL Admin API, orders query, access scopes, protected customer data. (Shopify)