> ## Documentation Index
> Fetch the complete documentation index at: https://print.app/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify a project before fulfilling

> Check, from your server, that a project a customer is ordering belongs to your store and was made for that product.

When a customer saves a design, the editor gives your page a `projectId` in the [`app:saved`](https://print.app/docs/api-reference/editor/configuration#the-appsaved-payload) event. That event happens in the customer's browser, so treat the `projectId` as a claim, not as proof: a customer could send any id with their order.

Before you fulfil an order, have your server ask Print.App about the project with your [secret key](https://print.app/docs/api-reference/v2/authentication.md). The answer tells you whether the project is yours and which product it was made for.

**1. Tell the editor which product it's for**

When you open the editor, pass a `product` object describing the product and, ideally, a reference your server generated for this cart line or session. Print.App saves it with the project exactly as you pass it.

```javascript theme={null}
const printAppInstance = new PrintAppClient({
    domainKey: 'dom_…',
    designId: 'design_…',
    product: {
        id: 'sku-123',               // your product id
        ref: 'cart-line-7f3a',       // generated by your server, rendered into the page
    },
});
```

`product` can hold any JSON values you find useful. Keep it small.

**2. Send the projectId with the order**

On `app:saved`, put the `projectId` into your cart line or post it to your server.

```javascript theme={null}
printAppInstance.addEventListener('app:saved', async ({ data }) => {
    await fetch('/cart/lines', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ productId: 'sku-123', printappProjectId: data.projectId }),
    });
});
```

**3. Verify on your server**

Before accepting the order, fetch the project:

```javascript Node.js theme={null}
async function verifyProject(projectId, { productId, ref }) {
    const res = await fetch(`https://api.print.app/v2/projects/${encodeURIComponent(projectId)}`, {
        headers: { Authorization: `Bearer ${process.env.PRINTAPP_SECRET_KEY}` },
    });
    if (res.status === 404) return { ok: false, reason: 'not your project' };
    if (!res.ok) throw new Error(`Print.App API ${res.status}: ${res.headers.get('x-request-id')}`);

    const project = await res.json();
    if (project.product?.id !== productId) return { ok: false, reason: 'made for another product' };
    if (ref && project.product?.ref !== ref) return { ok: false, reason: 'made in another session' };
    if (project.status !== 'submitted') return { ok: false, reason: 'not finished' };
    return { ok: true, project };
}
```

```php PHP theme={null}
function verify_project(string $projectId, string $productId, ?string $ref = null): array {
    $ch = curl_init('https://api.print.app/v2/projects/' . rawurlencode($projectId));
    curl_setopt_array($ch, [
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('PRINTAPP_SECRET_KEY')],
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

    if ($status === 404) return ['ok' => false, 'reason' => 'not your project'];
    if ($status !== 200) throw new RuntimeException("Print.App API $status");

    $project = json_decode($body, true);
    if (($project['product']['id'] ?? null) !== $productId) return ['ok' => false, 'reason' => 'made for another product'];
    if ($ref !== null && ($project['product']['ref'] ?? null) !== $ref) return ['ok' => false, 'reason' => 'made in another session'];
    if ($project['status'] !== 'submitted') return ['ok' => false, 'reason' => 'not finished'];
    return ['ok' => true, 'project' => $project];
}
```

* **`404`**: the project doesn't exist, or belongs to another Print.App account. Reject the line.
* **`product`**: compare it with the product being ordered and the reference you issued.
* **`status`**: `submitted` means the customer finished the design. `saved` means they saved it for later. `draft` means it was only autosaved.

> [!TIP]
>
> Verify at checkout, not inside the `app:saved` handler. The save reaches Print.App a moment after the event fires, so a check made in that same instant can get a `404`. If you must verify immediately, retry a `404` once after a second or two.

**4. Fetch the print file**

The project's `exports` hold its print files: `pdf`, `png`, `jpeg` and `tiff`. Download the file once the order is placed and keep your own copy. Files are rendered from the project's current state, so if the project is edited later, a later download would differ.

Export links work without a key, so treat them as confidential: don't publish them.

On WooCommerce, [Automate fulfilment from WooCommerce](https://print.app/docs/api-reference/guides/woocommerce-fulfilment.md) shows the whole step, from the paid order to your print partner.

## What this proves

* **A `200` proves the project belongs to your account.** The API only answers for your own projects.
* **`product` shows which product page the project was made on.** It stops a customer attaching someone else's project, a project from another store, or a project made for a different product.
* **`product` still travels through the customer's browser.** A customer who deliberately crafts a project with a forged `product` can pass this check. If print dimensions matter for pricing, also check the page size of the PDF you print.

## Looking projects up later

[`GET /v2/projects`](https://print.app/docs/api-reference/v2/projects/list) lists your projects, most recently saved first. It can filter by `status` and save time, which is useful for reconciling orders in bulk.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.