Get subscription fulfillment details for latest order
Retrieves fulfillment information for the most recent successful order associated with a subscription contract. This includes tracking details, shipment status, delivery estimates, and fulfillment line items from Shopify.
What This Endpoint Returns: Complete fulfillment data for the subscription’s last successfully billed order, queried in real-time from Shopify’s GraphQL API. Shows customers when and how their subscription items will be (or were) delivered.
Fulfillment Data Included:
Shipment Tracking:
- Tracking numbers and URLs
- Carrier information (USPS, FedEx, UPS, etc.)
- Tracking company details
- Tracking status updates
Delivery Status:
- Fulfillment status (fulfilled, in_transit, out_for_delivery, delivered, etc.)
- Estimated delivery date
- Actual delivery timestamp (if delivered)
- Delivery method (shipping, pickup, local delivery)
Fulfillment Details:
- Fulfilled line items (which products shipped)
- Quantities fulfilled
- Fulfillment service (manual, automated, 3PL)
- Fulfillment created/updated dates
- Multiple fulfillments (if order shipped in parts)
Location Information:
- Fulfillment location/warehouse
- Origin address details
- Fulfillment service name
Order Determination Logic:
Which Order is Retrieved:
- Finds most recent SUCCESS billing attempt for contract
- If found: Uses that order’s Shopify order ID
- If none: Falls back to subscription’s origin order ID
- Queries Shopify for that order’s fulfillments
Why This Matters:
- Shows CURRENT/LATEST fulfillment status
- Not historical fulfillments from months ago
- Reflects what customer is waiting for NOW
- Updates after each new billing cycle
Use Cases:
1. Customer Portal “Where’s My Order”:
- Display tracking information
- Show estimated delivery date
- Provide carrier tracking links
- Update fulfillment status
2. Subscription Order Tracking:
- Track recurring order deliveries
- Monitor fulfillment progress
- Identify delayed shipments
- Provide proactive delivery updates
3. Customer Support:
- Answer “where is my order” questions
- Verify shipment details
- Troubleshoot delivery issues
- Provide accurate tracking info
4. Automated Notifications:
- Send tracking emails automatically
- Notify customers of shipments
- Alert on delivery completion
- Trigger review request flows
5. Subscription Management:
- Show fulfillment in subscription history
- Display delivery patterns
- Track fulfillment reliability
- Monitor shipping performance
Response Structure:
Returns Shopify Order object with fulfillments:
{
"id": "gid://shopify/Order/123456789",
"name": "#1001",
"fulfillmentOrders": {
"edges": [
{
"node": {
"id": "gid://shopify/FulfillmentOrder/111111",
"status": "SUCCESS",
"fulfillments": {
"edges": [
{
"node": {
"trackingInfo": [
{
"number": "1Z999AA10123456784",
"url": "https://wwwapps.ups.com/tracking/...",
"company": "UPS"
}
],
"status": "IN_TRANSIT",
"estimatedDeliveryAt": "2024-03-20T00:00:00Z",
"deliveredAt": null
}
}
]
}
}
}
]
}
}
Common Scenarios:
Scenario: Order Fulfilled & Shipped
{
"trackingInfo": [{"number": "9400...", "company": "USPS"}],
"status": "IN_TRANSIT",
"estimatedDeliveryAt": "2024-03-18T00:00:00Z"
}
Scenario: Order Delivered
{
"status": "DELIVERED",
"deliveredAt": "2024-03-15T14:23:00Z"
}
Scenario: Not Yet Fulfilled
{
"fulfillmentOrders": {
"edges": [
{
"node": {
"status": "OPEN",
"fulfillments": {"edges": []}
}
}
]
}
}
Scenario: No Order Yet (New Subscription)
Returns null or empty response
Important Considerations:
Real-Time Shopify Query:
- Makes live call to Shopify GraphQL API
- NOT cached data
- Response time: 500-1500ms
- Subject to Shopify rate limits
Data Freshness:
- Returns current fulfillment status from Shopify
- Tracking updates reflect Shopify’s data
- May lag behind carrier’s actual status
- Shopify updates tracking periodically
Null Responses:
- Returns null if no orders exist yet
- Returns null if order ID not found
- Handle gracefully in UI
Multiple Fulfillments:
- Order may have multiple fulfillments (split shipments)
- Each fulfillment has own tracking
- Iterate through all fulfillment nodes
Integration Example:
Customer Portal - Track Shipment:
const order = await fetch(
`/api/external/v2/subscription-contract-details/subscription-fulfillments/${contractId}`,
{ headers: { 'X-API-Key': 'your-key' } }
).then(r => r.json());
if (!order) {
return '<p>No shipment information available yet.</p>';
}
const fulfillments = order.fulfillmentOrders?.edges || [];
fulfillments.forEach(fo => {
fo.node.fulfillments?.edges?.forEach(f => {
const tracking = f.node.trackingInfo?.[0];
if (tracking) {
console.log(`Track with ${tracking.company}: ${tracking.number}`);
console.log(`URL: ${tracking.url}`);
}
});
});
Best Practices:
- Cache Response: Cache for 30-60 minutes to reduce Shopify API calls
- Handle Nulls: Always check for null/empty responses
- Parse GraphQL: Navigate edges/nodes structure carefully
- Show All Tracking: Display all fulfillments if multiple shipments
- Link to Carrier: Provide clickable tracking URLs
Authentication: Requires valid X-API-Key header
curl --request GET \
--url https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}import requests
url = "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}';
const options = {method: 'GET'};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"fulfillmentOrders": {
"get__typename": "<string>",
"nodes": [
{
"fulfillAt": {},
"get__typename": "<string>",
"id": "<string>",
"status": "OPEN"
}
],
"pageInfo": {
"endCursor": "<string>",
"get__typename": "<string>",
"hasNextPage": true,
"hasPreviousPage": true,
"startCursor": "<string>"
}
},
"get__typename": "<string>",
"id": "<string>"
}{
"detail": "Contract ID must be a valid positive integer",
"status": 400,
"title": "Invalid contract ID",
"type": "https://example.com/errors/bad-request"
}{
"detail": "Valid X-API-Key header is required",
"status": 401,
"title": "Authentication required",
"type": "https://example.com/errors/unauthorized"
}{
"detail": "Contract does not belong to your shop",
"status": 403,
"title": "Access denied",
"type": "https://example.com/errors/forbidden"
}{
"detail": "Failed to retrieve fulfillment data from Shopify",
"status": 502,
"title": "Shopify API error",
"type": "https://example.com/errors/bad-gateway"
}Headers
Path Parameters
Contract ID
Query Parameters
API Key (Deprecated - Use Header X-API-Key instead)
curl --request GET \
--url https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}import requests
url = "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const url = 'https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}';
const options = {method: 'GET'};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://subscription-admin.appstle.com/api/external/v2/subscription-contract-details/subscription-fulfillments/{contractId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_body{
"fulfillmentOrders": {
"get__typename": "<string>",
"nodes": [
{
"fulfillAt": {},
"get__typename": "<string>",
"id": "<string>",
"status": "OPEN"
}
],
"pageInfo": {
"endCursor": "<string>",
"get__typename": "<string>",
"hasNextPage": true,
"hasPreviousPage": true,
"startCursor": "<string>"
}
},
"get__typename": "<string>",
"id": "<string>"
}{
"detail": "Contract ID must be a valid positive integer",
"status": 400,
"title": "Invalid contract ID",
"type": "https://example.com/errors/bad-request"
}{
"detail": "Valid X-API-Key header is required",
"status": 401,
"title": "Authentication required",
"type": "https://example.com/errors/unauthorized"
}{
"detail": "Contract does not belong to your shop",
"status": 403,
"title": "Access denied",
"type": "https://example.com/errors/forbidden"
}{
"detail": "Failed to retrieve fulfillment data from Shopify",
"status": 502,
"title": "Shopify API error",
"type": "https://example.com/errors/bad-gateway"
}