> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.solvimon.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.solvimon.com/_mcp/server.

# Searching resources

How the `POST /search` endpoints work: the `search_fields` and `filter_fields` body, what each resource accepts, and the rules that decide whether a field is applied or quietly dropped.

---

## Why this matters

A list endpoint takes [query parameters](/platform-guides/for-developers/query-parameters) and answers "give me the next page". A search endpoint takes a body and answers "find the ones that look like this". Multiple resources expose one, all on the same body shape, so once you can build the body for invoices you can build it for subscriptions, products or workflow executions.

The body has two parts:

* **`search_fields`** is text search. It matches on names, references and other free-text attributes, with wildcards and case handling you control.
* **`filter_fields`** is exact filtering. It matches ids, statuses, types and date or amount ranges.

Both are lists, both are optional, and a resource has to match **all** the entries you send. There is no OR between entries.

## The request

**`Request`**

```bash Request
curl -X POST https://test.api.solvimon.com/v1/pricing-plan-subscriptions/search \
  -H "X-API-KEY: <apiKey>" \
  -H "Content-Type: application/json" \
  -d '{
    "search_fields": [
      { "name": "reference", "value": "ACME", "wildcard_start": false }
    ],
    "filter_fields": [
      { "name": "customer_id", "value": "cust_UwDeeN0vxxHxMPCaeb1R" },
      { "name": "type", "value": "STANDARD" },
      { "name": "status", "values": ["ACTIVE"] }
    ]
  }'
```

That reads: active standard subscriptions of one customer whose reference starts with "ACME", ignoring case.

**`Response`**

```json Response
{
  "data": [
    {
      "object_type": "PRICING_PLAN_SUBSCRIPTION",
      "id": "ppsu_8Hq2pqJnwo42rzGV7abcd",
      "reference": "acme-platform-2026",
      "name": "Acme platform subscription",
      "customer_id": "cust_UwDeeN0vxxHxMPCaeb1R",
      "type": "STANDARD",
      "status": "ACTIVE"
    }
  ],
  "page": 1,
  "limit": 50,
  "total_number_of_pages": 1,
  "links": {
    "current": "https://test.api.solvimon.com/v1/pricing-plan-subscriptions/search?page=1&limit=50"
  }
}
```

The response is the standard paginated wrapper, the same one the list endpoints return.

## Text search with `search_fields`

| Field            | Default | Description                                                                                                           |
| ---------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| `name`           | none    | Which attribute to search. Leave it out to search every text attribute the resource exposes, where that is supported. |
| `value`          | none    | The text to look for. Required.                                                                                       |
| `wildcard_start` | `true`  | Allow anything before the value.                                                                                      |
| `wildcard_end`   | `true`  | Allow anything after the value.                                                                                       |
| `ignore_case`    | `true`  | Match regardless of case.                                                                                             |

The two wildcard flags combine into four kinds of match:

| `wildcard_start` | `wildcard_end` | Match                  |
| ---------------- | -------------- | ---------------------- |
| `true`           | `true`         | Contains. The default. |
| `false`          | `true`         | Starts with.           |
| `true`           | `false`        | Ends with.             |
| `false`          | `false`        | Exact.                 |

Sending several `search_fields` entries narrows the result: each one has to match.

> **Note**
>
> Omitting `name` is not supported everywhere, and some resources have no text search at all. Each endpoint's **Search fields** table below lists the names it takes, with an `omitted` row when a nameless entry is allowed.

## Exact filtering with `filter_fields`

| Field       | Description                                                                                                                                       |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`      | Which attribute to filter on.                                                                                                                     |
| `value`     | A single value.                                                                                                                                   |
| `values`    | A list of values, for a field that accepts several.                                                                                               |
| `reference` | Read only by the `custom_field` filter, and only on the endpoints whose filter table says `value` plus `reference`. Leave it out everywhere else. |

### When to use `value` and when to use `values`

A field reads one of the two, and the three behaviours look identical from outside, which is why this is the part that most often silently returns the wrong rows:

* **Single-valued fields** such as `customer_id`, `id` and `type` read `value`. A `values` list is ignored.
* **Multi-valued fields** read `values`, and accept a single `value` as a one-item list. A status filter is usually one of these.
* **A few multi-valued fields read `values` only.** Sent as `value`, the filter is dropped and the request returns everything, with no error to tell you.

The **Reads** column in each endpoint's filter table says which of the three a field is. Check it rather than assuming: a dropped filter looks exactly like a filter that matched a lot.

> **Warning**
>
> A `name` the endpoint does not know is not always an error. Most endpoints validate the names in both lists and answer `422` naming the entry; a few ignore the entry and return the unfiltered result, which looks like a working request that returns too much. Each subsection below says which of the two its endpoint does, so check your field names there rather than against the response.

### Enum values

Where a filter is backed by an enum, the endpoint's table below lists every value it accepts. Whether a value outside that list is refused depends on the endpoint: most validate it and answer `422` naming the field, while some match it as a plain string, so an unknown value returns no rows instead of an error. Each subsection says which.

Send enum values in upper case. The comparison is case-sensitive unless the subsection says the endpoint upper-cases for you, so `active` is not `ACTIVE`: a `422` on a validated field, and silently empty on one that is not validated.

### Ranges

A range field takes its bounds as a single string in `value`, in the same bracket notation the list endpoints use:

| Notation                                      | Meaning                         |
| --------------------------------------------- | ------------------------------- |
| `[2026-01-01T00:00:00Z,2026-04-01T00:00:00Z]` | Between the two, both included. |
| `{2026-01-01T00:00:00Z,2026-04-01T00:00:00Z}` | Between the two, both excluded. |
| `[2026-01-01T00:00:00Z,*]`                    | From that date onwards.         |
| `[*,2026-04-01T00:00:00Z]`                    | Up to and including that date.  |
| `2026-01-01T00:00:00Z`                        | Exactly that value.             |

A square bracket includes the bound and a curly bracket excludes it, so the two can be mixed: `[100,*}` is "100 or more". Omitting both brackets is an exact match, and one bracket without the other is rejected. Dates are ISO 8601; amounts are plain numbers.

### Custom fields

A `custom_field` filter matches a [custom field](/platform-guides/platform-configuration/custom-fields) by reference and value. It is written in one of two forms, and each endpoint's filter table says which form it takes.

One form puts the reference in the `reference` field, which is the only place `reference` is read:

**`Customers whose "segment" custom field is "enterprise"`**

```json Customers whose "segment" custom field is "enterprise"
{
  "filter_fields": [
    { "name": "custom_field", "reference": "segment", "value": "enterprise" }
  ]
}
```

The other carries both halves in `value`, separated by a single colon:

**`Products whose "line" custom field is "hardware"`**

```json Products whose "line" custom field is "hardware"
{
  "filter_fields": [
    { "name": "custom_field", "value": "line:hardware" }
  ]
}
```

A value with no colon, or with more than one, is rejected with `custom fields needs to be formatted as key:value`. A reference that matches no custom field on that resource type is rejected with `Not able to find custom field with reference …`.

To search custom field content as text rather than match it exactly, use the `custom_field.value` entry in `search_fields` instead. That entry carries no reference in either form, so it searches across every custom field the resource has.

## Paging and ordering

Paging and ordering stay in the query string, not in the body:

**`Second page, newest first`**

```bash Second page, newest first
curl -X POST "https://test.api.solvimon.com/v1/invoices/search?page=2&limit=25&order_by=invoice_date&order_direction=desc" \
  -H "X-API-KEY: <apiKey>" \
  -H "Content-Type: application/json" \
  -d '{ "filter_fields": [{ "name": "status", "values": ["FINAL"] }] }'
```

`page` defaults to 1 and `limit` to 50. The fields you can order by are listed under [Query parameters](/platform-guides/for-developers/query-parameters). A few search endpoints return a fixed order of their own and ignore both parameters; the subsection below says so where that is the case.

---

## Supported fields per endpoint

Each resource below lists its `search_fields` names and its `filter_fields` names, with every value an enum filter accepts.

### Customers

[POST /v1/customers/search](https://docs.solvimon.com/api-docs/configuration-api/customers/post-customers-search)

**Search fields**

| `name`               | Searches                                                                                                              |
| -------------------- | --------------------------------------------------------------------------------------------------------------------- |
| omitted              | All four attributes below at once.                                                                                    |
| `name`               | The customer's name.                                                                                                  |
| `reference`          | Your own reference.                                                                                                   |
| `email`              | The customer's email address.                                                                                         |
| `custom_field.value` | The values of all custom fields. A `search_fields` entry has no `reference`, so this cannot be narrowed to one field. |

**Filter fields**

| `name`               | Reads                    | Values                                                                   |
| -------------------- | ------------------------ | ------------------------------------------------------------------------ |
| `id`                 | `value`                  | A customer id.                                                           |
| `status`             | `value` or `values`      | `DRAFT`, `ACTIVE`, `DEPRECATED`, `ARCHIVED`, `CLOSED`                    |
| `type`               | `value`                  | `ORGANIZATION`, `INDIVIDUAL`                                             |
| `role`               | `value` or `values`      | `DEFAULT`, `NON_PROCESSING`, `SELF_BILLING`, `SELLER`, `PROCESSING_ONLY` |
| `parent_customer_id` | `value`                  | A customer id.                                                           |
| `processing_only`    | `value`                  | `true`, `false`                                                          |
| `custom_field`       | `value` plus `reference` | Any text.                                                                |

An unknown or repeated name in either list is `422`. `role` and `processing_only` are validated; `status` and `type` are not, so an unknown value returns no rows.

### Pricing plans

[POST /v1/pricing-plans/search](https://docs.solvimon.com/api-docs/configuration-api/pricing-plans/post-pricing-plans-search)

**Search fields**

| `name`               | Searches                                                                                                              |
| -------------------- | --------------------------------------------------------------------------------------------------------------------- |
| omitted              | All three attributes below at once.                                                                                   |
| `name`               | The plan's name.                                                                                                      |
| `reference`          | Your own reference.                                                                                                   |
| `custom_field.value` | The values of all custom fields. A `search_fields` entry has no `reference`, so this cannot be narrowed to one field. |

**Filter fields**

| `name`         | Reads                        | Values                                      |
| -------------- | ---------------------------- | ------------------------------------------- |
| `id`           | `value`                      | A pricing plan id.                          |
| `type`         | `value`                      | `STANDARD`, `CUSTOM`                        |
| `status`       | `value` or `values`          | `DRAFT`, `ACTIVE`, `DEPRECATED`, `ARCHIVED` |
| `variant`      | `value` or `values`          | `DEFAULT`, `RATE_CARD`                      |
| `customer_id`  | `value`                      | A customer id, which has to exist.          |
| `custom_field` | `value` as `reference:value` | Any text.                                   |

An unknown or repeated name in either list is `422`, as is an unknown `type` or a `customer_id` that does not resolve. `status` and `variant` are not validated, so an unknown value there returns no rows. When the body also carries `selection_rule_fields`, a `type` filter has to be `STANDARD`.

### Pricing plan subscriptions

[POST /v1/pricing-plan-subscriptions/search](https://docs.solvimon.com/api-docs/configuration-api/pricing-plan-subscriptions/post-pricing-plan-subscriptions-search)

**Search fields**

| `name`      | Searches                                                        |
| ----------- | --------------------------------------------------------------- |
| omitted     | Both attributes below at once, or an exact id match. See below. |
| `name`      | The subscription's name.                                        |
| `reference` | Your own reference.                                             |

**Filter fields**

| `name`        | Reads         | Values                                             |
| ------------- | ------------- | -------------------------------------------------- |
| `id`          | `value`       | A subscription id.                                 |
| `customer_id` | `value`       | A customer id.                                     |
| `type`        | `value`       | `QUOTE`, `BILLING`, `TEMPLATE`                     |
| `status`      | `values` only | `DRAFT`, `ACTIVE`, `VOID`, `ARCHIVED`, `CANCELLED` |

An unknown or repeated name in either list is `422`. Values are not validated, so an unknown status or type returns no rows. An empty `values` list is dropped rather than matching nothing.

> **Warning**
>
> `status` is the one filter on the platform that reads `values` and nothing else. Sent as `value` it is dropped silently, and the response carries every status rather than the one you asked for.

**Searching by id in a search box.** A `search_fields` entry with no `name` whose `value` is a valid resource id is treated as an exact filter rather than as text: a `cust_…` id filters on `customer_id`, and a `ppsu_…` id filters on the subscription's own `id`. This exists so a single search box can take either a name or a pasted id. An id of any other type matches nothing. No other endpoint does this.

### Products

[POST /v1/products/search](https://docs.solvimon.com/api-docs/configuration-api/products/post-products-search)

**Search fields**

| `name`      | Searches            |
| ----------- | ------------------- |
| `name`      | The product's name. |
| `reference` | Your own reference. |

**Filter fields**

| `name`             | Reads                        | Values                                            |
| ------------------ | ---------------------------- | ------------------------------------------------- |
| `status`           | `value` or `values`          | `DRAFT`, `ACTIVE`, `DEPRECATED`, `ARCHIVED`       |
| `product_category` | `value`                      | A product category reference, which has to exist. |
| `customer_id`      | `value`                      | A customer id, which has to exist.                |
| `custom_field`     | `value` as `reference:value` | Any text.                                         |

`name` is required on every `search_fields` entry. An unknown name in either list is `422`, as is an unknown status, an unknown category reference or a `customer_id` that does not resolve.

### Product items

[POST /v1/product-items/search](https://docs.solvimon.com/api-docs/configuration-api/product-items/post-product-items-search)

**Search fields**

| `name`               | Searches                                                                                                              |
| -------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `name`               | The item's name.                                                                                                      |
| `reference`          | Your own reference.                                                                                                   |
| `custom_field.value` | The values of all custom fields. A `search_fields` entry has no `reference`, so this cannot be narrowed to one field. |

**Filter fields**

| `name`         | Reads                        | Values                                                                                 |
| -------------- | ---------------------------- | -------------------------------------------------------------------------------------- |
| `product_id`   | `value`                      | A product id.                                                                          |
| `status`       | `value` or `values`          | `DRAFT`, `ACTIVE`, `DEPRECATED`, `ARCHIVED`                                            |
| `type`         | `value`                      | `REVENUE`, `DEDUCTION`                                                                 |
| `model_type`   | `value`                      | `USAGE_BASED`, `USAGE_BASED_CONVERSION`, `RECURRING`, `ONE_OFF`, `PER_SEAT`, `CREDITS` |
| `customer_id`  | `value`                      | A customer id, which has to exist.                                                     |
| `custom_field` | `value` as `reference:value` | Any text.                                                                              |

`name` is required on every `search_fields` entry. An unknown or repeated name is `422`, and so is an unknown `type`, `model_type` or `status`. These three are upper-cased before they are checked, so case does not matter here.

### Product categories

[POST /v1/product-categories/search](https://docs.solvimon.com/api-docs/configuration-api/product-categories/post-product-categories-search)

**Search fields**

| `name`      | Searches             |
| ----------- | -------------------- |
| `name`      | The category's name. |
| `reference` | Your own reference.  |

**Filter fields**

| `name`        | Reads   | Values         |
| ------------- | ------- | -------------- |
| `customer_id` | `value` | A customer id. |

`name` is required on every `search_fields` entry, and an unknown name in either list is `422`.

### Invoices

[POST /v1/invoices/search](https://docs.solvimon.com/api-docs/transaction-api/invoices/post-invoices-search)

**Search fields**

| `name`           | Searches                             |
| ---------------- | ------------------------------------ |
| omitted          | Every text attribute of the invoice. |
| `invoice_number` | The invoice number.                  |

**Filter fields**

| `name`                                                                                                                | Reads                      | Values                                                          |
| --------------------------------------------------------------------------------------------------------------------- | -------------------------- | --------------------------------------------------------------- |
| `id`                                                                                                                  | `value`                    | An invoice id.                                                  |
| `type`                                                                                                                | `value`                    | `STANDARD`, `CREDIT`, `ONE_OFF`, `ONE_OFF_CREDIT`               |
| `status`                                                                                                              | `value` or `values`        | `FUTURE`, `OPEN`, `DRAFT`, `FINAL`, `EMPTY`, `VOID`, `ARCHIVED` |
| `billing_type`                                                                                                        | `value`                    | `DEFAULT`, `SELF_BILLING`                                       |
| `e_invoice_status`                                                                                                    | `value` or `values`        | `SUBMITTED`, `ACCEPTED`, `ERROR`, `IGNORE`, `SUCCESS`           |
| `linked_invoice.link_type`                                                                                            | `value`                    | `CREDIT`, `ORIGINAL`, `REPLACED`, `REPLACES`                    |
| `paid`                                                                                                                | `value`                    | `true`, `false`                                                 |
| `on_hold`                                                                                                             | `value`                    | `true`, `false`                                                 |
| `has_e_invoice`                                                                                                       | `value`                    | `true`, `false`                                                 |
| `customer_id`                                                                                                         | `value`                    | A customer id.                                                  |
| `customer.reference`                                                                                                  | `value`                    | A customer reference.                                           |
| `pricing_plan_subscription_id`                                                                                        | `value`                    | A subscription id.                                              |
| `product_id`                                                                                                          | `value`                    | A product id.                                                   |
| `product_item_id`                                                                                                     | `value`                    | A product item id.                                              |
| `billing_entity_id`                                                                                                   | `value`                    | A billing entity id.                                            |
| `invoice_amount_including_tax.currency`                                                                               | `value`                    | An ISO 4217 currency code.                                      |
| `invoice_amount_including_tax.quantity`                                                                               | `value` as a numeric range | See [Ranges](#ranges).                                          |
| `created_at`, `updated_at`, `invoice_date`, `billing_period_date`, `delivery_date`, `updated_to_final_at`, `due_date` | `value` as a date range    | See [Ranges](#ranges).                                          |

Every enum above is validated: an unknown value is `422` naming the field. An unknown *name*, though, is ignored rather than refused, so a typo in a field name quietly widens the result. `customer.reference` resolves to a customer and conflicts with a `customer_id` naming a different one. Pair `invoice_amount_including_tax.quantity` with `invoice_amount_including_tax.currency` to find every invoice over a threshold in one currency.

### Reports

[POST /v1/reports/search](https://docs.solvimon.com/api-docs/transaction-api/reports/post-reports-search)

**Search fields**

Not supported. A `search_fields` entry with a `name` is `422`.

**Filter fields**

| `name`        | Reads   | Values                                                                                                                                                                                                                                                                                                                                                               |
| ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `report_code` | `value` | `INVOICE`, `INVOICE_USAGE`, `INVOICE_HEADER`, `INVOICE_LINE`, `INVOICE_TAX`, `INVOICE_COMMITMENT`, `E_INVOICING`, `CHILD_PARENT_BREAKDOWN`, `CHILD_PARENT_BREAKDOWN_PLATFORM`, `NOT_MATCHED_METER_DATA`, `DUPLICATE_METER_DATA`, `METERING`, `CUSTOMER`, `PRODUCT`, `PRICING_PLAN_SUBSCRIPTION`, `PAYMENT`, `PRICING`, `REVENUE_RECOGNITION`, `CLAUSES`, `REPROCESS` |
| `customer_id` | `value` | A customer id.                                                                                                                                                                                                                                                                                                                                                       |
| `invoice_id`  | `value` | An invoice id.                                                                                                                                                                                                                                                                                                                                                       |

Reports and report generate requests share one list of accepted names, so `integration_id` and `status` are accepted here without error but have no effect on a report search. Any other name is `422`. `report_code` is passed through without validation, so an unknown code returns no rows. Results come back ordered by `report_code` descending unless you pass `order_by`.

### Report generate requests

[POST /v1/report-generate-requests/search](https://docs.solvimon.com/api-docs/transaction-api/reports/post-report-generate-requests-search)

**Search fields**

Not supported. A `search_fields` entry with a `name` is `422`.

**Filter fields**

| `name`           | Reads               | Values                                                          |
| ---------------- | ------------------- | --------------------------------------------------------------- |
| `report_code`    | `value`             | The same codes as Reports, above.                               |
| `status`         | `value` or `values` | `CREATED`, `QUEUED`, `SCHEDULED`, `GENERATING`, `DONE`, `ERROR` |
| `customer_id`    | `value`             | A customer id.                                                  |
| `invoice_id`     | `value`             | An invoice id.                                                  |
| `integration_id` | `value`             | An integration id.                                              |

An unknown or repeated name is `422`, as is an unknown status. `status` is upper-cased before it is checked, so its case does not matter.

### Bulk actions

[POST /v1/bulk-actions/search](https://docs.solvimon.com/api-docs/transaction-api/bulk-actions/post-bulk-actions-search)

**Search fields**

Not supported. A `search_fields` entry is ignored.

**Filter fields**

| `name`       | Reads                   | Values                                                                               |
| ------------ | ----------------------- | ------------------------------------------------------------------------------------ |
| `type`       | `value`                 | `FINALIZE_INVOICES`, `REQUEST_USAGE_REPORTS`                                         |
| `status`     | `value` or `values`     | `CREATED`, `QUEUED`, `DETERMINE_WORK`, `NOTHING_TODO`, `PROCESSING`, `DONE`, `ERROR` |
| `created_at` | `value` as a date range | See [Ranges](#ranges).                                                               |

An unknown name is ignored. Both enums are validated. `status` is upper-cased first, so its case does not matter; `type` is not, so it has to be sent in upper case. A `type` entry needs a `value`.

### Workflows

[POST /v1/workflows/search](https://docs.solvimon.com/api-docs/configuration-api/workflows/post-workflows-search)

**Search fields**

Not supported.

**Filter fields**

| `name`   | Reads               | Values                                                               |
| -------- | ------------------- | -------------------------------------------------------------------- |
| `status` | `value` or `values` | `ACTIVE`, `INACTIVE`, `DRAFT`, `ARCHIVED`                            |
| `type`   | `value`             | `INVOICE`, `PAYMENT`, `PRICING_PLAN_SUBSCRIPTION`, `WALLET`, `USAGE` |

Both are validated, so an unknown status or type is `422`. A `status` entry with neither `value` nor `values` is `422` as well. `workflowType` is accepted as an older spelling of `type`, and a `type` sent as `values` is only read when the list holds exactly one entry. This endpoint returns its own order and ignores `order_by` and `order_direction`.

### Workflow triggers

[POST /v1/workflow-triggers/search](https://docs.solvimon.com/api-docs/transaction-api/workflow-triggers/post-workflow-triggers-search)

**Search fields**

Not supported.

**Filter fields**

| `name`        | Reads   | Values            |
| ------------- | ------- | ----------------- |
| `workflow_id` | `value` | A workflow id.    |
| `customer_id` | `value` | A customer id.    |
| `type`        | `value` | `MANUAL`, `EVENT` |

All three read `value`; a `values` list is ignored. Any other name is `422`, as is an unknown `type`. This endpoint returns its own order and ignores `order_by` and `order_direction`. See [Workflow triggers](/platform-guides/workflows/workflow-triggers).

### Workflow action executions

[POST /v1/workflow-action-executions/search](https://docs.solvimon.com/api-docs/transaction-api/workflow-action-executions/post-workflow-action-executions-search)

**Search fields**

Not supported.

**Filter fields**

| `name`                         | Reads               | Values                                           |
| ------------------------------ | ------------------- | ------------------------------------------------ |
| `status`                       | `value` or `values` | `PENDING`, `SUCCEEDED`, `FAILED`, `NOT_EXECUTED` |
| `workflow_id`                  | `value`             | A workflow id.                                   |
| `workflow_action_id`           | `value`             | A workflow action id.                            |
| `workflow_trigger_id`          | `value`             | A trigger id.                                    |
| `invoice_id`                   | `value`             | An invoice id.                                   |
| `customer_id`                  | `value`             | A customer id.                                   |
| `payment_id`                   | `value`             | A payment id.                                    |
| `pricing_plan_subscription_id` | `value`             | A subscription id.                               |
| `wallet_id`                    | `value`             | A wallet id.                                     |

Any other name is `422`, as is an unknown status. The id filters take the first entry of `values` when `value` is absent. This is the endpoint to use for "what did this workflow do to this invoice", since it filters on the action, the trigger and the status together. See [Monitoring and audit](/platform-guides/workflows/monitoring-and-audit).

### API keys

[POST /v1/api-keys/search](https://docs.solvimon.com/api-docs/identity-api/api-keys/post-api-keys-search)

This one does not use the shared body, and it does not page: it looks up one API key and returns it.

| Field              | Required       | Description                                                                     |
| ------------------ | -------------- | ------------------------------------------------------------------------------- |
| `username`         | One of the two | The key's username.                                                             |
| `resource_id`      | One of the two | The key's resource id.                                                          |
| `user_type`        | No             | `MORTAL`, `TOKEN`, `API`, `PROXY`, `EXTERNAL_CREDENTIALS`. An API key is `API`. |
| `include_inactive` | No             | `true` to include keys that are no longer active.                               |

Sending neither `username` nor `resource_id` is rejected with `Neither resource_id nor username are set`, and a lookup that matches nothing answers `404`.

---

## Edge cases

* Searching needs the same `VIEW` permission as reading the resource. A key that cannot read invoices cannot search them either.
* An empty body is valid on every endpoint that takes the shared body, and returns the first page of everything, which makes `POST /search` with `{}` a usable list call.
* Text search runs against stored values, so it will not find a resource by a figure that is calculated at read time.
* Sending the same field name twice is rejected with `field … already specified` on every endpoint that validates names. Combine the values into one entry instead of repeating the entry.
* The two lists are validated independently. A name that is valid in `filter_fields` is not necessarily valid in `search_fields`, and the error names which list it came from.

---