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

# Download Receipt

> Download the proof of payment for a successful transaction

## Overview

Streams the biller's receipt for a paid bill. This is the only Bill Payment endpoint that does not return JSON on success — the response body is the raw file.

Receipts exist only for transactions whose `status` is `SUCCESS`. Anything else — a transaction still in flight, a failed one, a refunded one, one that belongs to another partner — answers the ordinary JSON error envelope with `404 NOT_FOUND`.

<Note>
  The `receiptUrl` on a successful transaction is exactly this endpoint. It still needs your `X-Access-Token` header, so it is **not** a link you can give to a customer or embed in an email. Download the bytes, store them, and serve them from your own system.
</Note>

## Path Parameters

<ParamField path="transactionId" type="string" required>
  The transaction identifier. A 24-character lowercase hexadecimal string, from [Get Transaction by ID](/en/api-reference/bill-payment/check-by-id) or from any list.
</ParamField>

## Response

On success the body is the file itself. Read the response headers to know what you received.

<ResponseField name="Content-Type" type="header" required>
  The media type of the file: `application/pdf`, `image/png` or `image/jpeg`. Anything we cannot identify is served as `application/octet-stream`.

  **Always branch on this header** rather than assuming a PDF.
</ResponseField>

<ResponseField name="Content-Disposition" type="header" required>
  `attachment; filename="..."` — the suggested file name, with the extension that matches `Content-Type`.
</ResponseField>

<ResponseField name="Content-Length" type="header" required>
  The size of the file in bytes.
</ResponseField>

<ResponseField name="Cache-Control" type="header" required>
  `private, max-age=86400`. The receipt belongs to one partner; never cache it in a shared or public cache.
</ResponseField>

<ResponseField name="X-Content-Type-Options" type="header" required>
  `nosniff`.
</ResponseField>

<ResponseField name="X-Request-Id" type="header" required>
  Correlation identifier for this request. There is no JSON body on success, so this header is the only place it appears — log it.
</ResponseField>

## Examples

Each sample checks the status before writing anything, and takes the file extension from the response rather than assuming one.

<CodeGroup>
  ```bash cURL theme={null}
  # -J -O writes the file under the name the server suggests
  curl https://billapi.oneclickdz.com/v3/bills/transactions/68b2f4c1a7d3e9f204c81a55/receipt \
    -H "X-Access-Token: YOUR_API_KEY" \
    --fail \
    --remote-header-name --remote-name
  ```

  ```javascript Node.js theme={null}
  import { writeFile } from "node:fs/promises";

  const transactionId = "68b2f4c1a7d3e9f204c81a55";

  const response = await fetch(
    `https://billapi.oneclickdz.com/v3/bills/transactions/${transactionId}/receipt`,
    { headers: { "X-Access-Token": process.env.ONECLICKDZ_API_KEY } },
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`${error.error.code}: ${error.error.message}`);
  }

  const contentType = response.headers.get("content-type");
  const extension = contentType.includes("pdf")
    ? "pdf"
    : contentType.includes("png")
      ? "png"
      : contentType.includes("jpeg")
        ? "jpg"
        : "bin";

  const bytes = Buffer.from(await response.arrayBuffer());
  await writeFile(`receipt-${transactionId}.${extension}`, bytes);

  console.log(response.headers.get("x-request-id"), bytes.length);
  ```

  ```python Python theme={null}
  import os
  import requests

  transaction_id = '68b2f4c1a7d3e9f204c81a55'

  response = requests.get(
      f'https://billapi.oneclickdz.com/v3/bills/transactions/{transaction_id}/receipt',
      headers={'X-Access-Token': os.getenv('ONECLICKDZ_API_KEY')}
  )

  if response.status_code != 200:
      error = response.json()
      raise RuntimeError(f"{error['error']['code']}: {error['error']['message']}")

  content_type = response.headers.get('Content-Type', '')
  extension = (
      'pdf' if 'pdf' in content_type
      else 'png' if 'png' in content_type
      else 'jpg' if 'jpeg' in content_type
      else 'bin'
  )

  with open(f'receipt-{transaction_id}.{extension}', 'wb') as handle:
      handle.write(response.content)

  print(response.headers.get('X-Request-Id'), len(response.content))
  ```

  ```php PHP theme={null}
  <?php
  $transactionId = '68b2f4c1a7d3e9f204c81a55';

  $ch = curl_init("https://billapi.oneclickdz.com/v3/bills/transactions/$transactionId/receipt");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-Access-Token: ' . getenv('ONECLICKDZ_API_KEY')
  ]);

  $bytes  = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $type   = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
  curl_close($ch);

  if ($status !== 200) {
      $error = json_decode($bytes, true);
      throw new Exception($error['error']['code'] . ': ' . $error['error']['message']);
  }

  $extension = str_contains($type, 'pdf') ? 'pdf'
      : (str_contains($type, 'png') ? 'png'
      : (str_contains($type, 'jpeg') ? 'jpg' : 'bin'));

  file_put_contents("receipt-$transactionId.$extension", $bytes);

  echo strlen($bytes);
  ?>
  ```
</CodeGroup>

### Success Response

The body is binary. The headers look like this:

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 48211
Content-Disposition: attachment; filename="receipt-68b2f4c1a7d3e9f204c81a55.pdf"
Cache-Control: private, max-age=86400
X-Content-Type-Options: nosniff
X-Request-Id: req_9f3a1c72e0b84d51aB3xZq07
```

## Error Responses

Errors are returned as JSON, in the same envelope as every other endpoint.

<AccordionGroup>
  <Accordion title="401 — Missing or invalid access token">
    **The key was absent or rejected.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INVALID_ACCESS_TOKEN",
        "message": "The provided access token is invalid."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** verify the key with [Validate API Key](/en/api-reference/bill-payment/validate-key).
  </Accordion>

  <Accordion title="404 — No receipt available">
    **Either the transaction is not yours in this environment, or it has no receipt.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "NOT_FOUND",
        "message": "Receipt not available for this transaction."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    A receipt exists only once a payment has reached `SUCCESS`. Downloading while the transaction is `PROCESSING` returns this, and so does a `FAILED` or `REFUNDED` transaction — neither has a receipt because no payment was completed.

    **What to do:** read the transaction first, and download only when `status` is `SUCCESS` and `receiptUrl` is present.
  </Accordion>

  <Accordion title="503 — Authentication or service unavailable">
    **We could not verify your key in time, or the API is in planned maintenance.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "AUTH_UNAVAILABLE",
        "message": "Authentication is temporarily unavailable. Please retry shortly."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** honour the `Retry-After` header (5 seconds) and retry. Downloading is safe to repeat.
  </Accordion>

  <Accordion title="500 — Internal error">
    **The receipt could not be read.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INTERNAL_ERROR",
        "message": "An unexpected error occurred."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **What to do:** retry once, then contact support with the `requestId`. The payment itself is unaffected — the transaction is still `SUCCESS`.
  </Accordion>
</AccordionGroup>

## Downloading at the right moment

Fetch the receipt as soon as a payment reaches `SUCCESS`, in the same step that records the success on your side.

```javascript theme={null}
async function settle(transactionId) {
  const response = await fetch(
    `https://billapi.oneclickdz.com/v3/bills/transactions/${transactionId}`,
    { headers: { "X-Access-Token": process.env.ONECLICKDZ_API_KEY } },
  );

  const body = await response.json();
  if (!body.success) throw new Error(body.error.code);

  const transaction = body.data;
  if (transaction.status !== "SUCCESS") return transaction.status;

  // operationId is the biller's proof; the receipt is the document for it.
  await saveOrder({
    transactionId: transaction.transactionId,
    operationId: transaction.operationId,
    total: transaction.total,
  });

  await downloadReceipt(transactionId);

  return "SUCCESS";
}
```

<Warning>
  Store the bytes, not the URL. `receiptUrl` requires your API key, so a stored link is useless to your customer and dangerous to share.
</Warning>

## Best Practices

<CardGroup cols={2}>
  <Card title="Download once, store forever" icon="box-archive">
    Keep the receipt with your own order record. It is the document a customer dispute is settled with.
  </Card>

  <Card title="Read Content-Type" icon="file-lines">
    The receipt is a PDF or an image depending on the biller. Take the extension from the header.
  </Card>

  <Card title="Never expose the URL" icon="shield-halved">
    Serve receipts from your own system, behind your own authentication.
  </Card>

  <Card title="Pair it with operationId" icon="receipt">
    The receipt is the document; `operationId` is the reference. Store both.
  </Card>
</CardGroup>

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Get Transaction by ID" icon="id-card" href="/en/api-reference/bill-payment/check-by-id">
    Where `receiptUrl` and `operationId` appear
  </Card>

  <Card title="List Transactions" icon="list" href="/en/api-reference/bill-payment/list-transactions">
    Find everything paid in a period
  </Card>

  <Card title="Pay a Bill" icon="money-bill-transfer" href="/en/api-reference/bill-payment/pay-bill">
    The payment that produced it
  </Card>

  <Card title="Receipts and Reconciliation" icon="scale-balanced" href="/en/bill-payment-guides/5-receipts-and-reconciliation">
    Storing and reconciling receipts
  </Card>
</CardGroup>
