Skip to main content

Production API

The production API handles order creation, status updates, and fulfillment tracking.

Endpoints

MethodEndpointDescription
POST/api/productionCreate new order
GET/api/production/[id]Get order details
PATCH/api/production/[id]Update order (ops)

Create Order

POST /api/production

Request

interface CreateOrderRequest {
  designUrl: string;     // URL of design to print
  garmentId: string;     // Shopify product ID
  sizes: SizeQuantity[]; // Sizes and quantities
  priority?: "rush" | "standard" | "hold";
  notes?: string;
}

interface SizeQuantity {
  size: string;   // "S", "M", "L", "XL", etc.
  quantity: number;
}

Example

curl -X POST https://yourapp.com/api/production \
  -H "Content-Type: application/json" \
  -H "Cookie: sb-access-token=..." \
  -d '{
    "designUrl": "https://ucarecdn.com/xxx/design.png",
    "garmentId": "gid://shopify/Product/123456",
    "sizes": [
      { "size": "M", "quantity": 2 },
      { "size": "L", "quantity": 3 }
    ],
    "priority": "standard"
  }'

Response

{
  "checkoutUrl": "https://checkout.stripe.com/c/pay/cs_xxx"
}
The response redirects to Stripe checkout. After payment, the order is created.

Get Order

GET /api/production/[id]

Response

interface OrderResponse {
  order: {
    id: string;
    reference: string;     // Human-readable: "ORD-001234"
    status: ProductionStatus;
    orderStatus: OrderState;
    createdAt: string;
    updatedAt: string;
    prompt: string | null;
    priority: "Rush" | "Standard" | "Hold";
    etaDays: number;
    imageUrl: string | null;
    garment: Garment | null;
    lineItems: LineItem[];
    events: FulfillmentEvent[];
  };
}

type ProductionStatus = 
  | "Spec Review"
  | "Queued"
  | "Printing"
  | "Finishing"
  | "Ready";

type OrderState =
  | "draft"
  | "submitted"
  | "approved"
  | "in_production"
  | "fulfilled"
  | "canceled";

Example

curl https://yourapp.com/api/production/550e8400-e29b-41d4-a716-446655440000 \
  -H "Cookie: sb-access-token=..."

Update Order (Ops Only)

PATCH /api/production/[id]
Requires operator role.

Request

interface UpdateOrderRequest {
  status?: OrderState;
  priority?: "rush" | "standard" | "hold";
  notes?: string;
}

Example

curl -X PATCH https://yourapp.com/api/production/xxx \
  -H "Content-Type: application/json" \
  -H "Cookie: sb-access-token=..." \
  -d '{
    "status": "in_production",
    "notes": "Started printing batch"
  }'

Order Flow

1

Checkout

User submits order → redirected to Stripe
2

Payment

Stripe processes payment → webhook fires
3

Order Created

Webhook creates order with “submitted” status
4

Production

Ops team processes through status flow
5

Fulfillment

Order marked “fulfilled” → user notified

Webhook Integration

Stripe webhook creates orders on payment:
// app/api/stripe/webhook/route.ts
case 'checkout.session.completed': {
  const session = event.data.object;
  const { designUrl, garmentId, sizes, userId } = session.metadata;
  
  // Create order
  const { data: order } = await supabase
    .from('production_orders')
    .insert({
      user_id: userId,
      status: 'submitted',
      metadata: { designUrl, garmentId },
    })
    .select()
    .single();
  
  // Create line items
  await supabase
    .from('production_line_items')
    .insert(
      JSON.parse(sizes).map((item: SizeQuantity) => ({
        order_id: order.id,
        size: item.size,
        quantity: item.quantity,
      }))
    );
  
  // Create initial event
  await supabase
    .from('fulfillment_events')
    .insert({
      order_id: order.id,
      status: 'Queued',
      message: 'Payment confirmed, order queued',
    });
  
  break;
}

Status Updates

Status changes create fulfillment events:
async function updateOrderStatus(orderId: string, newStatus: OrderState) {
  await supabase
    .from('production_orders')
    .update({ 
      status: newStatus,
      updated_at: new Date().toISOString(),
    })
    .eq('id', orderId);
  
  // Create event for history
  await supabase
    .from('fulfillment_events')
    .insert({
      order_id: orderId,
      status: STATUS_MAP[newStatus],
      message: getStatusMessage(newStatus),
    });
}

Realtime Subscriptions

Subscribe to order updates:
const channel = supabase
  .channel(`order:${orderId}`)
  .on('postgres_changes', {
    event: 'UPDATE',
    schema: 'public',
    table: 'production_orders',
    filter: `id=eq.${orderId}`,
  }, handleUpdate)
  .on('postgres_changes', {
    event: 'INSERT',
    schema: 'public',
    table: 'fulfillment_events',
    filter: `order_id=eq.${orderId}`,
  }, handleNewEvent)
  .subscribe();

Line Items

Create

Included in order creation via webhook.

Schema

interface LineItem {
  id: string;
  size: string;
  quantity: number;
  technique: string; // "dtg" | "screen" | "embroidery"
  metadata: Record<string, unknown> | null;
}

Fulfillment Events

Event history for audit trail:
interface FulfillmentEvent {
  id: string;
  status: string;
  message: string | null;
  occurredAt: string;
  metadata: Record<string, unknown> | null;
}

Error Responses

StatusErrorDescription
400”Invalid request”Missing required fields
401”Unauthorized”No valid session
403”Forbidden”Not order owner (or not ops for update)
404”Order not found”Invalid order ID
500”Internal error”Database or Stripe error

Ops Dashboard

Operators access all orders at /ops/orders:
  • Filter by status, priority, date
  • Bulk status updates
  • Add notes and messages
  • View order details
  • Track fulfillment progress
// Check ops access
import { requireOperatorAccess } from "@/lib/ops/access";

export async function PATCH(req: Request, { params }) {
  await requireOperatorAccess(); // Throws if not operator
  
  // Process update...
}