> ## 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`](/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](/docs/api-reference/v2/authentication). The answer tells you whether the project is yours and which product it was made for.

<Steps>
  <Step title="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.
  </Step>

  <Step title="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 }),
        });
    });
    ```
  </Step>

  <Step title="Verify on your server">
    Before accepting the order, fetch the project:

    <CodeGroup>
      ```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];
      }
      ```
    </CodeGroup>

    * **`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.
    </Tip>
  </Step>

  <Step title="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](/docs/api-reference/guides/woocommerce-fulfilment) shows the whole step, from the paid order to your print partner.
  </Step>
</Steps>

## 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`](/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.