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

# Receipts

> Cryptographically signed, transparency-log-anchored receipts for any event

## What Are Receipts?

Receipts are signed, immutable records of discrete events — payments, security incidents, compliance checks, deliveries, and more. Unlike logs or audit trails, every receipt is:

* **Cryptographically signed** with your issuer key (Ed25519 or ES256)
* **Anchored in the transparency log** with a Merkle inclusion proof
* **Offline-verifiable** via proof bundle export
* **Idempotent** — the same event always produces the same receipt

## Receipt types

Platform-defined receipt types are available to all tenants. Each type has a JSON Schema that the `payload` field is validated against before signing.

| Type                     | Use Case                              |
| ------------------------ | ------------------------------------- |
| `payment_receipt`        | Payments, invoices, refunds           |
| `security_event_receipt` | Auth events, key rotations, anomalies |
| `delivery_receipt`       | Document delivery, notifications      |
| `compliance_receipt`     | KYC/AML checks, regulatory evidence   |
| `custom_receipt`         | Any other event with open schema      |

### Discovering available types

Call [`GET /v1/receipt-types`](/api-reference/receipts/list-types) to list every type your tenant can use, or [`GET /v1/receipt-types/{name}`](/api-reference/receipts/get-type) to fetch a single type and inspect its JSON Schema.

<CodeGroup>
  ```typescript JavaScript theme={null}
  const types = await client.receipts.listTypes();

  // Inspect a specific type's schema
  const paymentType = await client.receipts.getType("payment_receipt");
  console.log(paymentType.schema.required); // ["amount", "currency", "provider", ...]
  ```

  ```go Go theme={null}
  types, _ := client.Receipts.ListTypes(ctx)
  for _, t := range types.Items {
      fmt.Println(t.Name, t.Version)
  }

  // Pin a specific version
  rt, _ := client.Receipts.GetType(ctx, "payment_receipt@1.0.0")
  ```
</CodeGroup>

## Minting a receipt

<Steps>
  <Step title="Choose a receipt type">
    Call `GET /v1/receipt-types` to list available types and inspect the schema for required fields.
  </Step>

  <Step title="Build your payload">
    Construct the `payload` object matching the required fields for your receipt type. For `payment_receipt`, you need `amount`, `currency`, `provider`, `provider_reference`, and `subject`.
  </Step>

  <Step title="Mint via API">
    ```bash theme={null}
    curl -X POST https://api.truthlocks.com/v1/receipts \
      -H "X-API-Key: tl_live_..." \
      -H "Idempotency-Key: $(uuidgen)" \
      -H "Content-Type: application/json" \
      -d '{
        "issuer_id": "<your-issuer-id>",
        "kid": "ed-key-2026",
        "alg": "Ed25519",
        "receipt_type": "payment_receipt",
        "subject": "customer@example.com",
        "payload": {
          "amount": 5000,
          "currency": "USD",
          "provider": "stripe",
          "provider_reference": "ch_3Px..."
        }
      }'
    ```
  </Step>

  <Step title="Store the receipt_id">
    The response includes the `receipt_id` and `log.leaf_index`. Store both for future verification.
  </Step>
</Steps>

## SDK Examples

<CodeGroup>
  ```typescript JavaScript theme={null}
  const receipt = await client.receipts.mint({
    issuer_id: 'your-issuer-id',
    kid: 'ed-key-2026',
    alg: 'Ed25519',
    receipt_type: 'payment_receipt',
    subject: 'customer@example.com',
    payload: {
      amount: 5000,
      currency: 'USD',
      provider: 'stripe',
      provider_reference: 'ch_3Px...'
    }
  });

  console.log(receipt.receipt_id, receipt.log.leaf_index);
  ```

  ```go Go theme={null}
  receipt, err := client.Receipts.Mint(ctx, &truthlock.MintReceiptRequest{
      IssuerID:    "your-issuer-id",
      KID:         "ed-key-2026",
      Alg:         "Ed25519",
      ReceiptType: "payment_receipt",
      Subject:     "customer@example.com",
      Payload: map[string]interface{}{
          "amount":             5000,
          "currency":           "USD",
          "provider":           "stripe",
          "provider_reference": "ch_3Px...",
      },
  })
  ```
</CodeGroup>

### Listing receipts

<CodeGroup>
  ```typescript JavaScript theme={null}
  const receipts = await client.receipts.list({
    receipt_type: "payment_receipt",
    status: "active",
    limit: 20,
  });
  ```

  ```go Go theme={null}
  receipts, err := client.Receipts.List(ctx, &truthlock.ListReceiptsFilter{
      ReceiptType: "payment_receipt",
      Status:      "active",
      Limit:       20,
  })
  ```
</CodeGroup>

### Retrieving a receipt

<CodeGroup>
  ```typescript JavaScript theme={null}
  const receipt = await client.receipts.get("receipt-uuid");
  ```

  ```go Go theme={null}
  receipt, err := client.Receipts.Get(ctx, "receipt-uuid")
  ```
</CodeGroup>

## Webhook events

Subscribe to receipt events by configuring a webhook endpoint in the console:

| Event             | Triggered When                   |
| ----------------- | -------------------------------- |
| `receipt.created` | A receipt is successfully minted |
| `receipt.revoked` | A receipt is revoked             |

## Revoking a receipt

Revocation is permanent and recorded in the transparency log. Receipts with status `revoked` remain queryable and their revocation timestamp is included in proof bundles. You can include an optional `reason` to record why the receipt was revoked.

```bash theme={null}
curl -X POST https://api.truthlocks.com/v1/receipts/{id}/revoke \
  -H "X-API-Key: tl_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Duplicate charge refunded"}'
```

<CodeGroup>
  ```typescript JavaScript theme={null}
  await client.receipts.revoke("receipt-uuid", {
    reason: "Duplicate charge refunded",
  });
  ```

  ```go Go theme={null}
  _, err := client.Receipts.Revoke(ctx, "receipt-uuid", &truthlock.RevokeReceiptRequest{
      Reason: "Duplicate charge refunded",
  })
  ```
</CodeGroup>

## Verifying a receipt

Use the verify endpoint to check a receipt's cryptographic validity and current status. The API checks the signature, key status, and revocation state, then returns a verdict.

```bash theme={null}
curl -X POST https://api.truthlocks.com/v1/receipts/verify \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "receipt_id": "receipt-uuid" }'
```

<CodeGroup>
  ```typescript JavaScript theme={null}
  const result = await client.receipts.verify("receipt-uuid");

  if (result.verdict === "VALID") {
    console.log("Receipt is valid");
  }
  ```

  ```go Go theme={null}
  result, err := client.Receipts.Verify(ctx, "receipt-uuid")
  if result.Verdict == "VALID" {
      fmt.Println("Receipt is valid")
  }
  ```

  ```python Python theme={null}
  result = client.receipts.verify("receipt-uuid")

  if result["verdict"] == "VALID":
      print("Receipt is valid")
  ```
</CodeGroup>

### Verdicts

| Verdict             | Meaning                                                   |
| ------------------- | --------------------------------------------------------- |
| `VALID`             | Signature is valid, key is active, receipt is not revoked |
| `REVOKED`           | Receipt has been explicitly revoked                       |
| `INVALID_SIGNATURE` | Cryptographic signature verification failed               |
| `KEY_COMPROMISED`   | Signing key was marked compromised at or before issuance  |
| `KEY_INACTIVE`      | Signing key is no longer active                           |
| `NOT_FOUND`         | Receipt not found for this tenant                         |

## Proof bundles

A proof bundle is a self-contained package for offline verification. It includes the signed receipt envelope, Merkle inclusion proof from the transparency log, and the issuer key snapshot at issuance time.

After downloading a proof bundle, you can verify the receipt without making any further API calls.

<CodeGroup>
  ```typescript JavaScript theme={null}
  const bundle = await client.receipts.getProofBundle("receipt-uuid");

  console.log(bundle.bundle_version);          // "receipt-v1"
  console.log(bundle.transparency_log);        // Merkle inclusion proof
  ```

  ```go Go theme={null}
  bundle, err := client.Receipts.GetProofBundle(ctx, "receipt-uuid")
  ```

  ```python Python theme={null}
  bundle = client.receipts.get_proof_bundle("receipt-uuid")
  ```
</CodeGroup>

See the [proof bundle API reference](/api-reference/receipts/proof-bundle) for the full bundle structure.

<Tip>
  You can download proof bundles directly from the console. Open a receipt's detail page and click **Download Proof Bundle**. See [downloading in the console](#downloading-a-proof-bundle).
</Tip>

## Searching receipts

Search across your receipts using full-text search and faceted filters. The search index supports queries against payload content, and you can narrow results by type, status, date range, or indexed payload fields.

```bash theme={null}
curl -X POST https://api.truthlocks.com/v1/receipts/search \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "q": "stripe",
    "receipt_type": "payment_receipt",
    "from_date": "2026-01-01T00:00:00Z",
    "limit": 20
  }'
```

<CodeGroup>
  ```typescript JavaScript theme={null}
  const results = await client.receipts.search({
    q: "stripe",
    receipt_type: "payment_receipt",
    from_date: "2026-01-01T00:00:00Z",
    limit: 20,
  });

  for (const r of results.items) {
    console.log(r.receipt_id, r.receipt_type);
  }
  ```

  ```go Go theme={null}
  results, err := client.Receipts.Search(ctx, &truthlock.SearchReceiptsQuery{
      Q:           "stripe",
      ReceiptType: "payment_receipt",
      FromDate:    "2026-01-01T00:00:00Z",
      Limit:       20,
  })
  ```

  ```python Python theme={null}
  results = client.receipts.search(
      q="stripe",
      receipt_type="payment_receipt",
      from_date="2026-01-01T00:00:00Z",
      limit=20,
  )
  ```
</CodeGroup>

### Search filters

| Field          | Type     | Description                                      |
| -------------- | -------- | ------------------------------------------------ |
| `q`            | string   | Full-text search across payload content          |
| `receipt_type` | string   | Filter by type name                              |
| `status`       | string   | `active`, `revoked`, `superseded`, or `redacted` |
| `issuer_id`    | UUID     | Filter by issuer                                 |
| `from_date`    | RFC 3339 | Start of date range                              |
| `to_date`      | RFC 3339 | End of date range                                |
| `index_key`    | string   | Indexed payload field name                       |
| `index_value`  | string   | Value to match for `index_key`                   |
| `limit`        | integer  | Max results (1–100, default 20)                  |
| `offset`       | integer  | Pagination offset                                |

<Tip>
  You can also search receipts from the console without writing code. Open **Receipts** in the sidebar and use the search bar and filter controls. See [searching in the console](#searching-receipts-1).
</Tip>

## Exporting receipts

Queue an asynchronous bulk export of receipts as JSON or CSV. The API returns immediately with a job ID. Poll the export status endpoint until `status` is `complete`, then download from the pre-signed URL.

<CodeGroup>
  ```typescript JavaScript theme={null}
  // Start an export
  const job = await client.receipts.export({
    format: "json",
    filters: {
      receipt_type: "payment_receipt",
      status: "active",
    },
  });

  console.log(job.id, job.status); // "pending"

  // Poll for completion
  const completed = await client.receipts.getExport(job.id);
  if (completed.status === "complete") {
    console.log("Download:", completed.download_url);
  }

  // List all export jobs
  const exports = await client.receipts.listExports();
  ```

  ```go Go theme={null}
  job, err := client.Receipts.Export(ctx, &truthlock.CreateExportRequest{
      Format: "json",
      Filters: map[string]interface{}{
          "receipt_type": "payment_receipt",
          "status":       "active",
      },
  })

  // Poll for completion
  completed, err := client.Receipts.GetExport(ctx, job.ID)
  ```

  ```python Python theme={null}
  job = client.receipts.export(
      format="json",
      filters={"receipt_type": "payment_receipt", "status": "active"},
  )

  # Poll for completion
  completed = client.receipts.get_export(job["id"])
  if completed["status"] == "complete":
      print("Download:", completed["download_url"])
  ```
</CodeGroup>

### Export status values

| Status     | Meaning                            |
| ---------- | ---------------------------------- |
| `pending`  | Queued, not yet started            |
| `running`  | Processing records                 |
| `complete` | Done — `download_url` is available |
| `failed`   | Error — see `error_message`        |

<Tip>
  You can queue exports and track their progress from the console. Open **Receipts**, click **Export**, and choose CSV or JSON. A status banner tracks the job until the download is ready. See [exporting in the console](#exporting-receipts-1).
</Tip>

## Redacting receipts

Redaction permanently removes PII from a receipt's payload while preserving the cryptographic proof. The receipt's signature, transparency log entry, and Merkle inclusion proof remain intact — only the `payload_json` is replaced with a redaction marker.

Use this for GDPR right-to-erasure requests on receipts containing personal data.

<CodeGroup>
  ```typescript JavaScript theme={null}
  await client.receipts.redact("receipt-uuid");
  ```

  ```go Go theme={null}
  _, err := client.Receipts.Redact(ctx, "receipt-uuid")
  ```

  ```python Python theme={null}
  client.receipts.redact("receipt-uuid")
  ```
</CodeGroup>

<Warning>
  Redaction is permanent. The original payload cannot be restored. The cryptographic proof remains valid for audit purposes.
</Warning>

After redaction:

* `status` changes to `redacted`
* `payload_json` is replaced with `{"redacted": true, "redacted_by": "tenant_request"}`
* A `RECEIPT_REDACT` event is anchored in the transparency log
* All other fields (signature, log proof, receipt type) are preserved

## Managing receipts in the console

You can manage the full receipts lifecycle from the console without writing any code. Open **Receipts** in the console sidebar to access the following features.

### Searching receipts

The receipts list page includes a search bar and filters. Type a keyword to search across receipt payloads, or use the filter controls to narrow results by receipt type, status, issuer, or date range. Results update in real time as you refine your query.

### Exporting receipts

Click **Export** from the receipts list to queue a bulk export as CSV or JSON. A status banner appears at the top of the page tracking the job through `pending`, `running`, and `complete` states. Once the export finishes, click the banner to download the file. You can also monitor export jobs from the API using `GET /v1/receipt-exports/{id}` — see [export receipts](/api-reference/receipts/export).

### Downloading a proof bundle

Open any receipt's detail page and click **Download Proof Bundle** to save a self-contained verification package. The bundle includes the signed receipt envelope, Merkle inclusion proof, and issuer key snapshot — everything needed for [offline verification](#proof-bundles).

### Redacting a receipt

From any active receipt's detail page, click **Redact** to permanently remove personally identifiable information from the payload. A confirmation dialog explains that redaction is irreversible before you proceed. Redaction is only available for receipts with `active` status. After redaction, the cryptographic proof and transparency log entry are preserved — only the payload content is removed. See [redacting receipts](#redacting-receipts) for details on what changes.

## Retention policy

Receipts are automatically cleaned up based on the retention policy set at mint time. A background worker sweeps expired receipts on a regular schedule.

Set `retention_policy` when minting:

| Policy      | Duration | Description                                          |
| ----------- | -------- | ---------------------------------------------------- |
| `standard`  | 2 years  | Default. Suitable for most transactional receipts.   |
| `extended`  | 7 years  | For compliance use cases requiring longer retention. |
| `permanent` | Never    | Never automatically deleted.                         |

Receipts approaching their retention deadline can be redacted instead of deleted if you need to preserve the cryptographic proof while removing payload data.

## Next steps

<CardGroup cols={2}>
  <Card title="Proof bundle specification" icon="file-certificate" href="/specification/proof-bundle">
    Full structure and offline verification steps for proof bundles.
  </Card>

  <Card title="Receipts API reference" icon="code" href="/api-reference/receipts/mint">
    Complete API reference for minting, searching, exporting, and redacting receipts.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/guides/webhooks">
    Subscribe to receipt events and process them in real time.
  </Card>

  <Card title="Issuance policies" icon="shield-check" href="/guides/issuance-policies">
    Enforce approval workflows and constraints on receipt minting.
  </Card>
</CardGroup>
