> For the complete documentation index, see [llms.txt](https://ask.birdie.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ask.birdie.ai/integrations-and-data-ingestion/ingestion-api.md).

# Ingestion API

The ingestion API can be used to upload data to Birdie directly. Clients upload data by making an HTTP PUT for each record they want to upload.

## Base URL

Birdie has two regional environments. Use the base URL that matches the region where your Birdie account is hosted:

| Region        | Base URL                   |
| ------------- | -------------------------- |
| United States | `https://api.birdie.ai`    |
| Brazil        | `https://api.br.birdie.ai` |

All endpoint paths in this page are relative to the base URL of your region. The examples below use `{BASE_URL}` as a placeholder, replace it with the base URL of your region.

## Authentication

The request must include a Birdie API key in an HTTP header in the form:

```
Authorization: ApiKey {api-key}
```

To generate an API key, see [How to generate an API Key](/integrations-and-data-ingestion/how-to-generate-an-api-key.md).

## API Responses and limitations

Response status codes returned by the API:

* 201 Created: Request was accepted and will be processed.
* 400 Bad Request: if the request body is invalid (response body will be a JSON object with an error message).
* 401 Unauthorized: if the API key is missing or invalid.
* 500 Internal Server Error: unexpected error, contact the Birdie team.
* 502 Bad Gateway: API unavailable, contact the Birdie team.
* 503 Service Unavailable: if too many requests are being made in a short amount of time.

{% hint style="danger" %}
We suggest making up to 100 requests per second (6000 per minute) to the Birdie API to avoid 5XX errors.
{% endhint %}

The payload / row for any type of event (e.g an entire conversation upload with all its messages, an entire feedback, or an entire account upload) will be serialized into a JSON object by Birdie, which has a size limit of 10MB.

{% hint style="danger" %}
**IMPORTANT**: Payloads larger than 10MB (with the exception of [audios](#audios-endpoint)) will error out and not be ingested.
{% endhint %}

## Uploadable data types

The Birdie API supports all data types documented under our [Data Schema definitions](/integrations-and-data-ingestion/data-schema-definitions-for-birdie-ingestion.md), which include:

* Conversations (and messages).
  * Audios for conversation transcription.
* Feedbacks.
* Accounts (Optional).

For more details on their definitions, why, how and when to use them, please refer to the [linked documentation](https://ask.birdie.ai/integrations-and-data-ingestion/pages/5Z8b5BkvkdE2oYhrsKO8#id-2.-understanding-data-types).

## JSON formats

Most ingestion endpoints accept JSON payloads.

Audio ingestion is the exception and uses `multipart/form-data` because it contains both structured metadata and binary recording content.

Schema definitions for JSON payloads are available [here](https://api.birdie.ai/ingestion/schemas), and contain a [JSON schema](https://json-schema.org/) representation of each payload. These are NOT the payloads. For sample payloads, please continue reading this documentation, as it will be presented in later sections.

Each feedback or conversation (and therefore messages and audios) request must include the kind. For the definition of a kind, check the [Data Schema documentation](https://ask.birdie.ai/integrations-and-data-ingestion/pages/5Z8b5BkvkdE2oYhrsKO8#id-2.-understanding-data-types). For the ingestion API, a kind is basically represented via a nested JSON object containing the kind name (e.g `nps`, `csat`, etc.) and an object of kind values. We have multiple kinds, which can be listed here: <https://api.birdie.ai/ingestion/schemas>

You will need to analyze your source of data and see which record type (so Conversation vs Feedbacks) and which [Kind](https://ask.birdie.ai/integrations-and-data-ingestion/pages/5Z8b5BkvkdE2oYhrsKO8#id-3.-data-schemas) best fits your data.

## Common fields across data types

* Path parameters in endpoints are identifiers for records. They should be strings (alpha-numerical suggested) and must be unique for records that co-exist in your Birdie account.
* Common fields across record types:
  * additional\_fields: lets clients upload any fields that don't fit the pre-defined schema
    * These fields can be mapped to custom fields in Birdie. Refer to the [Custom fields](https://ask.birdie.ai/integrations-and-data-ingestion/pages/5Z8b5BkvkdE2oYhrsKO8#id-4.-custom-fields) documentation for more details.
  * batch\_id: optional field to group uploaded records into a batch, which can later be visualized as tasks to track data ingestion. Guidelines:
    1. Upload all records with the same batch\_id within 24 hours.
    2. Avoid using a different batch\_id for each record (this would create too many batches).

## Endpoints

There are six endpoints to upload different types of data.

### Conversations and messages endpoints

```http
PUT /ingestion/conversations/{conversation-id}
PUT /ingestion/conversations/{conversation-id}/messages/{message-id}
```

Use to create or update a conversation and its messages. Updates overwrite the entire record (no partial updates).

A [conversation](/core-concepts-and-entities/feedbacks.md#why-conversations-are-a-special-type-of-feedback) is an identifier of a discussion between one or more users (e.g., a support ticket or a GitHub issue). A message is a part of a conversation that is written by a person participating in the conversation at some point in time.

Conversations and messages must be uploaded together (we suggest uploading the conversation first, then its messages). Conversations don't appear inside Birdie if they don't have an associated conversation message and vice-versa. Birdie calculates a summary for each conversation based on its messages, which is shown in the feed and can be expanded to see individual messages.

The `conversation-id` in a message path must match a conversation that has been uploaded separately.

#### Example 1 - Conversation of kind support\_ticket

Upload the conversation:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/123 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "support_ticket",
      "fields": {
        "subject": "Account reset",
        "status": "open",
        "priority": "normal",
        "channel": "email"
      }
    },
    "additional_fields": {
      "ticket_opened_at": "2024-01-01T00:00:00Z"
    }
  }'
```

{% endcode %}

Then upload its first message, posted by an agent:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/123/messages/456 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "support_ticket",
      "fields": {
        "author_id": "author-1",
        "author_name": "John Doe",
        "author_type": "Agent"
      }
    },
    "text":"Hello, My name is John! Im here to assist you today.",
    "posted_at":"2024-01-01T00:00:00Z",
    "additional_fields": {
      "high_priority": true
    }
  }'
```

{% endcode %}

And the [survey answer](/core-concepts-and-entities/feedbacks.md#ticket-satisfaction-survey) as a dedicated message, posted by the customer. Only this message should have the survey fields. The fields need to follow specific requirements:

* `survey_type` must be `csat` or `nps`, in lower case.
* `rating` must be between 0 and 10 for NPS and between 1 and 5 for CSAT.
* `solved` must be a boolean.

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/123/messages/789 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "support_ticket",
      "fields": {
        "author_id": "customer-1",
        "author_name": "Jane Doe",
        "author_type": "User",
        "survey_title": "How satisfied are you with our support?",
        "survey_type": "csat",
        "rating": 5,
        "solved": true
      }
    },
    "text": "Quick and helpful answer, thanks!",
    "posted_at": "2024-01-01T01:00:00Z"
  }'
```

{% endcode %}

#### Example 2 - Conversation of kind issue

Upload the conversation:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/issue-1 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "issue",
      "fields": {
        "project_id": "birdie/repository",
        "project_name": "repository",
        "status": "open",
        "title": "Doesnt work after update 1.01"
      }
    },
    "additional_fields": {
      "issue_opened_at": "2024-01-01T00:00:00Z",
      "repository_tags": ["repo"]
    }
  }'
```

{% endcode %}

Then upload the first message, posted by a user:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/issue-1/messages/issue-msg-1 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "issue",
      "fields": {
        "author_id": "engineer123",
        "author_name": "John"
      }
    },
    "text":"Since the last update, I cant get the service to work!",
    "posted_at":"2024-01-01T10:00:00Z",
    "additional_fields": {
      "thumbs_up": 5,
      "thumbs_down": 3
    }
  }'
```

{% endcode %}

And the second message, posted by another user:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/issue-1/messages/issue-msg-2 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "issue",
      "fields": {
        "author_id": "contributor456",
        "author_name": "Josh"
      }
    },
    "text":"Try rolling back to the last version, that worked for me",
    "posted_at":"2024-01-01T10:15:00Z",
    "additional_fields": {
      "thumbs_down": 10
    }
  }'
```

{% endcode %}

#### Example 3 - Conversation of kind complaint

Upload the conversation:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/complaint-1 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "complaint",
      "fields": {
        "category": "Delivery",
        "status": "solved",
        "url": "https://example.com/complaints/12345",
        "rating": 3
      }
    }
  }'
```

{% endcode %}

Then upload its message:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/complaint-1/messages/complaint-msg-1 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "complaint",
      "fields": {
        "account_id": "acct-456",
        "author_id": "customer@example.com",
        "author_name": "Jane Doe",
        "author_type": "User"
      }
    },
    "text": "My order has not arrived and it has been 15 days already.",
    "posted_at": "2024-02-10T08:00:00Z"
  }'
```

{% endcode %}

#### Example 4 - Conversation of kind social\_media\_post

Upload the conversation:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/reddit-post-1 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "social_media_post",
      "fields": {
        "title": "Search broken after update",
        "channel": "reddit",
        "owner": "Owner",
        "category": "r/BirdieApp",
        "tags": ["bug"],
        "url": "https://reddit.com/r/BirdieApp/post/001"
      }
    }
  }'
```

{% endcode %}

Then upload its message:

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/conversations/reddit-post-1/messages/reddit-msg-1 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "name": "social_media_post",
      "fields": {
        "author_id": "user@example.com",
        "author_name": "Jane Doe",
        "author_type": "User",
        "upvotes": 22
      }
    },
    "text": "The new update broke the search feature.",
    "posted_at": "2024-04-01T12:00:00Z"
  }'
```

{% endcode %}

### Feedbacks endpoint

```http
PUT /ingestion/feedbacks/{feedback-id}
```

Use to create or update feedbacks. Updates overwrite the entire record (no partial updates).

A single [feedback](/core-concepts-and-entities/feedbacks.md) record posted by a user (e.g., NPS response or app review). Feedbacks are displayed alongside conversation summaries in the feed.

#### Example 1 - Feedback of kind review

The `rating` of a `review` feedback must be between 0 and 5.

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/feedbacks/abcd-123 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "posted_at": "2024-01-01T00:00:00Z",
    "text": "Excellent app",
    "kind": {
      "name": "review",
      "fields": {
        "owner": "Owner",
        "rating": 5.0
      }
    },
    "additional_fields": {
      "author": "John Doe",
      "device": "Android"
    }
  }'
```

{% endcode %}

#### Example 2 - Feedback of kind nps

The `rating` of a `nps` feedback must be between 0 and 10.

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/feedbacks/xyzw-451 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "posted_at": "2024-01-01T00:00:00Z",
    "text": "Im always recommending this service to my friends!",
    "kind": {
      "name": "nps",
      "fields": {
        "author_id": "john123",
        "author_name": "John Doe",
        "account_id": "companyABC",
        "title": "[NPS 2024] How Likely are you to recommend Birdie?",
        "rating": 10
      }
    },
    "additional_fields": {
      "classification": "promoter"
    }
  }'
```

{% endcode %}

#### Example 3 - Feedback of kind csat

The `rating` of a `csat` feedback must be between 1 and 5.

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/feedbacks/csat-555 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "posted_at": "2024-01-01T00:00:00Z",
    "text": "The agent was very helpful and quick.",
    "kind": {
      "name": "csat",
      "fields": {
        "author_id": "john123",
        "author_name": "John Doe",
        "account_id": "companyABC",
        "title": "How satisfied are you with our support?",
        "rating": 5
      }
    },
    "additional_fields": {
      "ticket_id": "123"
    }
  }'
```

{% endcode %}

#### Example 4 - Feedback of kind ces

The `rating` of a `ces` feedback must be between 1 and 7.

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/feedbacks/ces-789 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "posted_at": "2024-01-01T00:00:00Z",
    "text": "It was easy to get my issue solved.",
    "kind": {
      "name": "ces",
      "fields": {
        "author_id": "john123",
        "author_name": "John Doe",
        "account_id": "companyABC",
        "title": "How easy was it to solve your issue?",
        "rating": 6
      }
    },
    "additional_fields": {
      "channel": "chat"
    }
  }'
```

{% endcode %}

#### Example 5 - Feedback of kind survey

A `survey` feedback has no rating. Use it for open-ended survey answers that don't fit the `nps`, `csat` or `ces` kinds.

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/feedbacks/survey-321 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "posted_at": "2024-01-01T00:00:00Z",
    "text": "I would like to see more integrations with other tools.",
    "kind": {
      "name": "survey",
      "fields": {
        "author_id": "john123",
        "author_name": "John Doe",
        "account_id": "companyABC",
        "title": "[Product Survey 2024] What could we improve?"
      }
    },
    "additional_fields": {
      "question_id": "q-3"
    }
  }'
```

{% endcode %}

### Audios endpoint

```http
PUT /ingestion/audios/{recording-id}
```

This endpoint is used to upload (create or update) an Audio.

An Audio represents a recording file and the metadata required to process it. Birdie transcribes it into a [conversation](/core-concepts-and-entities/feedbacks.md#why-conversations-are-a-special-type-of-feedback).

Both components must be submitted together in the same request.

The request must use `multipart/form-data` and contain:

* A `metadata` part containing a JSON payload.
* A `file` part containing the audio recording.
  * Supported recording formats depend on the transcription pipeline. In general, any common audio-only format is expected to work, including:
    * WAV
    * MP3
  * If you intend to use a different format, please reach out to the Birdie team so compatibility can be validated during implementation.
  * The API is intended for recordings up to approximately 100 MB.

The multipart request establishes the correlation between the recording and its metadata, and the `recording-id` uniquely identifies the Audio.

No additional upload, synchronization or correlation step is required from clients.

If the same `recording-id` is submitted again, the existing record will be updated following the same semantics used by the other ingestion endpoints.

Example request:

{% code title="curl" %}

```bash
    curl {BASE_URL}/ingestion/audios/call-123 \
    -H 'Authorization: ApiKey {api-key}' \
    -X PUT \
    -F 'metadata={
            "recorded_at":"2024-01-01T10:00:00Z",
            "kind": {
                "name": "support_ticket",
                "fields": {
                    "subject": "Billing dispute",
                    "status": "solved",
                    "priority": "normal"
                }
            },
            "account_id": "account-111",
            "agent": {
                "id": "agent-123",
                "supervisor_id": "sup-456",
                "company": "acme",
                "team": "customer-support",
                "experience": "1-3 years"
            },
            "additional_fields":{
                "queue":"billing"
            }
        };type=application/json' \
    -F 'file=@call.wav'
```

{% endcode %}

#### Response

A successful request returns:

{% code title="http" %}

```http
201 Created
```

{% endcode %}

This response indicates that the Audio Record was accepted for processing.

It does not indicate that transcription has completed.

#### Validation Errors

The request will be rejected with:

{% code title="http" %}

```http
400 Bad Request
```

{% endcode %}

if:

* The `metadata` part is missing.
* The `recording` part is missing.
* The metadata payload is an invalid JSON.
* Required metadata fields are missing.

### Accounts endpoint

```
PUT /ingestion/audience/accounts/{account-id}
```

Use to create or update an account. Updates overwrite the entire record (no partial updates).

[Accounts](/core-concepts-and-entities/accounts.md) are optional records to further segment feedbacks and conversations. Use [`account_id`](/integrations-and-data-ingestion/data-schema-definitions-for-birdie-ingestion.md#accounts) fields inside the kind fields of feedbacks or conversation messages. You can upload accounts before or after uploading feedbacks/conversations.

For the account fields to register, you must have set up the additional fields as custom fields (See the [Custom fields](https://ask.birdie.ai/integrations-and-data-ingestion/pages/5Z8b5BkvkdE2oYhrsKO8#id-4.-custom-fields) documentation).

#### Example - Match uploaded NPS Feedback

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/audience/accounts/companyABC \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "additional_fields": {
      "key_account": true
    }
  }'
```

{% endcode %}

### Lookup endpoint

```http
PUT /ingestion/lookup/{lookup-type}/{lookup-id}
```

Use to create or update a lookup record for [Field Lookups](/integrations-and-data-ingestion/field-lookups.md). Updates overwrite the entire record (no partial updates).

The `lookup-id` is the value matched against the incoming feedbacks and conversations, e.g. the account ID used by one of your sources. It can only contain letters, numbers, hyphens (`-`) and underscores (`_`), with up to 1000 characters.

Lookup records are only applied to records ingested after they are uploaded, so upload them before the feedbacks and conversations that depend on them. The lookup itself must be [set up by the Birdie team](/integrations-and-data-ingestion/field-lookups.md#setting-up-a-lookup) before the lookup records have any effect.

Supported lookup types:

* `account_identifier`: the body must only contain the new account ID, in `kind.fields.account_id`. Other fields, including the common `additional_fields` and `batch_id`, are rejected.

#### Example - Normalize a Zendesk account ID

{% code title="curl" %}

```bash
curl {BASE_URL}/ingestion/lookup/account_identifier/zd-881 \
  -H 'Authorization: ApiKey {api-key}' \
  -H 'content-type: application/json' \
  -X PUT -d '{
    "kind": {
      "fields": {
        "account_id": "acct-123"
      }
    }
  }'
```

{% endcode %}

Feedbacks and conversations ingested with the account ID `zd-881` are then stored with `acct-123`.

If the account `acct-123` is uploaded, account analytics will become available.

#### Response

A successful request returns:

{% code title="http" %}

```http
200 OK
```

{% endcode %}

#### Validation Errors

The request will be rejected with:

{% code title="http" %}

```http
400 Bad Request
```

{% endcode %}

if:

* The lookup type is not supported.
* The `lookup-id` contains unsupported characters or is longer than 1000 characters.
* The body is an invalid JSON.
* The body doesn't match the fields required by the lookup type.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://ask.birdie.ai/integrations-and-data-ingestion/ingestion-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
