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

# Advanced Filters

> Comprehensive guide to filtering API resources with query parameters and JSON filters

The Penbox API provides two powerful filtering methods to query and retrieve exactly the data you need. Both methods are available on `GET` (list) and `GET /count` endpoints.

## Two Filtering Methods

### Method 1: Direct Query Parameters

The simplest way to filter is by adding query parameters directly to the URL:

```bash theme={null}
GET /v1/cases?workspace[id]=abc-123&owner[email]=john@acme.com&status=pending
```

**Benefits:**

* Simple and readable
* Easy to construct manually
* Good for basic filtering needs

**Example:**

```javascript theme={null}
// Filter cases by workspace and owner
const params = new URLSearchParams({
  'workspace[id]': workspaceId,
  'owner[email]': 'sales@acme.com',
  'archived': 'false'
});

const response = await fetch(
  `https://connect.penbox.io/v1/cases?${params}`,
  { headers: { 'Authorization': `Bearer ${token}` } }
);
```

### Method 2: Advanced JSON Filter

For complex queries, use the `filter` parameter with a JSON object containing operators:

```bash theme={null}
GET /v1/cases?filter={"attributes":{"created_at":{"$gt":"2024-01-01"}}}
```

**Benefits:**

* Powerful operator-based filtering
* Support for complex conditions
* Filter on attributes and relationships
* Combine multiple criteria with operators

**Example:**

```javascript theme={null}
const filter = {
  attributes: {
    created_at: { $gt: '2024-01-01T00:00:00Z' },
    status: { $ne: 'draft' }
  },
  relationships: {
    owner: {
      attributes: {
        email: { $eq: 'sales@acme.com' }
      }
    }
  }
};

const response = await fetch(
  `https://connect.penbox.io/v1/cases?filter=${encodeURIComponent(JSON.stringify(filter))}`,
  { headers: { 'Authorization': `Bearer ${token}` } }
);
```

## Filter Operators

When using the JSON filter method, you can use the following operators:

| Operator | Description                       | Example                                 |
| -------- | --------------------------------- | --------------------------------------- |
| `$eq`    | Equals                            | `{ status: { $eq: 'pending' } }`        |
| `$ne`    | Not equals                        | `{ status: { $ne: 'draft' } }`          |
| `$gt`    | Greater than                      | `{ created_at: { $gt: '2024-01-01' } }` |
| `$lt`    | Less than                         | `{ created_at: { $lt: '2024-12-31' } }` |
| `$ge`    | Greater than or equal to          | `{ created_at: { $ge: '2024-01-01' } }` |
| `$le`    | Less than or equal to             | `{ created_at: { $le: '2024-12-31' } }` |
| `$like`  | Pattern matching                  | `{ title: { $like: '%onboarding%' } }`  |
| `$ilike` | Case-insensitive pattern matching | `{ title: { $ilike: '%Customer%' } }`   |

<Note>
  The `$like` and `$ilike` operators support SQL-style wildcards: `%` matches any characters
</Note>

<Note>
  Comparison operators (`$gt`, `$lt`, `$ge`, `$le`) can be used for numbers and dates.
</Note>

## Filter Structure

The JSON filter object has two main sections:

### Attributes

Filter on the resource's direct attributes:

```json theme={null}
{
  "attributes": {
    "status": { "$eq": "pending" },
    "archived_at": { "$eq": null },
    "created_at": { "$gt": "2024-01-01T00:00:00Z" }
  }
}
```

### Relationships

Filter on related resources:

```json theme={null}
{
  "relationships": {
    "owner": {
      "id": { "$eq": "abc-123" }
    },
    "company": {
      "attributes": {
        "slug": { "$eq": "acme-corp" }
      }
    }
  }
}
```

## Examples by Resource

### Filtering Cases

**Direct Parameters:**

```bash theme={null}
# Filter by template, workspace, and status
GET /v1/cases?template[id]=template-123&workspace[slug]=acme&parent_status=in_progress
```

**JSON Filter:**

```javascript theme={null}
// Cases created after a date with pending status
const filter = {
  attributes: {
    created_at: { $gt: '2024-01-01T00:00:00Z' },
    parent_status: { $eq: 'pending' },
    archived_at: { $eq: null }
  },
  relationships: {
    workspace: {
      attributes: {
        slug: { $eq: 'acme-corp' }
      }
    }
  }
};
```

### Filtering Case Templates

**Direct Parameters:**

```bash theme={null}
# Search by title and filter by workspace
GET /v1/case_templates?title=onboarding&workspace[slug]=acme&archived=false
```

**JSON Filter:**

```javascript theme={null}
// Templates with specific title pattern
const filter = {
  attributes: {
    is_template: { $eq: true },
    title: { $ilike: '%customer%' },
    archived_at: { $eq: null }
  }
};
```

### Filtering Forms

**Direct Parameters:**

```bash theme={null}
# Filter by flow, user, and completion status
GET /v1/forms?flow[slug]=kyc&user[email]=john@example.com&completed=true
```

**JSON Filter:**

```javascript theme={null}
// Active forms with specific owner
const filter = {
  attributes: {
    active: { $eq: true },
    completed: { $eq: false }
  },
  relationships: {
    owner: {
      attributes: {
        email: { $eq: 'sales@acme.com' }
      }
    }
  }
};
```

## Common Filtering Patterns

### Time-Based Filtering

```javascript theme={null}
// Cases created in the last 7 days
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();

const filter = {
  attributes: {
    created_at: { $gt: sevenDaysAgo }
  }
};
```

### Status Filtering

```javascript theme={null}
// Active, non-archived cases in progress
const filter = {
  attributes: {
    parent_status: { $eq: 'in_progress' },
    archived_at: { $eq: null }
  }
};
```

### Text Search

```javascript theme={null}
// Case templates with "customer" in the title
const filter = {
  attributes: {
    title: { $ilike: '%customer%' }
  }
};
```

### Multiple Criteria

```javascript theme={null}
// Complex filter combining multiple conditions
const filter = {
  attributes: {
    created_at: { $gt: '2024-01-01T00:00:00Z' },
    parent_status: { $ne: 'draft' },
    archived_at: { $eq: null }
  },
  relationships: {
    workspace: {
      attributes: {
        slug: { $eq: 'acme-corp' }
      }
    },
    owner: {
      attributes: {
        email: { $eq: 'sales@acme.com' }
      }
    }
  }
};
```

## Available Filters by Endpoint

### Cases (`/v1/cases`)

**Direct Parameters:**

* `template[id]` - Filter by template UUID
* `status` - Custom status value
* `parent_status` - Parent status (draft, pending, in\_progress, closed)
* `archived` - Boolean (true/false)
* `waiting_for` - Who the case is waiting for (none, owner, contact)
* `reference` - Custom reference number
* `owner[id]` - Owner UUID
* `owner[email]` - Owner email
* `workspace[id]` - Workspace UUID
* `workspace[slug]` - Workspace slug
* `contact[given_name]` - Contact first name
* `contact[family_name]` - Contact last name
* `contact[email]` - Contact email

### Case Templates (`/v1/case_templates`)

**Direct Parameters:**

* `title` - Search by title (partial match)
* `reference` - Custom reference
* `archived` - Boolean (true/false)
* `owner[id]` - Owner UUID
* `owner[email]` - Owner email
* `workspace[id]` - Workspace UUID
* `workspace[slug]` - Workspace slug

### Forms (`/v1/forms`)

**Direct Parameters:**

* `workspace[slug]` - Workspace slug
* `flow[slug]` - Form template slug (single value or array)
* `flow[customization]` - Template customization UUID
* `owner[email]` - Owner email
* `user[given_name]` - User first name
* `user[family_name]` - User last name
* `user[email]` - User email
* `user[phone]` - User phone
* `archived` - Boolean (true/false)
* `active` - Boolean (true/false)
* `completed` - Boolean (true/false)
* `processed` - Boolean (true/false)

## Best Practices

<AccordionGroup>
  <Accordion title="Use direct parameters for simple filters">
    For basic filtering needs (workspace, status, owner), use direct query parameters. They're easier to read and debug.
  </Accordion>

  <Accordion title="Use JSON filters for complex queries">
    When you need operators, date comparisons, or pattern matching, use the JSON filter method.
  </Accordion>

  <Accordion title="Always URL-encode JSON filters">
    When passing JSON in the filter parameter, always use `encodeURIComponent()` to properly encode special characters.
  </Accordion>

  <Accordion title="Implement pagination for large datasets">
    Always use pagination when expecting many results to avoid timeouts and improve performance.
  </Accordion>

  <Accordion title="Filter null values explicitly">
    To check for null values, use `{ $eq: null }` or `{ $ne: null }` in your filter.

    ```javascript theme={null}
    // Non-archived resources
    { archived_at: { $eq: null } }
    ```
  </Accordion>

  <Accordion title="Use $ilike for case-insensitive search">
    When searching text fields, use `$ilike` instead of `$like` for case-insensitive matching.
  </Accordion>
</AccordionGroup>

## Real-World Examples

### Dashboard: Active Cases by Team Member

```javascript theme={null}
async function getActiveCases(ownerEmail) {
  const filter = {
    attributes: {
      parent_status: { $eq: 'in_progress' },
      archived_at: { $eq: null }
    },
    relationships: {
      owner: {
        attributes: {
          email: { $eq: ownerEmail }
        }
      }
    }
  };

  const response = await fetch(
    `https://connect.penbox.io/v1/cases?filter=${encodeURIComponent(JSON.stringify(filter))}&sort=-created_at`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );

  return await response.json();
}
```

### Search Case Templates

```javascript theme={null}
async function searchTemplates(searchTerm) {
  // Using direct parameter for simplicity
  const params = new URLSearchParams({
    title: searchTerm,
    archived: 'false'
  });

  const response = await fetch(
    `https://connect.penbox.io/v1/case_templates?${params}&sort=title`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );

  return await response.json();
}
```

### Multi-Workspace Reporting

```javascript theme={null}
async function getCompletedCases(workspaceSlug, startDate, endDate) {
  const filter = {
    attributes: {
      parent_status: { $eq: 'closed' },
      created_at: { $gt: startDate, $lt: endDate }
    },
    relationships: {
      workspace: {
        attributes: {
          slug: { $eq: workspaceSlug }
        }
      }
    }
  };

  const response = await fetch(
    `https://connect.penbox.io/v1/cases?filter=${encodeURIComponent(JSON.stringify(filter))}`,
    { headers: { 'Authorization': `Bearer ${token}` } }
  );

  return await response.json();
}
```

## Error Handling

Invalid filters return a `400 Bad Request` error:

```json theme={null}
{
  "errors": [
    {
      "status": "400",
      "title": "Invalid Filter",
      "detail": "Invalid filter parameter format"
    }
  ]
}
```

**Common Issues:**

* Invalid JSON format in filter parameter
* Unrecognized filter fields
* Type mismatches (e.g., string instead of boolean)
* Invalid operator usage

**Solution:**

```javascript theme={null}
try {
  const response = await fetch(url, { headers });
  
  if (!response.ok) {
    const error = await response.json();
    console.error('Filter error:', error);
    return { data: [], meta: { total_count: 0 } };
  }
  
  return await response.json();
} catch (error) {
  console.error('Request failed:', error);
  return { data: [], meta: { total_count: 0 } };
}
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Pagination and Sorting" icon="arrows-up-down" href="/api-reference/pagination-and-sorting">
    Control result size and order
  </Card>

  <Card title="OpenAPI Specification" icon="code" href="/api-reference/openapi.json">
    Complete API specification with all available filters
  </Card>

  <Card title="List Cases" icon="folder-tree" href="/api-reference/cases/list-cases">
    Apply filters to the Cases endpoint
  </Card>

  <Card title="List Forms" icon="file-lines" href="/api-reference/requests/list-requests">
    Filter forms with advanced queries
  </Card>

  <Card title="Case Templates" icon="clipboard-list" href="/api-reference/case-templates/list-case-templates">
    Search and filter case templates
  </Card>
</CardGroup>
