# Peptaura Laboratory Integration Guide

This guide is for independent laboratories that fulfill Peptaura group-testing
orders. It describes three integration surfaces:

1. an **optional outbound** HTTPS request that Peptaura sends **to** an
   endpoint you configure,
2. an **authenticated inbound** lab API (`/api/lab/v1`) your lab calls with its
   own Bearer credential, and
3. the authenticated `/lab` portal used to review orders and upload results.

Direction matters. There is **no unauthenticated public API**: the inbound
endpoints require a lab-scoped Bearer token and only ever expose your own
lab's orders, and the outbound direction is always Peptaura calling your
endpoint — never the reverse.

## Order flow at a glance

1. A group-testing pool completes funding and Peptaura pays your lab's payout
   wallet on Arbitrum.
2. The paid order appears in your authenticated dashboard at `/lab#orders`
   under a stable `GT-<order number>` reference. That dashboard is the
   authoritative order channel and works with no integration at all. The same
   reference also works against the inbound order lookup described below.
3. Only if you have saved a complete, enabled API configuration under
   `/lab#api-settings`, Peptaura additionally sends one outbound `POST` with
   the order details to your endpoint after the payout transaction is
   confirmed.
4. You test the physical vials and return each result either from
   `/lab#orders` (**Upload COA**) or through the inbound results endpoint.
   Participants are notified automatically once every ordered result group is
   complete.

## Credentials

`/lab#api-settings` manages two **independent** secrets:

- **Outbound endpoint key** — the write-only Bearer key Peptaura presents when
  calling your receiver. Peptaura stores it encrypted; you rotate it by saving
  a replacement.
- **Inbound API token** — the Bearer credential *your lab* presents when
  calling `/api/lab/v1`. Issue, rotate, or revoke it in API Settings. The
  token is displayed **once** when issued; store it in your own secret
  manager, because Peptaura keeps only a hash and can never show it again.
  Rotating invalidates the previous token, and revoking disables it
  immediately.

The two credentials are never interchangeable: the inbound token is never
sent to your endpoint, and the outbound endpoint key never authenticates a
call to `/api/lab/v1`.

## Inbound lab API (authenticated)

Base URL: `https://www.peptaura.com`. All requests need
`Authorization: Bearer $PEPTAURA_LAB_API_TOKEN`, where
`PEPTAURA_LAB_API_TOKEN` is the token issued in API Settings. Every response
is `Cache-Control: private, no-store`, errors are sanitized JSON of the form
`{ "success": false, "error": { "code": "...", "message": "..." } }`, and
submissions are bounded by a durable per-token rate limit that fails closed.
The examples below use shell environment variables — never paste real
credentials into source, tickets, or this document's placeholders.

### Look up an order

`GET /api/lab/v1/orders/{clientOrderReference}`

```bash
curl -sS \
  -H "Authorization: Bearer $PEPTAURA_LAB_API_TOKEN" \
  "$PEPTAURA_BASE_URL/api/lab/v1/orders/GT-417481428"
```

`{clientOrderReference}` is the `GT-<order number>` shown in `/lab#orders`.
It is only resolvable once the order is paid and eligible for your lab; any
other reference — malformed, unpaid, or belonging to another lab — returns
the same generic not-found error, so the route cannot be used to enumerate
orders.

The JSON response is `{ "success": true, "order": { ... } }`. The `order`
carries the reference, `status`, `submissionCondition`, `paidAt`,
`resultsCompletedAt`, `outsideOrder`, the ordered `compound`
(`name`/`dose`/`batchNumber`), `supplierName`, `labPaymentUsd`,
`labPaymentTxHash`, a `progress` summary (`completed`/`total`), and the frozen
canonical `tests` groups. Each `tests[]` entry carries `testType`,
`testTypes`, `testLabel`, `vialCount`, `sampleIdentifiers`, the `currentResult`
(`{ resultId, revision, reportDate, uploadedAt }` or `null`), and the
`expectedRevision` to echo back on submission — `0` when no result exists,
otherwise the current revision. The response never contains participant
names, emails, or other customer data.

```json
{
  "success": true,
  "order": {
    "clientOrderReference": "GT-417481428",
    "status": "EXECUTED",
    "submissionCondition": "AWAITING_RESULTS",
    "progress": { "completed": 0, "total": 1 },
    "tests": [
      {
        "testType": "ID_PURITY_DOSE",
        "testTypes": ["ID_PURITY_DOSE"],
        "testLabel": "ID, Purity, Dose",
        "vialCount": 1,
        "sampleIdentifiers": ["417481428-1"],
        "currentResult": null,
        "expectedRevision": 0
      }
    ]
  }
}
```

(Other order fields omitted for brevity — see `/openapi.json` for the full
schema.)

### Submit a result

`POST /api/lab/v1/orders/{clientOrderReference}/results`

```bash
curl -sS -X POST \
  -H "Authorization: Bearer $PEPTAURA_LAB_API_TOKEN" \
  -H "Idempotency-Key: $REPORT_IDEMPOTENCY_KEY" \
  -F "testType=ID_PURITY_DOSE" \
  -F "reportFile=@$REPORT_FILE" \
  -F "reportDate=$REPORT_DATE" \
  -F "expectedRevision=$EXPECTED_REVISION" \
  -F "correctionNote=$CORRECTION_NOTE" \
  "$PEPTAURA_BASE_URL/api/lab/v1/orders/GT-417481428/results"
```

| Field | Required | Rule |
| --- | --- | --- |
| `Idempotency-Key` (header) | yes | Unique per submission. Replays with the same key return the recorded result; reusing the key for a different payload is a conflict. |
| `testType` | yes | One of the order's canonical test-group identifiers as returned by the order lookup — a built-in type such as `ID_PURITY_DOSE` or an immutable `CUSTOM:<name>`. |
| `reportFile` | yes | PDF, PNG, or JPG up to 5 MB. The content type is verified from the file itself, not just the extension. |
| `reportDate` | yes | `YYYY-MM-DD`, a real calendar date, never in the future. |
| `expectedRevision` | yes | `0` for the first result on a test group, otherwise the group's current revision from the order lookup. A stale value is a conflict. |
| `correctionNote` | no | At most 2000 characters; the participant-facing note attached to a correction, like the portal's Replace COA note. |

On success the response is `{ "success": true, "result": { ... } }` where
`result` carries `resultId`, `testType`, `revision`, `completed`, `replaced`,
and `alreadyRecorded`. A newly recorded result returns `201`; replaying the
same `Idempotency-Key` (or a same-content no-op) returns `200` with the
recorded result unchanged.

```json
{
  "success": true,
  "result": {
    "resultId": "R-EXAMPLE-001",
    "testType": "ID_PURITY_DOSE",
    "revision": 1,
    "completed": false,
    "replaced": false,
    "alreadyRecorded": false
  }
}
```

Submit only to orders your lab owns. The token is authenticated at the start
of the request and revalidated again inside the database transaction that
records the result, so a token revoked or rotated — or a lab deactivated —
while an upload is in flight rejects the submission and records nothing.
Statuses to handle: `400` for malformed
requests or invalid fields; `401` for a missing, invalid, or revoked token,
including a token revoked or rotated between authentication and commit;
`404` for any reference your lab cannot resolve — including an order that has
stopped accepting results, which returns the same sanitized not-found; `409`
for a stale `expectedRevision` (`revision_conflict`) or an `Idempotency-Key`
reused with a different payload (`idempotency_conflict`). For
`revision_conflict`, fetch the order again and submit against its latest
`expectedRevision`. For `idempotency_conflict`, retry only the original
payload with its original key if you need to resolve an uncertain outcome; a
changed submission needs a new key and the latest `expectedRevision`. `413`
means an oversized
report file (`report_file_too_large`) or multipart body (`request_too_large`);
`429` when the per-token rate limit trips; `503` when the service is
temporarily unavailable. A recorded result is immediately public under the
same visibility rules as a portal upload.

## Outbound order delivery (optional)

### Endpoint requirements

- A public HTTPS `POST` endpoint that you control, for example
  `https://lims.example.com/peptaura/orders`.
- HTTPS only. URLs carrying embedded credentials or fragments are rejected,
  and private, loopback, link-local, reserved, or otherwise non-public targets
  are rejected before any request is sent.
- Redirects are not followed; answer directly from the configured URL.
- Requests time out after 15 seconds, and response bodies are read only up to
  32 KB.

### Request headers

| Header | Value |
| --- | --- |
| `Content-Type` | `application/json` |
| `Accept` | `application/json` |
| `Authorization` | `Bearer <your API key>` — the write-only key you saved in API Settings |
| `Idempotency-Key` | A stable per-order key, e.g. `peptaura-group-test:<order UUID>:lab-order:v1` |

### Example request (fully synthetic)

`POST https://lims.example.com/peptaura/orders`

```json
{
  "clientOrderReference": "GT-417481428",
  "txHash": "0xabababababababababababababababababababababababababababababababab",
  "tests": [
    {
      "id": "LIMS-ID_PURITY_DOSE",
      "vialCount": 1
    }
  ],
  "compounds": [
    {
      "name": "Retatrutide",
      "dose": "10 mg",
      "batchNumber": "TEST-BATCH-001"
    }
  ],
  "customers": [
    {
      "name": "Example Vendor (prepared for Peptaura)",
      "email": "lab-contact@example.com"
    }
  ]
}
```

Field notes:

- `clientOrderReference` — the same `GT-<order number>` shown in `/lab#orders`
  and in vial-sender emails. It is the human-facing order reference and carries
  no participant data.
- `txHash` — the confirmed Arbitrum transaction paying your lab. Verify it
  on-chain before acknowledging.
- `tests[].id` — the LIMS test identifiers you mapped in API Settings;
  `vialCount` is the number of physical vials allocated to that test (1–25 per
  entry).
- `compounds` and `customers` always contain exactly one entry each. `dose`
  and `batchNumber` may be `null` when the pool has no value. `customers[0]` is
  the Peptaura-facing order contact, not a participant. The default name is
  `<Public Vendor Name> (prepared for Peptaura)` or
  `<Custom Vendor Name> (prepared for Peptaura)`; the default email is
  `support@peptaura.com`.

### Expected response

Return any 2xx status with a JSON body containing `success: true`:

```json
{
  "success": true
}
```

Any other outcome — a non-2xx status, a non-JSON body, or JSON without
`success: true` — counts as not acknowledged. Extra response fields are
ignored and are not persisted.

### Retries and idempotency

- An order that is not acknowledged stays pending and is retried later with
  the **same** `Idempotency-Key`.
- Treat repeated requests carrying the same key as the same order; do not
  create duplicate work.
- A missing acknowledgement never blocks the order — it becomes visible in
  `/lab#orders` once pool execution completes, even if the optional
  delivery fails.

### Before you acknowledge

Confirm that `txHash` is the settled payment for the expected payout amount,
that the test IDs and vial counts match the order, and that the compound is
one your lab supports.

## Submitting results through the portal

The authenticated lab dashboard remains a fully supported result path
alongside the inbound API.

1. Sign in and open `/lab#orders`.
2. Expand the paid order (`GT-<order number>`) and choose **Upload COA** on the
   ordered test group.
3. Attach the report file and its **report date** (`YYYY-MM-DD`, never in the
   future). Accepted files are PDF, PNG, or JPG up to 5 MB; the content type is
   verified from the file itself, not just the extension.
4. Submit. Each upload is handled by an internal server action that re-verifies
   your authenticated lab identity and that the order belongs to your lab
   before anything is stored.
5. The result appears on the public pool page immediately. To correct a result,
   use **Replace COA** on the same test group with an optional note for
   participants — or send the corrected report through the inbound endpoint
   with the current `expectedRevision`.

## Testing your endpoint

`/lab#api-settings` includes a request tester. Its payload editor starts from
the canonical example and stays editable; each send goes to your saved
endpoint with a fresh idempotency key and shows the exact HTTP status and
bounded response body. Test sends create no Peptaura orders and do not
require external delivery to be enabled, but they still POST the payload to
your endpoint, so they can create real work in a receiving LIMS. Prefer an
isolated or staging endpoint while testing, or recognize test requests by
their `peptaura-lab-api-test:` idempotency-key prefix.

## Summary

| Direction | Channel |
| --- | --- |
| Peptaura → lab (order copy) | Optional outbound `POST` to your public HTTPS endpoint, only after the lab payout is confirmed |
| Lab → Peptaura (order lookup) | `GET /api/lab/v1/orders/{clientOrderReference}` with your lab-scoped Bearer token |
| Lab → Peptaura (results) | `POST /api/lab/v1/orders/{clientOrderReference}/results` (multipart, idempotent), or the authenticated `/lab#orders` **Upload COA** flow |
