# Order Handoff API

Social Print Studio provides an API endpoint that lets external applications add products to our Shopify cart. Your app sends a simple JSON payload describing what to add, and receives a URL to redirect the customer to. The customer lands on our Shopify cart with the product already added, and can then checkout and pay directly on our website. We handle production, printing, and any customer service.

### What you don't need

- Shopify API credentials or access tokens
- Knowledge of Shopify variant IDs
- Access to our database
- Any understanding of our internal systems

### What you do need

- The variant **SKU** for the product (must exactly match a SKU in our Shopify store — see [SKU Requirements](#sku-requirements))
- A **source** identifier for your app (you create this yourself, based on your app name)
- **Project data** with at least one publicly accessible photo URL
- The ability to make an HTTP POST request and redirect the user's browser
- **(Optional)** An [API key](/docs/authentication) in the `Authorization` header for order attribution, revenue split, and tracking

## Endpoint

`POST https://printkit.dev/api/add-to-cart`

```
Content-Type: application/json
Authorization: Bearer <your-api-key>  (optional)
```

## Request Body

### Required Fields

| Field | Type | Description |
| --- | --- | --- |
| `sku` | string | The Shopify product SKU. Must exactly match a variant SKU in our Shopify store. |
| `source` | string | An identifier for your application (e.g. `"my-app-name"`). Used for tracking and debugging. |
| `projectData` | object | Project/order data to persist for fulfillment. All products require this since they are custom printed. |

### Optional Fields

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `email` | string | — | Your contact email address. Highly recommended — allows us to reach out if there are any issues with the order. |
| `quantity` | number | `1` | How many copies to add to the cart. Must be a positive integer. |
| `properties` | object | `{}` | Extra key-value pairs to attach to the Shopify line item. See [Line Item Properties](#line-item-properties). |
| `checkout` | boolean | `false` | Skip the cart and send the customer straight to checkout. See [Direct Checkout](#direct-checkout). |

## Project Data

The `projectData` object contains the custom content (photos, designs, etc.) that our fulfillment team needs to access when producing the order. When included, the data is saved to our project database and a unique `projectId` is generated. This ID is automatically attached to the Shopify line item.

| Field | Type | Description |
| --- | --- | --- |
| `projectData.photos` | string[] | Array of image URLs. Must be publicly accessible. At least one URL is required. |
| `projectData.metadata` | object | Any arbitrary JSON-serializable data you want to persist with the project. Optional in most cases. |

### Photo URL Requirements

| Requirement | Details |
| --- | --- |
| Formats | **JPG or PNG only** (`.jpg`, `.jpeg`, or `.png`) |
| Max file size | 50 MB per image |
| URL validity | Must remain publicly accessible for **at least one week** after the order is placed |

> **Image quality tip:** If your app exports or compresses images, use 100% quality (no lossy compression). These images are used for physical printing — downsampled or compressed images may result in visible artifacts in the final print.

## Line Item Properties

The `properties` object lets you attach key-value string pairs to the Shopify cart line item. These are visible in the Shopify order admin and available to our fulfillment systems.

> **Convention:** Prefix property keys with `_` (underscore) to hide them from the customer on the storefront. Properties without the underscore prefix will be visible to the customer in their cart and order confirmation.

| Example Key | Description |
| --- | --- |
| `_project_Id` | A project ID from your app (hidden from customer). Useful if you need to cross-reference an order later. |
| `_cover_preview_url` | A URL shown as the product thumbnail/preview image in the cart (hidden from customer). |
| `Edit Project Link` | A URL letting the customer return to your app to edit their project. Visible to the customer. |
| `Project Name` | An optional customer-facing name, shown in the cart. Visible to the customer. |

## Response

### Success (200)

```
{
  "success": true,
  "redirectUrl": "https://socialprintstudio.com/pages/newcart?cartKey=prod_1707400000_abc123def",
  "projectId": "65a1b2c3d4e5f6a7b8c9d0e1"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `success` | boolean | `true` if the item was staged successfully. |
| `redirectUrl` | string | The URL to redirect the customer's browser to. Single-use — works once, then expires. |
| `projectId` | string | The database ID of the saved project. Store this for your own records if needed. |

**After receiving the response, redirect the customer's browser to `redirectUrl`.**

### Error Responses

| Status | Meaning |
| --- | --- |
| `400` | Missing or invalid required field (`sku`, `source`, or `projectData`). Check the error message. |
| `401` | Invalid API key. Only returned when an `Authorization` header is provided but the key is not valid. Omitting the header entirely is fine. |
| `405` | Wrong HTTP method. Use `POST`. |
| `502` | SKU could not be resolved to a Shopify product. It likely doesn't match any variant in our store. |
| `500` | Internal server error. Contact us if this persists. |

All error responses include an `error` field with a human-readable message:

```
{
  "error": "Missing required field: \"sku\" (string). This should be the Shopify product SKU."
}
```

## Direct Checkout

By default, the `redirectUrl` sends the customer to the Social Print Studio cart where they can review their order before checking out. If you'd rather skip the cart and send the customer straight to checkout, add `"checkout": true` to your request body. Everything else stays the same — same endpoint, same fields, same response format. The only difference is where the `redirectUrl` points.

### When to use direct checkout

- **Single-product apps** where the cart feels like an unnecessary extra step. The customer already decided what they want in your app.
- **AI agents** creating a one-off print for a single user. Going straight to payment is the most natural flow.
- **External-site handoffs** where showing another storefront's cart could be confusing or distracting. Direct checkout keeps the experience seamless.

> **Default is add-to-cart:** If you omit `checkout` or set it to `false`, the behavior is exactly the same as before — the customer lands on the cart page. This is fully backward compatible.

### Direct Checkout Example

```javascript
const response = await fetch(
  'https://printkit.dev/api/add-to-cart',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      sku: 'metal-print-4x4',
      source: 'my-app',
      email: 'developer@example.com',
      checkout: true,
      projectData: {
        photos: ['https://cdn.example.com/photo.jpg'],
      },
    }),
  }
);

const { redirectUrl } = await response.json();

// Customer goes straight to checkout — no cart step
window.location.href = redirectUrl;
```

```bash
curl -X POST https://printkit.dev/api/add-to-cart \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "metal-print-4x4",
    "source": "my-app",
    "email": "developer@example.com",
    "checkout": true,
    "projectData": {
      "photos": ["https://cdn.example.com/photo.jpg"]
    }
  }'
```

## Examples

### Minimal Example — Simple Photo Product

```javascript
const response = await fetch(
  'https://printkit.dev/api/add-to-cart',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`,  // optional — enables attribution
    },
    body: JSON.stringify({
      sku: 'metal-print-4x4',
      source: 'my-partner-app',
      email: 'developer@example.com',  // your email, highly recommended
      projectData: {
        photos: [
          'https://cdn.example.com/uploads/user123/photo-sunset.jpg',
        ],
      },
    }),
  }
);

const { redirectUrl } = await response.json();

// Send the customer to the Shopify cart
window.location.href = redirectUrl;
```

```bash
curl -X POST https://printkit.dev/api/add-to-cart \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "sku": "metal-print-4x4",
    "source": "my-partner-app",
    "email": "developer@example.com",
    "projectData": {
      "photos": [
        "https://cdn.example.com/uploads/user123/photo-sunset.jpg"
      ]
    }
  }'

# Response:
# { "success": true, "redirectUrl": "https://...", "projectId": "..." }
# Redirect the customer's browser to redirectUrl
```

```python
import httpx

response = httpx.post(
    "https://printkit.dev/api/add-to-cart",
    headers={
        "Authorization": f"Bearer {API_KEY}",  # optional — enables attribution
    },
    json={
        "sku": "metal-print-4x4",
        "source": "my-partner-app",
        "email": "developer-email@example.com",  # your email, highly recommended
        "projectData": {
            "photos": [
                "https://cdn.example.com/uploads/user123/photo-sunset.jpg",
            ],
        },
    },
)

data = response.json()
redirect_url = data["redirectUrl"]

# Redirect the customer to the Shopify cart
# In a web framework: return RedirectResponse(redirect_url)
```

### Full Example — Photo Product with Metadata

```javascript
const response = await fetch(
  'https://printkit.dev/api/add-to-cart',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`,  // optional — enables attribution
    },
    body: JSON.stringify({
      sku: 'photo-books-softcover',
      quantity: 1,
      source: 'my-partner-app',
      email: 'developer-email@example.com',

      projectData: {
        photos: [
          'https://cdn.example.com/uploads/user123/cover-file.jpg',
          'https://cdn.example.com/uploads/user123/page-1.jpg',
          'https://cdn.example.com/uploads/user123/page-2.jpg',
          // ... continue for 38 total pages of the photo book
          'https://cdn.example.com/uploads/user123/page-38.jpg',
        ],
        metadata: {
          bookTitle: 'Summer Vacation 2025',
          customerNotes: 'Extra info stored with the order record.',
        },
      },

      properties: {
        'Project Name': 'Summer Vacation 2025',
        _cover_preview_url:
          'https://cdn.example.com/previews/user123/cover-thumb.jpg',
      },
    }),
  }
);

const data = await response.json();

if (!response.ok) {
  console.error('Failed to add to cart:', data.error);
  return;
}

console.log('Project saved with ID:', data.projectId);
window.location.href = data.redirectUrl;
```

```bash
curl -X POST https://printkit.dev/api/add-to-cart \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "sku": "photo-books-softcover",
    "quantity": 1,
    "source": "my-partner-app",
    "email": "developer-email@example.com",
    "projectData": {
      "photos": [
        "https://cdn.example.com/uploads/user123/cover-file.jpg",
        "https://cdn.example.com/uploads/user123/page-1.jpg",
        "https://cdn.example.com/uploads/user123/page-2.jpg",
        "https://cdn.example.com/uploads/user123/page-38.jpg"
      ],
      "metadata": {
        "bookTitle": "Summer Vacation 2025",
        "customerNotes": "Extra info stored with the order record."
      }
    },
    "properties": {
      "Project Name": "Summer Vacation 2025",
      "_cover_preview_url": "https://cdn.example.com/previews/user123/cover-thumb.jpg"
    }
  }'
```

```python
import httpx

response = httpx.post(
    "https://printkit.dev/api/add-to-cart",
    headers={
        "Authorization": f"Bearer {API_KEY}",  # optional — enables attribution
    },
    json={
        "sku": "photo-books-softcover",
        "quantity": 1,
        "source": "my-partner-app",
        "email": "developer-email@example.com",
        "projectData": {
            "photos": [
                "https://cdn.example.com/uploads/user123/cover-file.jpg",
                "https://cdn.example.com/uploads/user123/page-1.jpg",
                "https://cdn.example.com/uploads/user123/page-2.jpg",
                # ... continue for 38 total pages
                "https://cdn.example.com/uploads/user123/page-38.jpg",
            ],
            "metadata": {
                "bookTitle": "Summer Vacation 2025",
                "customerNotes": "Extra info stored with the order record.",
            },
        },
        "properties": {
            "Project Name": "Summer Vacation 2025",
            "_cover_preview_url": "https://cdn.example.com/previews/user123/cover-thumb.jpg",
        },
    },
)

data = response.json()

if response.status_code != 200:
    print(f"Failed to add to cart: {data['error']}")
else:
    print(f"Project saved with ID: {data['projectId']}")
    # Redirect the customer: return RedirectResponse(data["redirectUrl"])
```

### Multiple Quantity

```javascript
const response = await fetch(
  'https://printkit.dev/api/add-to-cart',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`,  // optional — enables attribution
    },
    body: JSON.stringify({
      sku: 'photo-print-30x40',
      quantity: 3,
      source: 'my-partner-app',
      projectData: {
        photos: ['https://cdn.example.com/photo1.jpg'],
      },
    }),
  }
);

const { redirectUrl } = await response.json();
window.location.href = redirectUrl;
```

```bash
curl -X POST https://printkit.dev/api/add-to-cart \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "sku": "photo-print-30x40",
    "quantity": 3,
    "source": "my-partner-app",
    "projectData": {
      "photos": ["https://cdn.example.com/photo1.jpg"]
    }
  }'
```

```python
import httpx

response = httpx.post(
    "https://printkit.dev/api/add-to-cart",
    headers={
        "Authorization": f"Bearer {API_KEY}",  # optional — enables attribution
    },
    json={
        "sku": "photo-print-30x40",
        "quantity": 3,
        "source": "my-partner-app",
        "projectData": {
            "photos": ["https://cdn.example.com/photo1.jpg"],
        },
    },
)

data = response.json()
redirect_url = data["redirectUrl"]
```

## SKU Requirements

The `sku` value must exactly match a product variant SKU in our Shopify store. If the SKU doesn't match, the API returns a `502` error.

- SKUs are **case-sensitive** and typically lowercase with hyphens (e.g. `metal-print-8x10`, `photo-books-softcover`)
- Some products include **style qualifiers** (e.g. `photo-print-30x40-fullbleed`, `print-8x10-black-mat`)
- For the fastest machine-friendly lookup, filter `https://printkit.dev/variants.json` and then verify the selected SKU against the per-product JSON file

> **Important:** An incorrect or misspelled SKU will cause the request to fail, or worse, succeed but print the wrong product or charge the wrong amount. Always verify SKUs against the per-product JSON file before submitting.

## Integration Flow

```
┌─────────────────────┐
│   Your Application  │
│                     │
│  1. User finishes   │
│     creating their  │
│     product         │
│                     │
│  2. POST to         │
│     /api/add-to-cart│
│     with SKU +      │
│     project data    │
└────────┬────────────┘
         │
         ▼
┌─────────────────────┐
│   Our API Endpoint  │
│                     │
│  3. Saves project   │
│     data to our DB  │
│                     │
│  4. Stages cart     │
│     items           │
│                     │
│  5. Returns         │
│     redirectUrl     │
└────────┬────────────┘
         │
         ▼
┌─────────────────────┐
│  Your app redirects │
│  the user's browser │
│  to redirectUrl     │
└────────┬────────────┘
         │
    ┌────┴─────┐
    ▼          ▼
┌────────┐ ┌────────────┐
│ Cart   │ │ Checkout   │
│(default│ │(checkout:  │
│ flow)  │ │  true)     │
│        │ │            │
│Customer│ │ Customer   │
│reviews │ │ goes       │
│cart,   │ │ straight   │
│then    │ │ to payment │
│checks  │ │            │
│out     │ │            │
└────────┘ └────────────┘
```

## Important Notes

1. **The redirect URL is single-use.** Once the Shopify cart page retrieves the cart data, the key is deleted. Do not attempt to reuse it.
2. **Photo URLs must be publicly accessible.** Our fulfillment system downloads these images when producing the order. URLs behind authentication or on localhost will not work. URLs must remain valid for at least one week. If you need simple image hosting, see the [Image Upload API](/docs/image-upload).
3. **The `source` field is important.** It helps us track which app generated the order and debug issues. Pick a consistent, descriptive identifier and use it on every request.
4. **CORS is enabled.** You can call this endpoint from client-side JavaScript. If you call from a backend server, make sure you pass the redirect URL to the customer's browser for the final redirect step.
5. **Keep payloads small.** The request body contains URLs and metadata, not file data. In practice this is well under any size limit, but avoid embedding large base64 strings or excessive metadata.
6. **The `projectId` in the response** is the database reference for the saved project. Store this in your own system if you may need to reference it later for debugging or support.
