> For the complete documentation index, see [llms.txt](https://docs.sprinthub.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sprinthub.com/topicos/api/api-listar-oportunidades-do-funil-com-filtro-por-campo-personalizado.md).

# API - Listar Oportunidades do Funil com Filtro por Campo Personalizado

### API - Listar Oportunidades do Funil com Filtro por Campo Personalizado

#### Listar oportunidades de um funil

`POST` `/crm/opportunities/XXXX?i=instancia&apitoken=XXXXXXX`

XXXX = ID do Funil (CRM)

Endpoint completo: `https://sprinthub-api-master.sprinthub.app/crm/opportunities/XXXX?i=instancia&apitoken=XXXXXXX`

Diferente de outros endpoints do CRM, a autenticação aqui vai na **query string**, não em headers.

**Headers**

| Name         | Value              |
| ------------ | ------------------ |
| Content-Type | `application/json` |

**Body**

| Campo                       | Tipo         | Obrigatório | Descrição                                                   |
| --------------------------- | ------------ | ----------- | ----------------------------------------------------------- |
| `page`                      | number       | **Sim**     | Página, começa em 0. Só pagina de fato quando há `columnId` |
| `limit`                     | number       | **Sim**     | Máximo **100**. Acima disso retorna 400                     |
| `filters`                   | array        | Não         | Condições sobre campos personalizados. Ver abaixo           |
| `columnId`                  | number       | Não         | Id da etapa. **Muda o formato da resposta**                 |
| `filterByUsers`             | array        | Não         | Ids dos responsáveis. Vazio = todos                         |
| `filterByStatus`            | array        | Não         | Situações incluídas: `open`, `gain`, `lost`                 |
| `filterByCreateDate`        | date \| null | Não         | Janela de data de criação                                   |
| `filterByExpectedCloseDate` | date \| null | Não         | Janela da data prevista de fechamento                       |
| `search`                    | string       | Não         | Texto procurado. Não alcança campos personalizados          |
| `searchBy`                  | string       | Não         | Onde buscar. Aceita **só** `lead` ou `opportunity`          |

```
{
    "filterByUsers": [],
    "filterByStatus": ["open", "gain", "lost"],
    "filterByCreateDate": null,
    "filterByExpectedCloseDate": null,
    "filters": [],
    "search": "",
    "searchBy": "lead",
    "columnId": 19,
    "page": 0,
    "limit": 100
}
```

#### Filtrar por campo personalizado

Cada condição do array `filters` tem duas camadas: a de fora identifica o tipo da condição, e `properties` carrega a comparação.

| Campo                  | Tipo   | Obrigatório | Descrição                                                                                 |
| ---------------------- | ------ | ----------- | ----------------------------------------------------------------------------------------- |
| `filterType`           | string | **Sim**     | `opField` para campo da oportunidade. **Sem ele a condição é descartada silenciosamente** |
| `properties.field`     | string | **Sim**     | Nome do campo, exato como no CRM. Não é id                                                |
| `properties.value`     | string | **Sim**     | Valor procurado                                                                           |
| `properties.operator`  | string | Não         | `=`, `!=` ou `contains`. Omitido, assume `=`                                              |
| `properties.fieldType` | string | Não         | Tipo do campo, ex. `text`                                                                 |
| `category`             | string | Não         | `opportunity` para campo da oportunidade                                                  |
| `operator`             | string | Não         | Combinação com as demais: `AND` ou `OR`                                                   |
| `id`                   | string | Não         | Usado pelo painel para identificar a linha na interface. A API ignora                     |

```
{
    "filters": [
        {
            "operator": "AND",
            "category": "opportunity",
            "filterType": "opField",
            "properties": {
                "field": "ID Omie",
                "fieldType": "text",
                "operator": "=",
                "value": "123456"
            }
        }
    ]
}
```

**Operadores:** `=` compara o valor exato, `!=` traz tudo que não seja aquele valor, `contains` procura um trecho — `845` encontra `84521`. Os valores `like` e `isNotNull` são aceitos pela API mas não filtram.

Múltiplas condições no array são aplicadas em conjunto. Ainda que vários campos sejam opcionais, o recomendável é enviar a estrutura completa, como o painel envia.

#### Resposta

**200 — sem `columnId` (kanban)**

Array de etapas, cada uma com `total`, `totalValue` e `items`. Neste modo `page` é ignorado e `items` traz apenas as primeiras `limit` oportunidades de cada etapa.

```
[
    {
        "id": 19,
        "name": "Proposta",
        "sequence": 0,
        "crm": 5,
        "total": 1,
        "totalValue": "22.10",
        "items": [
            {
                "id": 4079,
                "title": "Nro Único: 47004",
                "value": "22.10",
                "crm_column": 19,
                "crm_id": 5,
                "lead_id": 75358,
                "sequence": 4,
                "status": "open",
                "user": 191,
                "fields": {
                    "ID Omie": "123456",
                    "Item 1": "item1aqui",
                    "Nota Fiscal": "",
                    "Transportadora": ""
                },
                "dataLead": { }
            }
        ]
    },
    {
        "id": 20,
        "name": "Follow Up",
        "total": 0,
        "totalValue": 0,
        "items": []
    }
]
```

**200 — com `columnId` (lista plana)**

Lista direta das oportunidades da etapa, com paginação funcionando.

```
[
    {
        "id": 4079,
        "title": "Nro Único: 47004",
        "value": "22.10",
        "crm_column": 19,
        "crm_id": 5,
        "lead_id": 75358,
        "sequence": 4,
        "status": "open",
        "fields": {
            "ID Omie": "123456"
        }
    }
]
```

**400 — `page` ou `limit` ausente**

```
{
    "msg": "must have required property 'page'",
    "details": {
        "instancePath": "",
        "schemaPath": "#/required",
        "keyword": "required",
        "params": { "missingProperty": "page" }
    }
}
```

**400 — `limit` acima de 100**

```
{
    "msg": "must be <= 100",
    "details": {
        "instancePath": "/limit",
        "schemaPath": "#/properties/limit/maximum",
        "keyword": "maximum",
        "params": { "limit": 100 }
    }
}
```

**400 — `searchBy` inválido**

```
{
    "msg": "must be equal to one of the allowed values",
    "details": {
        "instancePath": "/searchBy",
        "params": { "allowedValues": ["lead", "opportunity"] }
    }
}
```

#### Observações

**Os campos personalizados são definidos por funil.** Um campo que existe em um funil pode não existir em outro. Filtrar por um campo inexistente naquele funil devolve lista vazia com status **200 OK**, sem mensagem de erro.

**O array `filters` não tem schema para os itens.** A API valida apenas que é um array. Uma condição sem `filterType` — ou malformada de outra maneira — é descartada silenciosamente, e a resposta volta **completa** com status 200, dando a impressão de que o filtro foi aplicado.

Para confirmar que o filtro está ativo, execute a mesma chamada com um `value` inexistente. Resultado vazio significa filtro funcionando; resultado completo significa filtro ignorado.

**Os ids de funil e de etapa saem de** `GET /crm?i=instancia&apitoken=XXXXXXX`, que devolve os funis com as etapas embutidas no campo `columns`.
