Skip to main content

Orders API

The Orders API allows customers to track their gallery purchases. Orders contain items from multiple artists with independent fulfillment, so each order may have multiple shipments.

Overview

Gallery orders work differently from traditional e-commerce:

  • Multi-artist: One order can contain items from many artists
  • Per-artist fulfillment: Each artist ships their items independently
  • Cascading status: Parent order status updates based on artist statuses
  • Public tracking: No authentication required (uses public order ID)

Endpoints

Get Order by ID

Retrieves complete order details including all artist shipments.

Endpoint: GET /api/gallery/orders/{publicId}

Authentication: None (public order ID acts as credential)

URL Parameters:

ParameterTypeDescription
publicIdstringPublic order ID (format: GAL-YYYY-XXXXXX)

Response:

{
"order": {
"id": "order_abc123",
"publicId": "GAL-2024-ABC123",
"status": "partially_shipped",
"total": 230.80,
"subtotal": 200.00,
"galleryFee": 24.00,
"stripeFee": 6.80,
"email": "buyer@example.com",
"shippingOption": "single_address",
"shippingAddress": {
"name": "John Doe",
"line1": "123 Main St",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
},
"createdAt": "2024-01-15T10:30:00Z",
"paidAt": "2024-01-15T10:32:00Z",
"artistOrders": [
{
"id": "artist_order_001",
"orgId": "org_abc",
"artistName": "Jane Smith Studio",
"status": "shipped",
"subtotal": 120.00,
"artistPayout": 105.60,
"trackingNumber": "1Z999AA10123456784",
"trackingUrl": "https://www.ups.com/track?tracknum=1Z999AA10123456784",
"shippedAt": "2024-01-16T14:20:00Z",
"items": [
{
"id": "item_001",
"productId": "prod_001",
"productName": "Abstract Painting #5",
"quantity": 2,
"price": 60.00,
"image": "https://..."
}
]
},
{
"id": "artist_order_002",
"orgId": "org_def",
"artistName": "Bob's Pottery",
"status": "processing",
"subtotal": 80.00,
"artistPayout": 70.40,
"trackingNumber": null,
"trackingUrl": null,
"shippedAt": null,
"items": [
{
"id": "item_002",
"productId": "prod_002",
"productName": "Ceramic Vase",
"quantity": 1,
"price": 80.00,
"image": "https://..."
}
]
}
]
}
}

Response Fields:

Order Object

FieldTypeDescription
idstringInternal order UUID
publicIdstringPublic-facing order ID
statusstringOverall order status (see statuses below)
totalnumberTotal amount charged (USD)
subtotalnumberSum of all product prices
galleryFeenumber12% gallery commission
stripeFeenumber2.9% + $0.30 payment processing
emailstringCustomer email
shippingOptionstring"single_address" or "per_artist"
shippingAddressobjectShipping address (if single_address)
createdAtstringISO 8601 timestamp
paidAtstringISO 8601 timestamp
artistOrdersarrayPer-artist order breakdowns

Artist Order Object

FieldTypeDescription
idstringArtist order UUID
orgIdstringArtist organization ID
artistNamestringArtist display name
statusstringArtist order status
subtotalnumberTotal for this artist's items
artistPayoutnumberAmount artist receives (88%)
trackingNumberstringShipment tracking number (if shipped)
trackingUrlstringCarrier tracking URL (if shipped)
shippedAtstringISO 8601 timestamp (if shipped)
itemsarrayLine items for this artist

Example Request:

curl https://gallery.artbase.studio/api/gallery/orders/GAL-2024-ABC123

Error Responses:

StatusErrorDescription
404Order not foundInvalid public ID

Order Statuses

Gallery orders have cascading statuses based on artist fulfillment:

StatusDescriptionCriteria
pendingAwaiting paymentPaymentIntent created but not paid
paidPayment successfulPaymentIntent succeeded, no shipments yet
processingBeing preparedAt least one artist order is processing
partially_shippedPartial shipmentSome but not all artist orders shipped
shippedFully shippedAll artist orders shipped
deliveredFully deliveredAll artist orders delivered
cancelledCancelledOrder cancelled before any shipment

Status Flow

pending → paid → processing → partially_shipped → shipped → delivered

cancelled

Artist Order Statuses

Each artist order has its own status:

StatusDescription
pendingAwaiting payment
paidPayment received, not yet preparing
processingArtist is preparing items
shippedArtist has shipped items
deliveredItems delivered to customer

Cascading Logic

The parent order status is calculated from all artist orders:

function calculateOrderStatus(artistOrders) {
const allDelivered = artistOrders.every(ao => ao.status === 'delivered');
const allShipped = artistOrders.every(ao => ao.status === 'shipped' || ao.status === 'delivered');
const anyShipped = artistOrders.some(ao => ao.status === 'shipped' || ao.status === 'delivered');
const anyProcessing = artistOrders.some(ao => ao.status === 'processing');

if (allDelivered) return 'delivered';
if (allShipped) return 'shipped';
if (anyShipped) return 'partially_shipped';
if (anyProcessing) return 'processing';
return 'paid';
}

Tracking Numbers

When an artist ships items, they provide a tracking number:

Supported Carriers

The system auto-generates tracking URLs for major carriers:

CarrierPatternTracking URL
UPS1Z...https://www.ups.com/track?tracknum={number}
USPSVarieshttps://tools.usps.com/go/TrackConfirmAction?tLabels={number}
FedExVarieshttps://www.fedex.com/fedextrack/?trknbr={number}

Tracking URL Generation

function generateTrackingUrl(trackingNumber: string): string {
if (trackingNumber.startsWith('1Z')) {
return `https://www.ups.com/track?tracknum=${trackingNumber}`;
} else if (/^\d{20,22}$/.test(trackingNumber)) {
return `https://tools.usps.com/go/TrackConfirmAction?tLabels=${trackingNumber}`;
} else {
return `https://www.fedex.com/fedextrack/?trknbr=${trackingNumber}`;
}
}

Shipping Address Handling

Single Address

When shippingOption: "single_address", all artists ship to the same address:

{
"shippingAddress": {
"name": "John Doe",
"line1": "123 Main St",
"line2": "Apt 4B",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
}
}

Per-Artist Addresses

When shippingOption: "per_artist", each artist order has its own address:

{
"shippingOption": "per_artist",
"shippingAddress": null,
"artistOrders": [
{
"artistName": "Jane Smith",
"shippingAddress": {
"name": "Alice (Birthday Gift)",
"line1": "456 Oak Ave",
"city": "Boston",
"state": "MA",
"postal_code": "02101",
"country": "US"
}
},
{
"artistName": "Bob Johnson",
"shippingAddress": {
"name": "Carol (Holiday Gift)",
"line1": "789 Pine St",
"city": "Seattle",
"state": "WA",
"postal_code": "98101",
"country": "US"
}
}
]
}

Timeline & Expectations

Typical Order Timeline

StatusTimelineWhat's Happening
paidImmediately after checkoutPayment processed, artists notified
processing1-3 daysArtists preparing and packing items
partially_shipped2-7 daysFirst artist(s) shipped
shipped3-10 daysAll artists shipped
delivered5-14 daysAll packages delivered

Why Different Arrival Times?

  • Independent artists: Each artist manages their own fulfillment
  • Different locations: Artists ship from different locations
  • Different carriers: Artists choose their preferred carriers
  • Different processing times: Some artists ship same-day, others take 2-3 days

This is intentional - buyers are supporting independent artists who maintain control of their businesses.


Use Cases

Customer Order Tracking Page

import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';

export default function OrderTrackingPage() {
const { publicId } = useParams();
const [order, setOrder] = useState(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
async function fetchOrder() {
const res = await fetch(`/api/gallery/orders/${publicId}`);
const data = await res.json();
setOrder(data.order);
setLoading(false);
}
fetchOrder();
}, [publicId]);

if (loading) return <div>Loading order...</div>;
if (!order) return <div>Order not found</div>;

return (
<div>
<h1>Order {order.publicId}</h1>
<p>Status: {order.status}</p>
<p>Total: ${order.total}</p>

<h2>Shipments</h2>
{order.artistOrders.map(ao => (
<div key={ao.id}>
<h3>{ao.artistName}</h3>
<p>Status: {ao.status}</p>
{ao.trackingNumber && (
<a href={ao.trackingUrl} target="_blank">
Track: {ao.trackingNumber}
</a>
)}
<ul>
{ao.items.map(item => (
<li key={item.id}>
{item.quantity}x {item.productName} @ ${item.price}
</li>
))}
</ul>
</div>
))}
</div>
);
}

Order confirmation emails include a tracking link:

https://gallery.artbase.studio/gallery/orders/GAL-2024-ABC123

Customers can bookmark this URL to track their order anytime.


Security & Privacy

Public ID Design

Order IDs use a public format (GAL-YYYY-XXXXXX) that:

  • ✅ Is safe to expose in URLs
  • ✅ Is difficult to guess (random 6-character suffix)
  • ✅ Doesn't reveal internal database IDs
  • ✅ Includes year for easy human reference

What's Exposed

The order endpoint intentionally exposes:

  • ✅ Order status and shipment info
  • ✅ Product names and images
  • ✅ Shipping address (customer's own)
  • ✅ Total and pricing breakdown
  • ❌ Payment method details
  • ❌ Internal UUIDs
  • ❌ Stripe IDs

Guessing Protection

While public order IDs are accessible without auth, they're difficult to guess:

  • Format: GAL-{YEAR}-{6-char random}
  • Charset: Alphanumeric uppercase (36^6 = 2.1 billion combinations per year)
  • Example: GAL-2024-H7K9M2

Next Steps