# Biller Discovery API

Discover billers from a connected mailbox and follow a discovery run.

> **Scope of this file.** It is the OPERATION reference for THIS resource, generated from the BillerAPI OpenAPI document served at [/docs/openapi.json](/docs/openapi.json): each API-key endpoint the resource owns, with its parameters, request body, responses and schemas. Narrative and catalog sections the rendered page also carries (walkthroughs, event taxonomies, payload catalogs) are NOT reproduced here — read [/docs/api/biller-discovery](/docs/api/biller-discovery) for those. Endpoints the hosted flow drives from inside the iframe, and dashboard-session endpoints, are absent by design: they are not API-key operations. [/docs/api.md](/docs/api.md) enumerates the full API-key surface, including the endpoints that have no reference page yet.

All request and response field names are snake_case. Errors use the coded envelope described in [/docs/concepts/errors.md](/docs/concepts/errors.md).

## Endpoints

- [`GET /v1/emails/messages/{messageId}/attachments/{attachmentId}/download-url`](#get-v1emailsmessagesmessageidattachmentsattachmentiddownload-url)
- [`GET /v1/emails`](#get-v1emails)
- [`POST /v1/emails`](#post-v1emails)
- [`GET /v1/emails/gmail/connection/{connectionId}`](#get-v1emailsgmailconnectionconnectionid)
- [`DELETE /v1/emails/gmail/connection/{connectionId}`](#delete-v1emailsgmailconnectionconnectionid)
- [`GET /v1/emails/{messageId}`](#get-v1emailsmessageid)
- [`POST /v1/emails/classify`](#post-v1emailsclassify)
- [`GET /v1/emails/classification/{classificationId}`](#get-v1emailsclassificationclassificationid)
- [`POST /v1/emails/find-biller`](#post-v1emailsfind-biller)
- [`GET /v1/emails/workflows`](#get-v1emailsworkflows)
- [`POST /v1/emails/gmail/oauth-sessions`](#post-v1emailsgmailoauth-sessions)
- [`POST /v1/emails/gmail/oauth-sessions/{session_id}/complete`](#post-v1emailsgmailoauth-sessionssession_idcomplete)
- [`GET /v1/emails/gmail/oauth-sessions/{session_id}`](#get-v1emailsgmailoauth-sessionssession_id)

## GET /v1/emails/messages/{messageId}/attachments/{attachmentId}/download-url

Get presigned download URL for email attachment

Operation ID: `getAttachmentDownloadUrl`

Auth: `client-api-key`

**Parameters**

| Parameter | In | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `messageId` | path | yes | string | — | — |
| `attachmentId` | path | yes | string | — | — |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Returns presigned download URL | — |
| 404 | Message or attachment not found | — |

## GET /v1/emails

Get email messages for client

Operation ID: `getMessages`

Auth: `client-api-key`

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Returns paginated email messages | — |
| 401 | Unauthorized - invalid client credentials | — |

## POST /v1/emails

Create a new email message

Operation ID: `createMessage`

Auth: `client-api-key`

**Request body** (required)

Schema: `CreateEmailMessageDto`

| Field | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `user_id` | yes | string | — | End user this message belongs to, in the client's namespace. |
| `from_email` | yes | string | — | Sender address. |
| `gmail_connection_id` | no | string | — | Gmail connection this message arrived through. |
| `provider_message_id` | no | string | — | Provider's own id for the message. |
| `to_email` | no | string | — | Recipient address. |
| `subject` | no | string | — | Message subject. |
| `body_text` | no | string | — | Plain-text body. |
| `body_html` | no | string | — | HTML body. |
| `received_at` | no | string | — | ISO-8601 receipt time. Defaults to now. |
| `labels` | no | array<string> | — | Provider labels. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 201 | Message created successfully | — |
| 400 | Invalid message data | — |
| 401 | Unauthorized - invalid client credentials | — |

## GET /v1/emails/gmail/connection/{connectionId}

Get Gmail connection details

Operation ID: `getGmailConnection`

Auth: `client-api-key`

**Parameters**

| Parameter | In | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `connectionId` | path | yes | string | — | — |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Returns Gmail connection details | — |
| 401 | Unauthorized - invalid client credentials | — |
| 404 | Connection not found | — |

## DELETE /v1/emails/gmail/connection/{connectionId}

Disconnect a Gmail connection

Operation ID: `disconnectGmailConnection`

Auth: `client-api-key`

**Parameters**

| Parameter | In | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `connectionId` | path | yes | string | — | — |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Gmail connection disconnected | — |
| 401 | Unauthorized - invalid client credentials | — |
| 404 | Connection not found | — |

## GET /v1/emails/{messageId}

Get email message by ID

Operation ID: `getMessageById`

Auth: `client-api-key`

**Parameters**

| Parameter | In | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `messageId` | path | yes | string | — | — |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Returns message details | — |
| 401 | Unauthorized - invalid client credentials | — |
| 404 | Message not found | — |

## POST /v1/emails/classify

Classify an email message

Operation ID: `classifyEmail`

Auth: `client-api-key`

**Request body** (required)

Schema: `ClassifyEmailDto`

| Field | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `message_id` | yes | string | — | Message to classify. |
| `user_id` | no | string | — | End user the message belongs to. |
| `force_reclassify` | no | boolean | — | Re-run classification even if a result exists. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Email classification started successfully | — |
| 401 | Unauthorized - invalid client credentials | — |

## GET /v1/emails/classification/{classificationId}

Get classification result by ID

Operation ID: `getClassification`

Auth: `client-api-key`

**Parameters**

| Parameter | In | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `classificationId` | path | yes | string | — | — |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Returns classification details | — |
| 401 | Unauthorized - invalid client credentials | — |
| 404 | Classification not found | — |

## POST /v1/emails/find-biller

Find biller by email sender

Operation ID: `findBillerByEmail`

Auth: `client-api-key`

**Request body** (required)

Schema: `FindBillerByEmailDto`

| Field | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `sender_email` | yes | string | — | Sender address to resolve to a biller. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Returns biller information if found | — |
| 401 | Unauthorized - invalid client credentials | — |

## GET /v1/emails/workflows

List email processing workflows

Operation ID: `listWorkflows`

Auth: `client-api-key`

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Returns list of workflows | — |
| 401 | Unauthorized - invalid client credentials | — |

## POST /v1/emails/gmail/oauth-sessions

Create a short-lived Gmail OAuth PKCE session

Operation ID: `createGmailOAuthSession`

Auth: `client-api-key`

**Request body** (required)

Schema: `CreateGmailOAuthSessionDto`

| Field | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `user_id` | yes | string | — | End user id in the authenticated client's namespace. |
| `redirect_uri` | yes | string | — | Allowlisted first-party callback URL after Google consent. |
| `code_challenge` | yes | string | — | Base64url SHA-256 challenge for the client-held PKCE verifier. |
| `code_challenge_method` | no | `S256` | default `"S256"` | — |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Session created | `GmailOAuthSessionResponseDto` |
| 400 | — | `GmailOAuthSessionErrorResponseDto` |
| 500 | — | `GmailOAuthSessionErrorResponseDto` |

Response body — `GmailOAuthSessionResponseDto` (200)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `data` | yes | GmailOAuthSessionDataDto | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (400)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (500)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

## POST /v1/emails/gmail/oauth-sessions/{session_id}/complete

Complete an authorized Gmail OAuth PKCE session

Operation ID: `completeGmailOAuthSession`

Auth: `client-api-key`

**Parameters**

| Parameter | In | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `session_id` | path | yes | string | — | — |

**Request body** (required)

Schema: `CompleteGmailOAuthSessionDto`

| Field | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `user_id` | yes | string | — | End user id used when this session was created. |
| `completion_token` | yes | string | — | One-time proof returned by the provider callback handoff. |
| `code_verifier` | yes | string | — | Original PKCE verifier held by the initiating client. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | Session reached a terminal state synchronously | `GmailOAuthSessionResponseDto` |
| 202 | Completion accepted; poll session status | `GmailOAuthSessionAcceptedResponseDto` |
| 400 | — | `GmailOAuthSessionErrorResponseDto` |
| 403 | — | `GmailOAuthSessionErrorResponseDto` |
| 404 | — | `GmailOAuthSessionErrorResponseDto` |
| 500 | — | `GmailOAuthSessionErrorResponseDto` |

Response body — `GmailOAuthSessionResponseDto` (200)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `data` | yes | GmailOAuthSessionDataDto | — | — |

Response body — `GmailOAuthSessionAcceptedResponseDto` (202)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `data` | yes | GmailOAuthSessionAcceptedDataDto | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (400)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (403)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (404)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (500)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

## GET /v1/emails/gmail/oauth-sessions/{session_id}

Get Gmail OAuth session completion status

Operation ID: `getGmailOAuthSession`

Auth: `client-api-key`

**Parameters**

| Parameter | In | Required | Type | Notes | Description |
| --- | --- | --- | --- | --- | --- |
| `session_id` | path | yes | string | — | — |
| `user_id` | query | yes | string | — | End user id used when this session was created. |

**Responses**

| Status | Description | Body |
| --- | --- | --- |
| 200 | — | `GmailOAuthSessionResponseDto` |
| 400 | — | `GmailOAuthSessionErrorResponseDto` |
| 403 | — | `GmailOAuthSessionErrorResponseDto` |
| 404 | — | `GmailOAuthSessionErrorResponseDto` |
| 500 | — | `GmailOAuthSessionErrorResponseDto` |

Response body — `GmailOAuthSessionResponseDto` (200)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `data` | yes | GmailOAuthSessionDataDto | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (400)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (403)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (404)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

Response body — `GmailOAuthSessionErrorResponseDto` (500)

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

## Schemas

### ClassifyEmailDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `message_id` | yes | string | — | Message to classify. |
| `user_id` | no | string | — | End user the message belongs to. |
| `force_reclassify` | no | boolean | — | Re-run classification even if a result exists. |

### CompleteGmailOAuthSessionDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `user_id` | yes | string | — | End user id used when this session was created. |
| `completion_token` | yes | string | — | One-time proof returned by the provider callback handoff. |
| `code_verifier` | yes | string | — | Original PKCE verifier held by the initiating client. |

### CreateEmailMessageDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `user_id` | yes | string | — | End user this message belongs to, in the client's namespace. |
| `from_email` | yes | string | — | Sender address. |
| `gmail_connection_id` | no | string | — | Gmail connection this message arrived through. |
| `provider_message_id` | no | string | — | Provider's own id for the message. |
| `to_email` | no | string | — | Recipient address. |
| `subject` | no | string | — | Message subject. |
| `body_text` | no | string | — | Plain-text body. |
| `body_html` | no | string | — | HTML body. |
| `received_at` | no | string | — | ISO-8601 receipt time. Defaults to now. |
| `labels` | no | array<string> | — | Provider labels. |

### CreateGmailOAuthSessionDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `user_id` | yes | string | — | End user id in the authenticated client's namespace. |
| `redirect_uri` | yes | string | — | Allowlisted first-party callback URL after Google consent. |
| `code_challenge` | yes | string | — | Base64url SHA-256 challenge for the client-held PKCE verifier. |
| `code_challenge_method` | no | `S256` | default `"S256"` | — |

### FindBillerByEmailDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `sender_email` | yes | string | — | Sender address to resolve to a biller. |

### GmailOAuthSessionAcceptedDataDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `session_id` | yes | string | — | — |
| `status` | yes | `INITIATED` \| `AUTHORIZED` \| `CLAIMED` \| `COMPLETED` \| `DENIED` \| `PROVIDER_ERROR` \| `EXPIRED` \| `REAUTHORIZE_REQUIRED` \| `MAILBOX_ALREADY_CONNECTED` | — | — |
| `expires_at` | yes | string (date-time) | — | — |
| `oauth_url` | no | string | — | Google authorization URL. Present only on create. |
| `connection_id` | no | string | — | — |
| `email_address` | no | string | — | — |
| `discovery_run_id` | no | string | — | — |
| `failure_code` | no | string | — | — |
| `status_url` | yes | string | — | — |

### GmailOAuthSessionAcceptedResponseDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `data` | yes | GmailOAuthSessionAcceptedDataDto | — | — |

### GmailOAuthSessionDataDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `session_id` | yes | string | — | — |
| `status` | yes | `INITIATED` \| `AUTHORIZED` \| `CLAIMED` \| `COMPLETED` \| `DENIED` \| `PROVIDER_ERROR` \| `EXPIRED` \| `REAUTHORIZE_REQUIRED` \| `MAILBOX_ALREADY_CONNECTED` | — | — |
| `expires_at` | yes | string (date-time) | — | — |
| `oauth_url` | no | string | — | Google authorization URL. Present only on create. |
| `connection_id` | no | string | — | — |
| `email_address` | no | string | — | — |
| `discovery_run_id` | no | string | — | — |
| `failure_code` | no | string | — | — |

### GmailOAuthSessionErrorResponseDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `error` | yes | string | — | — |
| `message` | yes | string | — | — |

### GmailOAuthSessionResponseDto

| Field | Always present | Type | Notes | Description |
| --- | --- | --- | --- | --- |
| `success` | yes | boolean | — | — |
| `data` | yes | GmailOAuthSessionDataDto | — | — |


## See also

- [API reference index](/docs/api.md)
- [Authentication](/docs/guides/authentication.md)
- [Pagination](/docs/concepts/pagination.md)
- [Error handling](/docs/concepts/errors.md)
