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

# Merchant invoices

> List and read store invoices with a Bearer API key.

Base URL: `https://dash.joinmarkt.com`

```txt theme={null}
Authorization: Bearer mk_live_...
Accept: application/json
```

Invoices are the same order rows as [merchant orders](https://docs.joinmarkt.com/api-reference/merchant-orders.md). There is no separate invoice table. `invoiceId` in the response is the order’s stored `orderId`. The path `{invoiceId}` is trimmed, then looked up as the row `id` or the stored `orderId`. The trimmed value is tried, then the same value uppercased when it differs. The order `publicId` column is not a lookup key.

Scope: `invoice:read`. Dashboard ceiling: `viewOrders`. The user must be able to access `{storeId}`. Store scope `SELECTED` must include that shop. `ALL_ACCESSIBLE` does not add a shop list. Access mode `RESTRICTED` must include `invoice:read`. Access mode `FULL` skips the scope list and still requires `viewOrders`.

Limiter: 120 requests per 60 seconds per client IP. `429` `Too many requests` is returned before the key is checked.

Successful bodies are the objects below. Errors:

```json theme={null}
{ "success": false, "error": "Forbidden", "code": "FORBIDDEN" }
```

`code` is `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `RATE_LIMITED`, or `INTERNAL_ERROR`. A `500` also includes `errorId`.

## `GET /api/merchant/v1/stores/{storeId}/invoices`

Query parameters this route reads are the merchant order list parameters. Other query keys are ignored.

| Param | Rule |
| - | - |
| `limit` | Integer 1–100. Default `50`. |
| `cursor` | Opaque string, 1–500 characters, from `nextCursor`. |
| `status` | Exact match: `pending`, `paid`, `processing`, `completed`, `cancelled`, `canceled`, `failed`, `expired`, `refunded`. |
| `from` | Order `createdAt` greater than or equal to this date. |
| `to` | Order `createdAt` less than or equal to this date. |

Omitted or empty `limit`, `cursor`, `status`, `from`, and `to` are left unset (`limit` defaults to 50). An invalid value returns `400`. The message is the Zod issues joined with `; ` (`path: message` when the issue has a path). An unreadable cursor returns `400` with `error` `Invalid cursor`.

Rows are newest first (`createdAt` descending, then `id` descending). The handler loads `limit + 1` rows. `hasMore` is true when an extra row exists. `nextCursor` is set only then, and is the base64url of `{last.createdAt ISO}|{last.id}`. `nextCursor` is `null` when `hasMore` is false.

Each invoice is mapped from the order list item. `keyCount` is not copied.

```json theme={null}
{
  "invoices": [
    {
      "id": "clxyzorder",
      "invoiceId": "ORD-2345-6789-ABCD-EFGH-JKMN",
      "orderId": "ORD-2345-6789-ABCD-EFGH-JKMN",
      "storeId": "cmstore",
      "status": "completed",
      "productName": "Premium key",
      "price": 9.99,
      "paid": 9.99,
      "paymentMethod": "stripe",
      "customerEmail": "buyer@example.com",
      "country": "DE",
      "createdAt": "2026-09-21T10:00:00.000Z",
      "completedAt": "2026-09-21T10:05:00.000Z",
      "updatedAt": "2026-09-21T10:05:00.000Z"
    }
  ],
  "limit": 50,
  "hasMore": false,
  "nextCursor": null
}
```

| Field | Type |
| - | - |
| `id` | Order row id |
| `invoiceId` | Same value as `orderId` |
| `orderId` | Stored `orderId` column. Not the separate `publicId` column. |
| `storeId` | Store id |
| `status` | Stored status string |
| `productName` | String |
| `price` | Number |
| `paid` | Number or `null` |
| `paymentMethod` | String |
| `customerEmail` | String |
| `country` | String or `null` |
| `createdAt` | ISO-8601 |
| `completedAt` | ISO-8601 or `null` |
| `updatedAt` | ISO-8601 |

Not included: `publicId`, `note`, `customFieldResponses`, `keyCount`, product-key strings, `ipAddress`, `userAgent`, `browser`, `operatingSystem`, `asn`, `ipIntel`, `stripeCheckoutUrl`, `cryptomusPaymentAddress`, `cryptomusQrCode`, `nowpaymentsPaymentAddress`, `paypalFfEmail`, `venmoFfEmail`, `cashAppEmail`, `customPaymentProofUrls`, `gatewayCheckoutUrl`. Lookup does not use `publicId`.

```bash theme={null}
curl "https://dash.joinmarkt.com/api/merchant/v1/stores/STORE_ID/invoices?status=completed&limit=20" \
  -H "Authorization: Bearer mk_live_..." \
  -H "Accept: application/json"
```

Fallback `error`: `Failed to list invoices`.

## `GET /api/merchant/v1/stores/{storeId}/invoices/{invoiceId}`

No query parameters.

Unknown id, or an order that belongs to another shop, returns `404` `Order not found`.

The body is one invoice object with the same fields as a list element. Detail does not add `note`, `customFieldResponses`, or `keyCount`.

Fallback `error`: `Failed to load invoice`.

## Errors

| Status | When |
| - | - |
| 400 | Invalid query or cursor |
| 401 | Missing header, token not starting with `mk_live_`, unknown, revoked, or expired key (`Unauthorized` or `API key expired`) |
| 403 | Shop, scope, or `viewOrders` ceiling (`Forbidden`) |
| 404 | Invoice order not in this store (`Order not found`) |
| 429 | `Too many requests` |
| 500 | Unexpected failure (`errorId` included) |

There is no create, update, or delete method on these paths.

## Related

* [Merchant orders](https://docs.joinmarkt.com/api-reference/merchant-orders.md)
* [API keys](https://docs.joinmarkt.com/developers/api-keys.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.