---
title: "Generate a batch's Certificate of Analysis (COA) PDF"
method: POST
path: "/public/v1/batches/{id}/primary-test-result/pdf"
tags: ["Batch"]
---

# Generate a batch's Certificate of Analysis (COA) PDF

`POST /public/v1/batches/{id}/primary-test-result/pdf`

## Request

- Base URL: `https://app.distru.com`
- URL: `https://app.distru.com/public/v1/batches/{id}/primary-test-result/pdf`
- Auth: HTTP bearer

Get the Certificate of Analysis (COA) PDF for a single batch and download it directly, get a signed link to it, or email it (see `format`). The PDF is the batch's designated primary test result (the lab result flagged as primary among the batch's test results). Returns 404 when the batch has no primary test result, or that result has no attached file, with the message `This batch has no lab results attached`, which tells it apart from an unknown ID (`Not Found`). A deleted batch returns `Not Found`. This serves a document already stored in Distru; it does not re-run or fetch anything from a lab or a state traceability system.

The same permissions that govern downloading this PDF in the Distru web app apply to this endpoint; when `format` is `email`, the web app's permission for emailing it applies instead (documents the web app cannot email keep the download permission).

`format` selects how the generated PDF is delivered and has three modes: `binary` returns the raw PDF bytes as the response body (`Content-Type: application/pdf`); `url` returns a JSON envelope `{"data": {"url": ..., "expires_datetime": ...}}` with a temporary signed download URL; `email` sends the PDF to the addresses in `email_addresses` and returns a JSON envelope `{"data": {"emailed_to": [...]}}`. `format` is required, but for backward compatibility omitting it defaults to `binary`.

All PDF download endpoints share a single, combined rate limit for your account: 20 requests per minute and 1000 per day in total across every PDF endpoint (not per endpoint). Requests over the limit return 429 with a Retry-After header, and only successful downloads count toward it.

## Path parameters

- `id` string, required

## Request body

- object
  - `format` 'binary' | 'url' | 'email', required: How the generated PDF is delivered. One of `binary` — the raw PDF bytes as the response body (`Content-Type: application/pdf`); `url` — a JSON body with a short-lived signed download URL (see the [PdfResponse](#model-pdfresponse) response); or `email` — emails the PDF to the addresses in `email_addresses` and returns a JSON body listing who it was emailed to. Required, but omitting it defaults to `binary` for backward compatibility.
  - `email_addresses` string[]: Recipient email addresses, used only when `format` is `email` (required in that case, ignored otherwise). An entry may hold several comma-separated addresses; spaces, blank entries and repeats are dropped, and `emailed_to` lists the addresses actually emailed. At least one address is required and every address must be valid, otherwise the request is rejected with a 400. At most 200 addresses may be given, counted after that cleanup.

## Response `200`

When `format` is `binary` (or omitted), the raw PDF file (`Content-Type: application/pdf`). When `format` is `url`, a JSON body with a temporary signed download URL; when `format` is `email`, a JSON body listing the addresses the PDF was emailed to (see the PdfResponse schema).

## Other responses

- `400`: Invalid parameters
- `401`: Missing or invalid API token
- `403`: The API token lacks the required permission, or team restrictions hide the record
- `404`: Not Found, or the batch has no lab results attached (the message says which)
- `429`: Too Many Requests — the account's shared PDF download rate limit (20/minute, 1000/day across all PDF endpoints) was exceeded. Retry after the period in the Retry-After header.

---

[API](https://skmtc.dev/distru/apis/distru-api.md) - [All operations](https://skmtc.dev/distru/apis/distru-api/llms.txt) - [OpenAPI document](https://skmtc.dev/distru/apis/distru-api/revisions/9d851a88e82f?raw)
