# GET /api/v2/contacts

List contacts

- MCP id: contacts.index
- Scope: api:read:customers
- Permission: customers::read
- Rate cost: 1 unit, read bucket. `view=detail` makes it 3. Adds: each include +1, `q` +2.
- Cache-Control: private, no-store

Returns the contacts the caller may read, by name A-Z.

Pass `filter[contactable_type]` and `filter[contactable_id]` together to list one customer's or one user's contacts (there is no nested path for them), or `filter[contact_role_id]` to list one role's. Pass q to search name and email, and `include=contactable,role` to expand the parent and the role.

## Parameters
- view (string, optional): Which field set to render.
- per_page (integer 1 to 100, optional): Rows per page. At most 25 when `include` names a to-many relation; a larger value is 400 invalid-query-parameter.
- cursor (string, optional): Opaque keyset cursor from a previous page's links.next. Never construct one by hand.
- page (integer 1 to 1000, optional): Opt in to page-number paging: 1-based, at most 1000. Omit it, the default, to page by opaque cursor, which is what links.next hands you. Cannot be combined with cursor. Published only on collections that count their rows on every request.
- filter[contact_role_id] (integer, optional): Assigned contact role. Use the `null` operator to list contacts with no role. Operators: = in null.
- filter[contactable_id] (integer, optional): Key of the parent record. This pair is how a customer's contacts are listed: there is no `/customers/{customer}/contacts` path. Operators: = in.
- filter[contactable_type] (string, optional): Kind of parent the contact hangs off. Must be sent together with `filter[contactable_id]`; one half without the other is a `400`.
- filter[created_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): Creation timestamp. Pair with `sort=id` for a stable full export. Operators: = gte lt.
- filter[updated_at] (timestamp (YYYY-MM-DDThh:mm:ssZ), optional): Last-write timestamp. Use for incremental sync. Operators: = gte lt.
- q (string, optional): Free-text search over `name`, `email`, in that order of relevance. At most one substring match per request: `q` together with a `contains` filter is 400 invalid-query-parameter.
- sort (string, optional): Up to three keys, comma-separated, each optionally prefixed with `-` for descending.
- include (string, optional): Comma-separated names to inline, at most five.
- X-Request-Id (header, optional): A correlation id of your own. It is recorded in our logs next to the server's request id but never returned: the response header always carries the server's id. A value that does not match the pattern is discarded silently, never rejected.
- If-None-Match (header, optional): The ETag a read is conditional on. A match is answered 304 with no body.

## Response 200
- id (integer, required): Unique identifier.
- contactable_type (string, required): The kind of record `contactable` points at; it says which branch of `contactable` applies.
- contactable_id (integer, required): The id of the record `contactable` points at.
- contactable (Ref or CustomerSummary or UserSummary, required): The record this contact belongs to: a customer for a CRM contact, a user for an employee's emergency contact. Rendered as {"id": N} until named in `?include=contactable`, and still as {"id": N} where the caller may not read the parent's prefix. `contactable_type` and `contactable_id` are always present, so the parent can be found without the include, and the reference form shows nothing `contactable_id` does not.
- contact_role_id (integer or null, required): Assigned contact role, or `null`. Deleting a role unassigns it rather than refusing.
- role (Ref or ContactRoleSummary or null, required): The contact's role. Rendered as {"id": N} until named in `?include=role`, and `null` where no role is assigned.
- name (string ≤ 255, required): Contact name. This is the default sort key and one of the fields `?q=` searches.
- email (string or null ≤ 255, required): Email address. The second field `?q=` searches; not unique.
- phone (string or null ≤ 255, required): Phone number.
- address (string or null ≤ 255, required): Street address. plnnrly pre-fills this from the customer when adding a contact, but the API fills in no default for it.
- zip (string or null ≤ 255, required): Postal code.
- city (string or null ≤ 255, required): City.
- country (string or null ≤ 255, required): Country as an ISO 3166-1 alpha-2 code, e.g. NL, as plnnrly writes it. Older records may hold any string.
- position (integer or null, required): Display order within one parent, ascending. Null where unset.
- comment (html or null, required): Free-form note, sanitised HTML. The emergency-contact form uses it for the relationship to the employee, e.g. Partner.
- created_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Creation timestamp.
- updated_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Last-write timestamp; use for incremental sync.
- links (ContactLinks, required)
  - self (url, required): The canonical URI of this record.

## Errors
- 403 forbidden-by-policy | insufficient-permission | insufficient-token-scope: Forbidden.
- 304 : Not modified.
- 400 idempotency-key-required | invalid-cursor | invalid-query-parameter | malformed-request | unsupported-parameter: Bad request.
- 401 token-expired | unauthenticated: Unauthenticated.
- 404 endpoint-not-found | resource-not-found: Not found.
- 406 not-acceptable: Not acceptable.
- 429 rate-limit-exceeded: Too many requests.
- 500 internal-error: Server error.
- 503 tenant-unavailable: Service unavailable.

## Example
```bash
curl -G \
  'https://<your-url>/api/v2/contacts' \
  --data-urlencode 'filter[contactable_type]=customer' \
  --data-urlencode 'filter[contactable_id]=1' \
  --data-urlencode 'include=role' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json'
```
