Reporting What You Sent

Note

Part of the Wholesaler Integrations guide. This step assumes you have already completed Registering an Available Wholesaler: receipts are submitted against a clinic’s wholesaler, which only exists once that clinic has connected you.

This endpoint records what you have sent: one line per product per batch on a delivery, with the batch number and expiry date the clinic needs for its inventory. A line carries a single batch number and expiry date, so a product you shipped from two batches is two lines, each with its own id and the quantity that came from that batch.

Each line is staged for review. Nothing moves into stock automatically, and accepting a batch is a manual step in the clinic’s review screen.

See Receive Batch Orders for the full customer-facing walkthrough of that screen: what clinic staff see after you submit, how they match a line you sent to an item in their own stock, and what accepting a batch does to their inventory.

Submitting receipt lines

curl -X POST \
  "https://[env.]provetcloud.com/<provet_id>/api/0.1/wholesaler/<wholesaler_pk>/receipts/" \
  -H "Authorization: Bearer <oauth_access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "receipt_lines": [
      {
        "id": "INV-202012164-1",
        "order_id": 5567,
        "invoice_number": "202012164",
        "invoice_date": "2026-08-27",
        "product_number": "ACM-001",
        "product_name": "Amoxicillin 250mg",
        "barcode": "6412345678901",
        "quantity": "12.00",
        "batch_number": "123456789",
        "expiry_date": "2027-12-31"
      }
    ]
  }'

wholesaler_pk is the per-clinic wholesaler.id you received in wholesaler.created and see again on every order.placed. The wholesaler scope is sufficient: no additional Provet permission is needed.

Each line carries its own invoice number and order reference, so one document can span several orders. Send a delivery note or an invoice, whichever you produce, but only one of them per shipment, or staff accepting both would double the stock.

A product shipped from more than one batch is sent as one line per batch. The lines repeat the product and the order, and differ in id, quantity, batch_number and expiry_date:

{
  "receipt_lines": [
    {
      "id": "INV-202012164-2a",
      "order_id": 5567,
      "invoice_number": "202012164",
      "product_number": "ACM-001",
      "quantity": "8.00",
      "batch_number": "123456789",
      "expiry_date": "2027-03-31"
    },
    {
      "id": "INV-202012164-2b",
      "order_id": 5567,
      "invoice_number": "202012164",
      "product_number": "ACM-001",
      "quantity": "4.00",
      "batch_number": "987654321",
      "expiry_date": "2027-06-30"
    }
  ]
}

Reusing one id for both would store the first batch and answer duplicate for the second, losing it silently.

Field

Type

Optional

Meaning

id

string

No

Your own identifier for this line, unique among the lines you send for this wholesaler. It is what makes a resubmission safe; see Resubmitting a line. Opaque to Provet: compose it however you like, up to 255 characters. Identifiers are compared without regard to case, so row-1 and ROW-1 are the same line.

quantity

string

No

Units sent, in the package units the order used, not dosable units. A decimal string with up to 9 whole digits and 3 decimal places. Zero is valid and says you have seen the order but shipped nothing yet; a negative quantity is not, since a receipt records what left your warehouse.

product_number

string

Yes*

Your product code, up to 63 characters. The same product code you would provide to the user when making it available to them via the catalog API

barcode

string

Yes*

Product barcode, up to 30 characters.

product_name

string

Yes

Shown to clinic staff when a line has to be matched by hand, so worth sending even when you expect a match. Up to 255 characters.

order_id

number

Yes

The order.id this line delivers against, from order.placed.

invoice_number

string

Yes

Your invoice or delivery note number, up to 63 characters. Used to link the line to an order when order_id is absent.

invoice_date

string

Yes

Calendar date on the invoice, YYYY-MM-DD.

batch_number

string

Yes

Batch identifier, up to 63 characters.

expiry_date

string

Yes

Use-before date from the packaging, YYYY-MM-DD.

* A product identifier is required, but a successful match is not. Every line must carry a product_number or a barcode; a line with neither is rejected. A line that matches no product is still stored and left for staff to match, which is how your code gets learned onto the product, so the first delivery of a product the clinic has not imported yet is expected to come back unmatched.

Dates are calendar dates. Send the date as it appears on the invoice or the packaging, without converting time zones. A value carrying a time or a UTC offset is stored as absent.

At most 500 lines per call. Split a larger submission across several calls: lines are deduplicated on their own id, so there is nothing to reconcile between them.

The response

{
  "lines_total": 1,
  "lines_accepted": 1,
  "lines_rejected": 0,
  "lines_duplicate": 0,
  "receipt_lines": [
    {
      "id": "INV-202012164-1",
      "status": "accepted",
      "order_id": 5567,
      "matched_item": true,
      "matched_by": "product_code"
    }
  ]
}

Field

Type

Meaning

id

string

The line id you submitted, echoed back. Provet does not return a storage identifier of its own.

status

string

accepted, rejected or duplicate.

reason

string

Why the line was rejected. Present only on a rejected line.

order_id

number / null

The order the line was actually linked to, which is not always the one you submitted: a line with no order_id can still resolve by invoice number, and an order that is not yours does not resolve at all.

matched_item

boolean

Whether the line resolved to a product in the clinic’s catalogue.

matched_by

string / null

product_code, barcode, or null when neither matched.

matched_by does not distinguish an unknown code from an ambiguous one where two products claim the same code: both come back as no match.

reason

Meaning

missing_product_identifier

The line carried neither a product_number nor a barcode.

invalid_quantity

quantity was absent, negative, or would not parse as a decimal within 9 whole digits and 3 decimal places.

missing_id

The line carried no id, so it could not be identified or deduplicated.

invalid_order_id

order_id was present but not an integer. Omit it rather than sending a string.

invalid_line

The line was not an object, or a value was longer than the field allows.

department_unresolved

The line linked to no order, and the id you posted to does not settle which department the goods are for. Only reachable for integrations predating per-credential-set ids. Send an order_id, or address the credential set for the department in question. See How a line is matched and filed.

Partial success is a 200

The status code describes the request, not its contents: you get a 200 however many lines were rejected, including all of them.

Check lines_rejected, not the status code. A 200 does not mean every line was stored.

A 400 means the request itself was unusable and nothing was stored: malformed JSON, a missing or empty receipt_lines array, or more than 500 lines.

Resubmitting a line

Your line id is the idempotency key. Sending the same id again for the same wholesaler creates nothing new and returns the original outcome with status: "duplicate", including the order_id and match result recorded the first time. Two different wholesalers may use the same id without colliding.

A line cannot be corrected or retracted once submitted. Resubmitting an id with different values returns the stored line and changes nothing, so a correction means a new line with a new id.

A timed-out request is therefore safe to retry: whatever landed comes back as duplicate, and whatever did not is created.

How a line is matched and filed

Products. Your product_number is tried first, against products the clinic has recorded as supplied by you, then the barcode. A barcode only matches a product you supply, so a barcode you have never shipped will not resolve even if the clinic stocks it.

Orders. order_id is used when you send one, and the invoice_number is matched against orders otherwise. Either way only your own orders are considered: a line naming an order placed with someone else is stored unlinked, with order_id: null. An invoice number is not unique (one document is expected to span several orders), so a reference matching more than one of yours also leaves the line unlinked. Send an order_id when you need the link to be certain.

Departments. A batch belongs to a department, but you don’t send one explicitly: Provet resolves it from the linked order first, and otherwise from the id you posted to. If that id is a department’s own credential set (see wholesaler.created), that settles it outright.

If it is the organization-wide credential set, nothing distinguishes one of the clinic’s departments from another, and a line with no order link is filed under the organization’s default department. On a clinic with several locations that is not necessarily the one the goods were delivered to, and staff at the other locations will not see the line at all — the review screen lists only the department they are working in. Send an order_id, or an invoice_number that matches one of your orders, whenever a line needs to reach the right location.

If your integration predates per-credential-set ids, Provet falls back to the wholesaler’s own department configuration instead. A wholesaler the clinic configured for more than one department then leaves an unlinked line with nothing to resolve it against, and it comes back rejected with department_unresolved.

Errors specific to receipts

Situation

Response

One or more lines could not be used

200: check lines_rejected and the per-line reason

An unlinked line with no way to settle its department

200 with reason: department_unresolved

receipt_lines missing, empty, or not an array

400

More than 500 receipt lines in one call

400

Wholesaler belongs to another integration

403 Token does not match this wholesaler.

Wholesaler id doesn’t exist, is archived, or isn’t an integration wholesaler

404 No Wholesaler matches the given query.

See the error reference for the integration-specific errors that can occur across every phase.