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

# Run large GraphQL queries with bulk operations

> Use Firmhouse GraphQL bulk operations to export large connection-based query results as a JSONL file.

Use GraphQL bulk operations when your integration needs to read a larger set of Firmhouse records than a normal paginated API request should handle at once.

A bulk operation runs in the background and gives you a JSONL download URL when the operation completes. This is useful for exports, data warehouse syncs, reporting jobs, and other integrations where you want to process many records outside the request-response cycle of the GraphQL API.

For large exports, use bulk operations instead of increasing normal query size until the request reaches GraphQL query complexity or timeout limits.

## Requirements

* A Firmhouse API access token with write access to start the bulk operation.
* Read access to the data selected by the query.
* One active bulk operation at a time per project.

## Start a bulk operation

Call `bulkOperationRunQuery` with the GraphQL query you want Firmhouse to run.

```graphql theme={null}
mutation CreateBulkOperation {
  bulkOperationRunQuery(input: {
    query: """
    query products {
      products {
        nodes {
          id
          title
        }
      }
    }
    """
  }) {
    bulkOperation {
      id
      status
      url
    }
    errors {
      attribute
      message
      path
    }
  }
}
```

Always request and check `errors` in the response. Firmhouse can return query parse or validation messages there, for example when the query is malformed or uses an unsupported bulk operation shape. If `errors` is not empty, `bulkOperation` will be `null` and no background operation was created.

When the operation is accepted, the response contains a bulk operation with status `CREATED`. Store the `id`; you need it to check the operation later.

## Query rules

Bulk operation queries use the same Firmhouse GraphQL schema as regular API requests, but the top-level query shape is restricted so the result can be exported predictably.

Your query must:

* Be a `query` operation, not a mutation or subscription.
* Select exactly one top-level field.
* Use a top-level connection field, such as a field that returns `nodes` or `edges { node { ... } }`.
* Leave top-level pagination arguments to Firmhouse. Do not pass `first`, `last`, `before`, or `after` on the top-level connection.
* Not select more than five total connection fields in the query, including nested connections.
* Not select nested connections more than two connection levels below the top-level connection.

This works:

```graphql theme={null}
query {
  products {
    nodes {
      id
      title
    }
  }
}
```

This does not work because it passes pagination arguments on the top-level connection:

```graphql theme={null}
query {
  products(first: 50) {
    nodes {
      id
      title
    }
  }
}
```

Bulk operations export all records from the top-level connection. If you select a nested connection, that nested connection follows the normal GraphQL API pagination rules. For example, it returns up to the default page size unless you pass supported pagination arguments for that nested field. A bulk operation does not automatically walk through every page of nested connections.

## Check the status

Use the `bulkOperation` query with the ID from `bulkOperationRunQuery`.

```graphql theme={null}
query BulkOperation($id: ID!) {
  bulkOperation(id: $id) {
    id
    status
    errorCode
    createdAt
    completedAt
    url
  }
}
```

Bulk operations can have these statuses:

* `CREATED`: the operation was created and is waiting to start.
* `RUNNING`: Firmhouse is reading the data.
* `COMPLETED`: the JSONL result file is ready and `url` is available.
* `FAILED`: the operation could not complete. Check `errorCode`.

## Download the JSONL result

When the status is `COMPLETED`, download the file from `url`.

The file is JSONL, also known as newline-delimited JSON. Each line is one complete JSON object from the selected connection:

```jsonl theme={null}
{"id":"1","title":"Starter box"}
{"id":"2","title":"Refill pack"}
```

JSONL is a good fit for bulk exports because the file does not need to be parsed as one large JSON array. Your integration can read one line, parse that line as JSON, process or store the record, and then move to the next line.

This matters for larger exports. Avoid loading the entire file into memory before parsing it. Instead, stream the response or downloaded file line by line.

For example, in Node.js you can process a downloaded file with a line reader:

```javascript theme={null}
import fs from "node:fs"
import readline from "node:readline"

const file = readline.createInterface({
  input: fs.createReadStream("bulk_operation_123.jsonl"),
  crlfDelay: Infinity
})

for await (const line of file) {
  if (!line) continue

  const record = JSON.parse(line)
  // Store, transform, or send the record to your own system.
}
```

The same pattern applies in other languages: open the file as a stream, read one line at a time, parse each line, and handle failures in a way that lets you retry from your own last processed record.

## Use the finished webhook

Instead of polling until completion, you can subscribe to the `graphql_bulk_operation_finished` outgoing webhook event.

Create this webhook before you start the bulk operation if your integration should continue automatically when the result file is ready.

1. In Firmhouse, go to **Apps**.
2. Scroll to **Webhooks** and click **Configure**.
3. Click **New outgoing webhook**.
4. Enter a name, such as **GraphQL bulk operation finished**.
5. Enter the endpoint URL in your own system that should receive the completion notification.
6. Set **Content type** to **JSON**.
7. Select the **GraphQL bulk operation finished** event.
8. Add a JSON template that includes the bulk operation fields your integration needs.
9. Click **Save**.

Recommended template:

```json theme={null}
{
  "bulk_operation": {
    "id": "{{ bulk_operation.id }}",
    "status": "{{ bulk_operation.status }}",
    "error_code": "{{ bulk_operation.error_code }}",
    "created_at": "{{ bulk_operation.created_at }}",
    "completed_at": "{{ bulk_operation.completed_at }}",
    "url": "{{ bulk_operation.url }}"
  }
}
```

Firmhouse sends this webhook after a bulk operation reaches a final state. Use it to trigger the download step in your own system. If the operation failed, inspect `status` and `error_code` before retrying or starting a new operation.

The `url` value is only available when the operation completed successfully. Your webhook handler should check `status` before trying to download the JSONL file.

## Related

* [Generate API access tokens](/configure/integrations/api-access-tokens)
* [Webhooks](/configure/integrations/webhooks)
* [API Reference](https://developer.firmhouse.com/graphql-api/api-reference)
