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

# Getting started

This page is the [Snapshot API](/reference/snapshot-api/overview) procedure: what to set up on the target Agent by hand, then each call. Every request and response comes from a real run, so you can compare against it.

For what a snapshot carries, the pre-import checks, and the limits, see the [Snapshot API overview](/reference/snapshot-api/overview) and [What a snapshot includes](/reference/snapshot-api/what-a-snapshot-includes).

## Quick start

The shortest safe path, in seven steps. The sections below give the full checklist, each request with a real response, and what to do when a job fails.

#### Get the target Agent ready

Work through the [Prerequisites](#prerequisites): connections, features, tags, secret values, and an export of the target if it has anything on it.

#### Export the source Agent

`GET /v2/config/` with the source key. Poll the job until `completed`, then download `result.url`.

#### Choose what to import

Decide which sections you need, for example `["playbooks"]`. Leave `include` out only when you mean to replace everything.

#### Validate on the target

`PUT /v2/config/` with `{"mode": "validate", "include": [...]}` and the target key. Upload the archive to `upload.url`. Poll until `completed`, then read `result.blockers`, `result.changes`, and `result.selection`.

#### Fix every blocking issue

Each one names its fix. Repeat the validate until `result.status` is `valid`, and read the `delete` counts.

#### Apply

`PUT /v2/config/` with `{"mode": "apply", "include": [...]}` and the target key. Upload the same archive to the new `upload.url`. Poll until `completed`. `apply` is the only mode that writes, and you have to ask for it by name.

#### Finish on the target

Work through [After the apply](#after-the-apply): read `warnings`, enter what the snapshot could not carry, then test before going live.

## Prerequisites

Nothing in this list can be done through the Snapshot API. Work through it in the dashboard before the first validate. Your Ada team does the items marked **Ada** on request.

### Both Agents

* **An API key for each Agent.** Go to **Config > PLATFORM > API keys** and click **New API key**. Name it, then copy the key from the dialog. It is shown once.

  ![The New API key dialog showing the generated key and the Copy button](/_fern-img/8caec63a1aa2a19c00d5656bb56962d96a174963bf3861954d76592cb0d8a306.webp)

### Target Agent, before the first validate

1. **Export the target first** if it has anything on it. That archive is your only way back.

2. **Live-agent platforms connected.** Every platform the source's Handoffs route to is connected and enabled under **Config > AI AGENT > Handoffs > Integrations**. A card that is disabled, or that still shows **Connect**, blocks the import.

   ![The Handoffs page, Integrations tab, with a Zendesk Support card that is disabled](/_fern-img/be74b49cb0c33282cbfe559db26c677ac2253b9a70876e832266f9461c3690cf.webp)

3. **Same accounts as the source.** Under **Config > PLATFORM > Apps**, Zendesk and Salesforce must point at the same subdomain or org the source uses. A different account blocks the import. The checks do not detect the same account with wrong credentials; check it yourself.

   ![The Apps page with a Zendesk card and its Connect button](/_fern-img/cd82efc8d990303c2c6e2b32964392b2c91d89074517028f915e7e5bc5fcc0a8.webp)

4. **Integrations enabled and connected.** Every integration the source's API tools or Handoffs call is both enabled and connected on **Apps**. Enabled without credentials blocks the import.

5. **Customer login set up**, if any API tool on the source signs end users in. It does not travel, and the checks do not report it. Set it up under **Config > AI AGENT > Tools > Manage tokens**. After the import, edit each API tool that used it to select the new token.

6. **Knowledge sources reconnected**, pointing at the same knowledge base as the source, so imported articles keep syncing. Web import sources need nothing; their crawl settings travel.

7. **Knowledge tags created.** Every tag the source's articles use must exist on the target with the same id. Create them through the Knowledge API. An article whose tag is missing fails to import. A same-Agent restore is unaffected.

8. **Features match the source (Ada).** Ask your Ada team to enable on the target whatever is enabled on the source. A missing feature blocks the import when the checks can see it. When they cannot, the import succeeds and the Agent misbehaves later.

9. **Secret values at hand.** Read `result.secrets` on the source export job for the names. These are the static tokens your API tools use, under **Config > AI AGENT > Tools > Manage tokens**. Have each value ready for the apply request.

10. **Room for the snapshot.** The target's limits on enabled API tools, live Custom instructions, Glossary terms, and articles must fit what the snapshot carries.

## Authenticate

Every call except the archive upload carries the API key of the Agent it targets. The examples below use `EXAMPLE.ada.support` and an `ADA_API_KEY` environment variable; substitute your Agent's host and key.

```bash
export ADA_BASE="https://EXAMPLE.ada.support/api"
export ADA_API_KEY="your_api_key"
```

## Export a snapshot

An export starts a background job and returns its id. The archive becomes available on the job once it completes. An export always captures the whole Agent; you choose what to import.

**To export an AI Agent:**

1. Start the export.

   ```bash
   curl -sS -X GET "$ADA_BASE/v2/config/" \
     -H "Authorization: Bearer $ADA_API_KEY"
   ```

   ```json
   {"job_id": "config_job_ccc3574a9b9342dbac32c50c0a656b93", "status": "queued"}
   ```

   If an export is already running for this Agent, the response carries that job's id and its current status, which may already be `in_progress`. Poll the `job_id` either way.

2. Poll the job until `status` is `completed`.

   ```bash
   curl -sS "$ADA_BASE/v2/config/jobs/config_job_ccc3574a9b9342dbac32c50c0a656b93" \
     -H "Authorization: Bearer $ADA_API_KEY"
   ```

   ```json
   {
     "job_id": "config_job_ccc3574a9b9342dbac32c50c0a656b93",
     "type": "export",
     "status": "completed",
     "progress": 100,
     "result": {
       "url": "https://…/config_20260904-145213.tar.gz?…",
       "expires_at": "2026-09-04T15:52:13Z",
       "secrets": {}
     },
     "warnings": null,
     "failure_reason": null,
     "failure_detail": null
   }
   ```

3. Download the archive with `curl`, or with a browser that keeps the file as `.tar.gz`. Safari unpacks the file and keeps the name, and the import then rejects it.

   ```bash
   curl -sS -f -o snapshot.tar.gz "PASTE result.url HERE"
   ```

   `-f` makes curl fail on an expired link instead of saving the error page as your snapshot. If the link has expired, poll the job again for a fresh one.

4. Note the names under `result.secrets`. These are the secret values the target must have; you supply them in the apply request.

Keep the archive somewhere safe. The job record, and with it the download link, is gone after 7 days.

## Choose what to import

The import request takes an optional `include` list. Name the sections you want, and the import brings along whatever those sections reference. Leave `include` out to import the whole snapshot.

The section names, and what each import replaces or only adds to, are under [Choosing sections](/reference/snapshot-api/overview#choosing-sections).

A name that is not a section is refused before any upload:

```json
{
  "errors": [{
    "type": "validation_error",
    "message": "Request body validation error",
    "details": [{
      "parameter": "$.include[0]",
      "location": "body",
      "message": "'not_a_section' is not one of ['persona', 'variables', 'actions', …]"
    }]
  }]
}
```

The examples below import Playbooks only. To import everything, leave out `include` and read every `delete` count before you apply.

## Validate a snapshot

A validate reads the target and reports what an apply would do, without writing anything. The one exception is `secrets`: values you send are stored on the Agent at once, so leave it empty until the apply. A validate is also what an import does when you send no `mode`.

**To validate a snapshot against the target Agent:**

1. Request the import in validate mode, using the **target** Agent's host and key.

   ```bash
   curl -sS -X PUT "$ADA_BASE/v2/config/" \
     -H "Authorization: Bearer $ADA_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"mode": "validate", "include": ["playbooks"]}'
   ```

   ```json
   {
     "job_id": "config_job_97fade03e7ac4a5f88eaa23227e68aa3",
     "status": "waiting_for_upload",
     "upload": {
       "url": "https://…/upload.tar.gz?…",
       "method": "PUT",
       "expires_at": "2026-09-04T15:52:59Z"
     }
   }
   ```

2. Upload the archive to `upload.url`. No Authorization header, no content type, the raw file in one request.

   ```bash
   curl -sS -f -X PUT -T snapshot.tar.gz "PASTE upload.url HERE"
   ```

   A `200` with an empty body means the upload succeeded. Check the status code yourself; the job cannot see this request. If no archive arrives, the job fails after about 90 minutes with `upload_not_received`. If the upload failed, request the import again. A job still waiting for its archive is replaced, not refused, and ends as `failed` with `superseded`.

3. Poll the job until `status` is `completed`. Allow up to two minutes in `waiting_for_upload` after the upload arrives.

   ```json
   {
     "status": "completed",
     "result": {
       "status": "valid",
       "source": "s3://…/upload.tar.gz",
       "source_bot_handle": "EXAMPLE",
       "articles": {"found": 0},
       "selection": {
         "requested": ["playbooks"],
         "included": ["actions", "playbooks", "variables"],
         "pulled": {"actions": 1, "variables": 1},
         "unresolved": []
       },
       "blockers": [],
       "warnings": [],
       "changes": {
         "variables": {"create": 0, "update": 1, "delete": 0, "created": [], "updated": ["order_id"], "deleted": []},
         "actions": {"create": 0, "update": 1, "delete": 0, "created": [], "updated": ["Get order status"], "deleted": []},
         "playbooks": {"create": 0, "update": 1, "delete": 0, "created": [], "updated": ["Order status"], "deleted": []}
       }
     },
     "warnings": [],
     "failure_reason": null,
     "failure_detail": null
   }
   ```

   This example is a same-Agent restore of one Playbook. `selection` shows that the Playbook pulled in one API tool and one variable, and `changes` names them. Nothing is created or deleted.

4. Read the result. `status: blocked` with entries in `blockers` means the apply would be refused for the same reasons. Fix each one on the target (see [Pre-import checks](/reference/snapshot-api/overview#pre-import-checks)) and validate again. Read `warnings`, `changes`, and `selection.unresolved` before you apply. Every non-zero `delete` count is a real deletion the apply will perform.

**To validate the whole snapshot**, send the same request without `include`. The result then has `selection: null` and a `changes` entry for every section, including the articles:

```json
"changes": {
  "variables": {"create": 0, "update": 4, "delete": 0, "created": [], "updated": ["email", "address", "full_name", "order_id"], "deleted": []},
  "actions": {"create": 0, "update": 4, "delete": 0, "created": [], "updated": ["Get account balance", "Get recent transactions", "Request new card", "Get order status"], "deleted": []},
  "handoffs": {"create": 0, "update": 1, "delete": 0, "created": [], "updated": ["Handoff"], "deleted": []},
  "custom_instructions": {"create": 0, "update": 3, "delete": 0, "created": [], "updated": ["Confirm if you've helped", "Explaining financial terms", "Order status guidance"], "deleted": []},
  "knowledge.sources": {"create": 0, "update": 1, "delete": 0, "created": [], "updated": ["Created in Ada"], "deleted": []},
  "knowledge.articles": {"create": 0, "update": 5, "delete": 0, "created": [], "updated": ["Understanding Our Fees", "Setting Up Account Alerts", "…"], "deleted": []}
}
```

`knowledge.articles.delete` is the number to check before applying to an Agent with live Knowledge.

## Apply a snapshot

An apply makes the changes. Run it only after a validate with the same archive and the same `include` returned `valid`, and you have read its warnings and change counts.

**To apply a snapshot to the target Agent:**

1. Request the import in apply mode, with the same `include`. Add the secret values the source export listed.

   ```bash
   curl -sS -X PUT "$ADA_BASE/v2/config/" \
     -H "Authorization: Bearer $ADA_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"mode": "apply", "include": ["playbooks"], "secrets": {"crm_api_token": "the-value"}}'
   ```

   The response has the same shape as the validate request, with a new `job_id` and a new `upload.url`.

2. Upload the same archive again, to the new link. The link from the validate round cannot be reused.

   ```bash
   curl -sS -f -X PUT -T snapshot.tar.gz "PASTE the new upload.url HERE"
   ```

3. Poll the job until `status` is `completed` or `failed`.

   ```json
   {
     "status": "completed",
     "result": {
       "source": "s3://…/upload.tar.gz",
       "source_bot_handle": "EXAMPLE",
       "articles": {"upserted": 0, "deleted": 0},
       "indexing": "backfill_enqueued",
       "selection": {
         "requested": ["playbooks"],
         "included": ["actions", "playbooks", "variables"],
         "pulled": {"actions": 1, "variables": 1},
         "unresolved": []
       },
       "post_apply_blockers": []
     },
     "warnings": [],
     "failure_reason": null,
     "failure_detail": null
   }
   ```

   `articles` is zero because `knowledge` was neither requested nor pulled in. `post_apply_blockers` empty is the healthy answer.

4. Finish on the target: work through [After the apply](#after-the-apply).

## After the apply

* Read `warnings` on the job. Each names a section and what to do.
* Enter what the snapshot could not carry: secret values not passed in the request, `Authorization` headers on API tools, and tags. Reconnect MCP servers and re-select them in Playbook steps. Re-select any off-hours response a warning flagged.
* Add conversation start events to imported Playbooks. They do not travel. The "When to use this Playbook" text does.
* Confirm connected Knowledge sources are syncing, and allow time for search to catch up.
* Run test conversations before anything goes live.

## When a job fails

One endpoint reports every job. `failure_reason` is a short code; `failure_detail` is a sentence you can show someone. The most common failures:

| You see                                                                 | Meaning                                                                                                                             | Do                                                                                                                                                                             |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `failed`, `failure_reason: "preflight_failed"`                          | The pre-import checks refused the apply. `result.blockers` lists each issue.                                                        | Fix them on the target, then request a new import. The same request without fixes fails again. Check **Apps**: integrations the snapshot lists were enabled before the checks. |
| `failed`, `failure_reason: "invalid_snapshot"` or `"source_unreadable"` | The archive is malformed, truncated, or not a snapshot. Nothing was written.                                                        | Read `failure_detail`, re-export, then import the new archive.                                                                                                                 |
| `failed`, `failure_reason: "superseded"`                                | You requested the import again while this job was waiting for its archive.                                                          | Poll the newer job. Expected, not an error.                                                                                                                                    |
| `failed`, `failure_reason: "timeout"`                                   | The job hit its time limit before finishing.                                                                                        | Request the same import again and upload the same archive. It resumes where it stopped.                                                                                        |
| `failed`, `failure_reason: "incomplete_knowledge"`                      | The configuration sections were applied, including their deletions. The Knowledge step did not finish, so no articles were deleted. | Request the same import again and upload the same archive. It resumes where it stopped.                                                                                        |
| `failed`, `failure_reason: "partially_applied"`                         | The apply stopped after some configuration was written.                                                                             | Do not simply retry. Check the Agent in the dashboard, then import an archive you know is good.                                                                                |
| `failed`, `failure_reason: "archive_expired"`                           | The export's archive has passed its retention.                                                                                      | Export again.                                                                                                                                                                  |
| `progress: 100` but `status` is not `completed`                         | Final steps still running.                                                                                                          | Keep polling. Only `status` means done.                                                                                                                                        |

The full list of codes is under [Failure reasons](/reference/snapshot-api/overview#failure-reasons). HTTP errors on the request itself, such as `409`, are listed under [Error responses](/reference/snapshot-api/overview#error-responses).

## Move a snapshot between regions

An archive exported from an Agent in one region imports into an Agent in another. Download it from the first Agent's host and upload it to the second Agent's upload link. The two regions need no access to each other. Everything in [Prerequisites](#prerequisites) applies unchanged.

## Choose a tool

The calls above are plain HTTP and work from any client. Two guided paths:

* [Export and import with Postman](/reference/snapshot-api/export-and-import-with-postman): build a collection from the Agent's own API specification, then run the three requests from its `config` folder.
* [Export and import with an AI assistant](/reference/snapshot-api/export-and-import-with-an-ai-assistant): one prompt that runs the whole flow with confirmation before anything is written.