> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.askelephant.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.askelephant.ai/_mcp/server.

# Import a call from another platform

**Need AskElephant to handle your import?** Contact **[support@askelephant.ai](mailto:support@askelephant.ai)** to get started. AskElephant-managed imports are approximately **\$0.99 per hour** of recorded calls.

## Overview

You can import a call from any platform that can make an HTTPS request to the AskElephant public API. Use `POST /v2/engagements` to create the engagement and include the call metadata, recording URL, and transcript timeline.

The basic flow is:

1. Collect the call's metadata, recording URL, and transcript from the source platform.
2. Convert the transcript into AskElephant's structured `transcript_timeline` format.
3. Submit the call with `POST /v2/engagements`.
4. Use the returned engagement ID to retrieve the engagement and its AI-generated insights.

**Billing warning:** Please include a `transcript_timeline` with every imported call. Without one, AskElephant will process the call through its transcription process, which may result in a transcription charge on your next invoice at a rate of **\$0.50 per hour** of transcription handled by AskElephant.

## Choose your import mode

You may provide both `media_url` and `transcript_timeline`. The recording is retained for playback, while the supplied timeline is used as the call transcript. If you do not have recording media, you can submit a transcript-only import with `transcript_timeline`.

`transcript_text` is deprecated and should not be used for new imports. It does not replace `transcript_timeline` for a structured import.

## Prerequisites

* An AskElephant API key with the `engagements:write` scope. Add `engagements:read` if you want to retrieve the imported engagement and its insights; private engagements also require `engagements:private:read`.
* A recording URL that AskElephant can access over HTTPS, if importing media
* A transcript with speaker attribution and timestamps, if you want to avoid transcription charges

See [Authentication and scopes](/authentication-and-scopes) for API key details.

## Decide who owns the engagement

The engagement owner is the AskElephant user responsible for the imported call. Ownership affects attribution in AskElephant and determines the user used as the engagement's owner and host during downstream processing. It is separate from the list of attendees.

Mark the owner in the `participants` array:

```json
{
  "name": "Alex Smith",
  "email": "alex@example.com",
  "is_owner": true
}
```

AskElephant resolves an owner participant in this order:

1. Match the participant's `email` to an active AskElephant user in the authenticated workspace.
2. If there is no email match, match the participant's `phone` number.
3. Use the matched user as both the engagement owner and host.

The matched user must be active and invited to the workspace. Users with `INACTIVE` or `NOT_INVITED` roles cannot be selected as the owner.

### If no participant is marked as the owner

When no participant has `is_owner: true`, ownership defaults to the user associated with the API key or OAuth credential. This is usually the right choice when the credential belongs to the person or service account responsible for the import.

An API key or OAuth credential without an associated user can still create an engagement, but only if one participant is marked as `is_owner: true` and resolves to an active workspace user.

### Owner validation rules

* At most one participant can have `is_owner: true`.
* The owner participant must resolve by email or phone to an active workspace user.
* A name alone is not enough to resolve ownership; include the user's email or E.164 phone number.
* If there are multiple owners, or the owner cannot be resolved, the API returns `400`.
* Owner validation happens before media ingestion, engagement persistence, or processing events, so a failed owner claim does not create a partial import.

### Attendees and contacts

Use `participants` to send the people who attended the call. Each participant can include a name, email, and/or E.164 phone number. Include the same identifying details in the transcript timeline's `speakers` when you want AskElephant to match transcript speakers to participants.

If you already have AskElephant contact IDs and want to link those existing contacts after creation, replace the engagement's managed contacts with:

```bash
curl --request PUT \
  --url 'https://app.askelephant.ai/api/v2/engagements/ngmt_01JWAS.../contacts' \
  --header 'Authorization: sk-apik_<id>.<secret>' \
  --header 'Content-Type: application/json' \
  --data '{
    "contact_ids": ["cnt_01CUSTOMER", "cnt_02CUSTOMER"]
  }'
```

This endpoint also requires `engagements:write`. Private engagements additionally require `engagements:private:write`. Use `contacts:read` only when you need to read expanded contact details in a response.

## Create the import

The following request imports a call with its recording and existing transcript:

```bash
curl --request POST \
  --url 'https://app.askelephant.ai/api/v2/engagements' \
  --header 'Authorization: sk-apik_<id>.<secret>' \
  --header 'Content-Type: application/json' \
  --data '{
    "type": "CALL",
    "title": "Discovery call with Jane Doe",
    "source_system": "my-crm",
    "external_id": "my-crm:call_12345",
    "media_url": "https://recordings.example.com/call_12345.mp3",
    "external_url": "https://my-crm.example.com/calls/12345",
    "start_at": "2026-06-12T17:00:00Z",
    "run_workflows": false,
    "participants": [
      { "name": "Alex Smith", "email": "alex@example.com", "is_owner": true },
      { "name": "Jane Doe", "email": "jane@example.com" }
    ],
    "transcript_timeline": {
      "speakers": [
        {
          "index": 0,
          "name": "Alex Smith",
          "email": "alex@example.com"
        },
        {
          "index": 1,
          "name": "Jane Doe",
          "email": "jane@example.com"
        }
      ],
      "entries": [
        {
          "speaker_index": 0,
          "start_seconds": 0,
          "end_seconds": 4.5,
          "sentences": [
            {
              "text": "Thanks for taking the time to meet today.",
              "start_seconds": 0,
              "end_seconds": 4.5
            }
          ]
        },
        {
          "speaker_index": 1,
          "start_seconds": 4.5,
          "end_seconds": 8,
          "sentences": [
            {
              "text": "Happy to be here.",
              "start_seconds": 4.5,
              "end_seconds": 8
            }
          ]
        }
      ]
    }
  }'
```

### Required fields

| Field                                | Description                                                                                                                 |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `type`                               | `CALL` or `MEETING`                                                                                                         |
| `title`                              | Display title for the engagement                                                                                            |
| `source_system`                      | Name of the platform that supplied the call                                                                                 |
| `external_id`                        | Stable ID for the call in the source platform                                                                               |
| `run_workflows`                      | `true` to run normal automations; use `false` for historical imports when automations should not fire                       |
| `media_url` or `transcript_timeline` | At least one is required. Include both when you want recording playback without having AskElephant transcribe the recording |

### Workflow and processing controls

`run_workflows` controls downstream automations, not core engagement processing:

| Value   | Result                                                                                                      |
| ------- | ----------------------------------------------------------------------------------------------------------- |
| `true`  | Normal workflow triggers and recap emails can run. These automations may create additional charges.         |
| `false` | Workflow triggers and recap emails are suppressed. This is usually the safer choice for historical imports. |

Transcription, summarization, and tagging continue to run regardless of `run_workflows`. Suppressing workflows does not suppress core AI processing or remove a transcription charge when no transcript timeline is supplied.

### Prevent workflows and attendee emails

For historical imports, set `run_workflows` to `false` before creating the engagement:

```json
{
  "run_workflows": false
}
```

This prevents workflow triggers and automated meeting summary or recap emails from being sent to people marked as attendees in `participants`. In other words, `run_workflows: false` is the API setting to use when you want `run_automations` turned off for an imported call.

The engagement can still be transcribed, summarized, and tagged. Setting `run_workflows` to `false` only suppresses downstream workflow automation and its related emails.

### Recording URL requirements

When `media_url` is included, it must be a publicly accessible HTTPS URL that serves an audio or video content type. AskElephant fetches the media during request processing, so the URL must be available at the time of the request. Private URLs, internal IP addresses, HTTP URLs, and non-media content types are rejected.

### Transcript timeline format

The timeline has two parts:

* `speakers` declares each speaker. Each speaker needs a unique numeric `index` and a non-empty `name`; email and phone are optional but help match the speaker to an AskElephant participant.
* `entries` contains the transcript turns. Each entry references a speaker with `speaker_index`, and includes offsets in seconds plus one or more `sentences`.

All timestamps are offsets from the start of the engagement, not absolute timestamps. Every `speaker_index` must match a declared speaker, and sentence timestamps must fall within their parent entry.

## Idempotency and retries

`external_id` is the idempotency key within the workspace. Repeating a request with the same `external_id` returns the existing engagement instead of creating a duplicate, so it is safe to retry a timed-out request. If IDs can overlap across source systems, prefix them, for example `my-crm:call_12345`.

The idempotency key is scoped to the workspace, not to `source_system`. The same `external_id` can therefore collide across two platforms unless you include the source in the ID. A new import returns `201`; an idempotent match returns `200` when the engagement can be returned, or `204` when the credential can write but not read it. If the matching engagement is inaccessible, the API returns `404`. If the matching engagement was deleted, the ID can be reused for a new import.

## Timestamps and engagement duration

Set `start_at` to the original call start time as an ISO 8601 timestamp. If you omit it, AskElephant uses the current server time.

When `transcript_timeline` is supplied, the engagement end time is derived from the largest `end_seconds` value in the timeline, even when `media_url` is also present. For media-only imports, the end time is derived from the recording duration during processing. Timeline offsets are relative to the engagement start, not absolute timestamps.

## Response and next steps

On a new import, the API returns `201` and an engagement object containing its AskElephant ID. Save the `id` from the response:

```json
{
  "object": "engagement",
  "id": "ngmt_01JWAS...",
  "title": "Discovery call with Jane Doe",
  "engagement_type": "CALL",
  "processing_status": "PENDING"
}
```

Use the ID to retrieve the engagement after processing:

```bash
curl --request GET \
  --url 'https://app.askelephant.ai/api/v2/engagements/ngmt_01JWAS...?expand=action_items,signals,transcript' \
  --header 'Authorization: sk-apik_<id>.<secret>' \
  --header 'Accept: application/json'
```

For reliable integrations, subscribe to `v1.engagement.transcript_timeline.completed` to know when the transcript is available, then poll the engagement until `processing_status` is `COMPLETED` before retrieving AI-generated insights. The transcript webhook can arrive while analysis and enrichment are still running. See [Webhooks](/webhooks) and the [Engagements API reference](/api-reference/engagements/create-engagement).

## Import checklist

1. Create or use an API key with `engagements:write` and `engagements:read`; add `engagements:private:read` when retrieving private engagements.
2. Choose the import mode: media only, media plus transcript timeline, or transcript only.
3. Use a stable, source-prefixed `external_id`.
4. Set `start_at` to the original call time when available.
5. Add every attendee to `participants`.
6. Mark exactly one attendee as `is_owner: true` when ownership should differ from the API-key user, or when the credential has no associated user.
7. Include `transcript_timeline` whenever possible to avoid transcription charges.
8. Save the returned engagement ID and wait for processing to complete before retrieving insights.

## Common errors

| Status | Meaning                                                                                                                                                   |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Owner participant claims are ambiguous or invalid. This includes multiple owners or an owner who is not an active AskElephant user                        |
| `401`  | API key is missing or invalid                                                                                                                             |
| `403`  | The credential does not have a required scope for the route or requested expansion                                                                        |
| `404`  | The idempotency key matches an engagement the credential cannot access. Private contact updates without `engagements:private:write` may also return `404` |
| `422`  | Request validation failed, or `media_url` is not a publicly accessible HTTPS audio/video URL                                                              |
| `429`  | Rate limit exceeded; retry after the indicated delay                                                                                                      |