> ## Documentation Index
> Fetch the complete documentation index at: https://developer.dromo.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Headless Quickstart

> The Headless API allows you to import CSVs and spreadsheet files via an API

With Headless, you can send a file or JSON payload to Dromo and specify a [schema](/#defining-a-schema) that defines the desired shape and constraints of the final data file.

If the import is successful, Dromo returns cleaned data in JSON format, just like an embedded import. If there are issues (e.g., missing columns or validation errors), Dromo provides a URL for users to resolve them via the embedded interface.

## Prerequisite[​](#prerequisite "Direct link to Activation")

The Headless API is a premium add-on to the [Dromo Enterprise Plan](https://dromo.io/pricing), [get in touch](mailto:support@dromo.io) and we can get you ready to go!

These limits help ensure service stability. For high-volume use cases, we recommend using webhooks instead of polling to avoid hitting rate limits.

## Using the Headless Importer[​](#using-the-headless-importer "Direct link to Using the Headless Importer")

A headless import can be run either on a file or a JSON object. The steps below walk through the procedure for either type of import.

You will need a schema to guide the import — either a saved schema referenced by `schema_id`, or an inline schema defined with `fields` in the request body. For more information about saved schemas, check out the [Schema Studio Guide](/guides/schema-builder) or the [Saved Schema API](/guides/saved-schema-api).

### Importing a File

<Steps>
  <Step title="Create a New Headless Import">
    The import process starts by creating a new headless import record. You will use this record ID to check the status of the import and retrieve the results.

    To create a headless import, use the [create headless import endpoint](/api-reference/headless/create-a-new-headless-import).

    You must provide two things when creating the import:

    * The ID of the saved schema you want to use
    * The original filename of the file you want to import

    Here is an example request:

    ```sh theme={null}
    curl \
      -H "Content-Type: application/json" \
      -H "X-Dromo-License-Key: your-backend-key" \
      -X POST \  'https://app.dromo.io/api/v1/headless/imports/' \
      -d '{"schema_id": "4eefe3fe-7181-4562-aa28-8cfe1c5bdf1b", "original_filename": "data.csv"}'
    ```

    <Note>Each headless import allows you to import only one file.</Note>
  </Step>

  <Step title="Upload the File">
    Once you've created the import record, Dromo will return an ID and a URL (in the `"upload"` field) where you can upload the file. Store the ID for later use (you need it to fetch the import status and results).

    To upload the file, make a PUT request to the URL, with the file as the body. Note that the upload URL is only valid for 30 minutes, so only create the import record when you are ready to upload the file.

    Here is an example request for uploading the file:

    ```sh theme={null}
    curl \
      -X PUT \'https://dromo-headless-imports-prod.s3.us-west-2.amazonaws.com/.....' \
      --data-binary "@/path/to/data.csv"
    ```
  </Step>

  <Step title="Wait for the Import to Run">
    Dromo will automatically begin the import process once the upload is complete.
  </Step>
</Steps>

### Importing a JSON Object

<Steps>
  <Step title="Create a New Headless Import">
    The import process starts by creating a new headless import record. You will use this record to check the status of the import and retrieve the results.

    To create a headless import, use the [create headless import endpoint](/api-reference/headless/create-a-new-headless-import).

    You must provide two things when creating the import:

    * The ID of the saved schema you want to use
    * The complete JSON data

    Here is an example request:

    ```sh theme={null}
    curl \
      -H "Content-Type: application/json" \
      -H "X-Dromo-License-Key: your-backend-key" \
      -X POST \  'https://app.dromo.io/api/v1/headless/imports/' \
      -d '{    "schema_id": "4eefe3fe-7181-4562-aa28-8cfe1c5bdf1b",    "initial_data": '[{"product": "widget1", "transaction": "foo123"}, {"product": "widget2", "transaction": "foo456"}]'  }'
    ```
  </Step>

  <Step title="Wait for the Import to Run">
    Dromo will automatically begin the import process once the upload is complete.
  </Step>
</Steps>

## Inline Schema Imports

Instead of referencing a saved schema via `schema_id`, you can define the schema inline at import time by passing `fields` directly in the request body. This is useful when you want to generate the schema dynamically or avoid managing saved schemas in the dashboard.

`schema_id` and `fields` are mutually exclusive — you must provide one or the other.

### Importing with an Inline Schema

Pass `fields` (required), `settings` (optional), and `hooks` (optional) in place of `schema_id`. Use `original_filename` when importing a file, or `initial_data` when importing JSON directly:

```sh theme={null}
curl \
  -H "Content-Type: application/json" \
  -H "X-Dromo-License-Key: your-backend-key" \
  -X POST \
  'https://app.dromo.io/api/v1/headless/imports/' \
  -d '{
    "fields": [
      { "key": "first_name", "type": "string", "label": "First Name", "validators": [] },
      { "key": "last_name", "type": "string", "label": "Last Name", "validators": [] }
    ],
    "initial_data": [
      { "first_name": "Zeph", "last_name": "paul.zeph@gmail.com" }
    ],
    "hooks": {
      "rowHooks": [
        "(record, mode) => { return { row: { first_name: { value: \"Updated from row hook\" } } }; }"
      ]
    }
  }'
```

The `fields` array accepts the same field definitions as a saved schema. See the [Field Object Reference](/reference/fields/fields) for the full list of options.

### Overriding Settings or Hooks on a Saved Schema

If you provide a `schema_id` alongside `settings` or `hooks`, Dromo will merge them on top of the saved schema for that specific import — without modifying the schema itself. This is useful for one-off overrides like changing validation behavior or injecting runtime hooks.

```sh theme={null}
curl \
  -H "Content-Type: application/json" \
  -H "X-Dromo-License-Key: your-backend-key" \
  -X POST \
  'https://app.dromo.io/api/v1/headless/imports/' \
  -d '{
    "schema_id": "4eefe3fe-7181-4562-aa28-8cfe1c5bdf1b",
    "settings": {
      "allowInvalidSubmit": true
    },
    "original_filename": "data.csv"
  }'
```

<Note>
  When overriding, `settings` and `hooks` are merged on top of the saved schema's values. Any keys you provide will overwrite the corresponding keys on the saved schema; keys you omit are left unchanged.
</Note>

## Handling Import Results[​](#handling-import-results "Direct link to Handling Import Results")

You can query for the current import status using the [retrieve headless import endpoint](/api-reference/headless/retrieve-headless-import). The import status will be one of the following:

* `AWAITING_UPLOAD`: The import record has been created, but the import file has not yet been uploaded.
* `PENDING`: The import file has been received, and the import process will begin shortly.
* `RUNNING`: The import process is currently running.
* `SUCCESSFUL`: The import was successful and the result data is ready to be retrieved.
* `NEEDS_REVIEW`: The import could not be completed due to data validation errors, and requires human attention.
* `FAILED`: The import encountered a fatal error, and cannot be processed.

### Successful import[​](#successful-import "Direct link to Successful import")

If the import data did not have any issues, the import will be in the `SUCCESSFUL` state.

You can be notified when an import completes in two ways:

1. **Dashboard-configured webhooks (RECOMMENDED)**:

   The recommended approach is to configure webhooks in the Dromo dashboard under the Webhooks section. This method provides several advantages:

   * More reliable delivery with retry mechanisms
   * Detailed event information
   * Easier configuration and management
   * Better security

   For detailed information on setting up and using dashboard webhooks, see the [Webhooks documentation](/reference/webhooks).

   These webhooks will receive detailed information about the completed import:

```sh theme={null}
{  "event": "import_completed",  "data": {    "id": "911224fa-c609-46d2-a278-bc2250d2be91",    "filename": "contacts (8).csv",    "user": null,    "created_date": "2025-02-25T17:07:03.769925Z",    "import_type": "HEADLESS",    "status": "SUCCESSFUL",    "num_data_rows": 92,    "development_mode": false,    "has_data": true  }}
```

After receiving this webhook notification, use the import ID from the payload to retrieve the presigned URL for downloading the data:

```sh theme={null}
curl \
  -H "X-Dromo-License-Key: your-backend-key" \
  -X GET \  'https://app.dromo.io/api/v1/headless/imports/{id}/url/'
```

The response will contain a presigned URL that you can use to download the data:

```sh theme={null}
{  "presigned_url": "https://dromo-user-imports.s3.us-west-2.amazonaws.com/..."}
```

2. **Legacy webhookUrl (DEPRECATED)**:

   If you define a [legacy webhookUrl](/reference/settings/settings#webhookurl) in your schema, it will still fire when the import completes successfully, but this method is deprecated and may be removed in the future.

**Alternative: Polling for completion**

If you prefer not to use webhooks, you can poll the [retrieve headless import endpoint](/api-reference/headless/retrieve-headless-import) to check the status:

```sh theme={null}
curl \
  -H "X-Dromo-License-Key: your-backend-key" \
  -X GET \  'https://app.dromo.io/api/v1/headless/imports/{id}/'
```

Once the status shows `SUCCESSFUL`, you can then retrieve the presigned URL using the [headless import URL endpoint](/api-reference/headless/get-headless-import-url):

```sh theme={null}
curl \
  -H "X-Dromo-License-Key: your-backend-key" \
  -X GET \  'https://app.dromo.io/api/v1/headless/imports/{id}/url/'
```

<Warning>
  Remember that polling is subject to rate limits (12 requests per minute per
  import). For production systems with many imports, webhooks are strongly
  recommended to avoid hitting these limits.
</Warning>

### Import needs review[​](#import-needs-review "Direct link to Import needs review")

If the import has issues that need manual review, the import will be in the `NEEDS_REVIEW` state.

This can happen for several reasons:

* The header row could not be determined
* The columns could not be mapped automatically
* There are failing data validations, and [invalidDataBehavior](/reference/settings/settings#invaliddatabehavior) is set to `BLOCK_SUBMIT`

When an import is in the `NEEDS_REVIEW` state, the data returned by the [retrieve headless import endpoint](/api-reference/headless/retrieve-headless-import) will include a `"review_url"` field. This is a URL which you can navigate to in a browser to resolve the import issues using Dromo's import UI.

After resolving the issues and completing the import, the import will transition to the `SUCCESSFUL` state.

### Import failed[​](#import-failed "Direct link to Import failed")

An import will enter the `FAILED` state if it has an unrecoverable error.

Some examples of unrecoverable errors are:

* The import file was corrupted and could not be parsed
* The import file was completely empty
* A custom hook threw an unhandled exception

Failed imports do not have results and also do not have a review URL.

## Rate Limits[​](#rate-limits "Direct link to Rate Limits")

The Headless API has the following rate limits:

* **Creating headless imports**: 20 requests per minute
* **Polling import status**: 12 requests per minute per import
