# Query Parameters

The generated API supports filtering, pagination, sorting, field selection, search, and total count via query parameters on `GET` list endpoints.

## Filter Operators

| Operator | Query Param | SQL Equivalent |
|----------|-------------|----------------|
| Equals | `?field=value` | `WHERE field = value` |
| Not equals | `?field.ne=value` | `WHERE field != value` |
| Greater than | `?field.gt=value` | `WHERE field > value` |
| Greater or equal | `?field.gte=value` | `WHERE field >= value` |
| Less than | `?field.lt=value` | `WHERE field < value` |
| Less or equal | `?field.lte=value` | `WHERE field <= value` |
| Pattern match | `?field.like=value` | `WHERE field LIKE '%value%'` |
| In list | `?field.in=a,b,c` | `WHERE field IN ('a','b','c')` |

### Examples

```bash
# Filter by status
GET /api/v1/jobs?status=open

# Range query
GET /api/v1/jobs?salaryMin.gte=100000&salaryMax.lte=200000

# Pattern matching
GET /api/v1/jobs?title.like=Engineer

# Multiple values
GET /api/v1/jobs?department.in=Engineering,Design,Product
```

## Pagination

| Parameter | Default | Description |
|-----------|---------|-------------|
| `limit` | `50` | Number of records to return (min: 1, max: 100) |
| `offset` | `0` | Number of records to skip |

```bash
GET /api/v1/jobs?limit=25&offset=50
```

The default `limit` can be configured per-resource in your `read` definition:

```typescript
read: {
  access: { roles: ["recruiter"] },
  pageSize: 25,       // Default limit
  maxPageSize: 100,   // Maximum allowed limit
},
```

### Response Shape

```json
{
  "data": [ /* records */ ],
  "pagination": {
    "limit": 25,
    "offset": 50,
    "count": 12
  }
}
```

- `count` — number of records returned on this page
- `total` — total matching records across all pages (only when `?total=true`, see below)

## Sorting

Sort by one or more fields. Use the `-` prefix for descending order.

### Multi-Sort (Recommended)

```bash
# Sort by status ascending, then createdAt descending
GET /api/v1/jobs?sort=status,-createdAt

# Single field descending
GET /api/v1/jobs?sort=-createdAt

# Multiple fields
GET /api/v1/jobs?sort=department,-salaryMax,title
```

| Prefix | Direction |
|--------|-----------|
| (none) | Ascending |
| `-` | Descending |

### Legacy Format

The original `sort` + `order` format is still supported for backwards compatibility:

```bash
GET /api/v1/jobs?sort=title&order=asc
```

| Parameter | Values | Default | Description |
|-----------|--------|---------|-------------|
| `sort` | Any column name | `createdAt` | Field to sort by |
| `order` | `asc`, `desc` | `desc` | Sort direction |

When the multi-sort format is detected (comma or `-` prefix), the `order` parameter is ignored.

## Field Selection

Select which columns to return using `?fields=`. Available on LIST and GET routes (not Views — they define their own field set).

```bash
# Return only id, title, and status
GET /api/v1/jobs?fields=id,title,status

# Combine with other query params
GET /api/v1/jobs?fields=id,title,status&status=open&sort=-createdAt

# Single record
GET /api/v1/jobs/job_123?fields=id,title,salaryMin,salaryMax
```

All columns are available including system columns (`id`, `organizationId`, `createdAt`, `modifiedAt`, etc.). Invalid field names are silently ignored. If no valid fields are provided, all columns are returned.

**Security**: Masking still applies to selected fields. Requesting `?fields=ssn` will return the masked value, not the raw data.

## Total Count

Get the total number of matching records across all pages by adding `?total=true`. Available on LIST and VIEW routes.

```bash
GET /api/v1/jobs?status=open&total=true
```

```json
{
  "data": [ /* 25 records */ ],
  "pagination": {
    "limit": 25,
    "offset": 0,
    "count": 25,
    "total": 142
  }
}
```

This is opt-in because it runs an additional `COUNT(*)` query. Only use it when you need the total (e.g., for pagination UI).

## Search

Full-text search across all text columns using `?search=`. Available on LIST and VIEW routes.

```bash
# Search across all text fields
GET /api/v1/jobs?search=engineer

# Combine with filters
GET /api/v1/jobs?search=engineer&status=open
```

The search generates an OR'd `LIKE` condition across all `text()` columns in your schema:

```sql
WHERE (title LIKE '%engineer%' OR department LIKE '%engineer%')
```

Only columns defined with `text()` in your Drizzle schema are searchable. Non-text columns (integers, timestamps, UUIDs, blobs) are automatically excluded.

## Complete Example

Combine all query parameters together:

```bash
GET /api/v1/jobs?fields=id,title,status,salaryMin&status=open&salaryMin.gte=100000&search=engineer&sort=salaryMin,-createdAt&limit=10&offset=20&total=true
```

This request:
1. **Selects** only `id`, `title`, `status`, `salaryMin` fields
2. **Filters** to open jobs with salaryMin >= 100000
3. **Searches** text columns for "engineer"
4. **Sorts** by salaryMin ascending, then createdAt descending
5. **Paginates** with 10 results starting at offset 20
6. **Counts** total matching records

## Parameter Summary

| Parameter | Applies To | Description |
|-----------|-----------|-------------|
| `limit` | LIST, VIEW | Page size (default: 50, max: 100) |
| `offset` | LIST, VIEW | Skip N records |
| `sort` | LIST, VIEW | Sort fields (comma-separated, `-` prefix for desc) |
| `order` | LIST, VIEW | Legacy sort direction (`asc` or `desc`) |
| `fields` | LIST, GET | Comma-separated column names to return |
| `total` | LIST, VIEW | Set to `true` to include total count |
| `search` | LIST, VIEW | Search text across all text columns |
| `field=value` | LIST, VIEW | Filter by exact match |
| `field.op=value` | LIST, VIEW | Filter with operator (gt, gte, lt, lte, ne, like, in) |
