# Request lifecycle and status
Source: https://www.desktopaccountingapi.com/docs/guides/request-lifecycle/

> How a QuickBooks request is queued, sent and answered, sync and async modes, timeouts, the per-connection queue, and how to read a request's status.

Every call that needs QuickBooks Desktop (any `/v1/quickbooks-desktop/` operation, the health check and passthrough) becomes a **request** with an ID such as `req_01j9x4m6v4c8k2t7q0r5s3w1zb`. The ID comes back in the `Daapi-Request-Id` header of every response, success or error. You can read the request at any time with `GET /v1/requests/{id}`, and the dashboard request log shows the same data.

Calls rejected before they are queued, for example a validation error, a connection that has not finished setup or a disabled connection, are in the log too: a `failed` request with `sentAt: null` and the error code. They are logged when the call names an end user of the project that has a connection, which exists from its first auth session. Rate-limited calls and calls without a valid end user ID are not logged; keep the `requestId` from their error body.

## Why requests queue

QuickBooks Desktop runs on your customer's computer and cannot accept inbound connections. The QuickBooks Web Connector on that computer polls us instead. A request therefore waits in a queue for its connection until the Web Connector picks it up:

1. **Queued.** We validate your call, translate it to qbXML and add it to the queue for that end user's company file.
2. **Check-in.** The Web Connector checks in. When work is waiting, it opens a QuickBooks session with the company file.
3. **Sent.** The Web Connector passes the request to QuickBooks. From this point the request may change the company file.
4. **Answered.** QuickBooks returns its response. We map it to JSON and finish the request as `succeeded` or `failed`.

QuickBooks handles one request at a time per company file. Requests for one connection run in the order you sent them. Requests for different end users run independently.

## How long requests take

| Situation | Typical wait before QuickBooks starts on your request |
| --- | --- |
| Session already open (another request in the last 2 seconds, or a cursor read in the last 10 seconds) | Under a second |
| Connection idle | Up to 10 seconds, until the next check-in |
| Active in the last 10 minutes, session closed | Up to 3 seconds |
| No requests for 7 days | Up to 30 seconds for the first request |
| QuickBooks closed, "always allow" access granted | Add the time QuickBooks needs to start, often 10 to 60 seconds |

After a request finishes, the session stays open for about 2 seconds so a follow-up request skips the check-in wait. While a [cursor](https://www.desktopaccountingapi.com/docs/guides/pagination/) has more pages, the session stays open for its 10-second idle window. A busy session closes after 2 minutes of continuous work and reopens at the next check-in, so other Web Connector applications on the same computer get their turn. While a session is open, QuickBooks treats the company file as in use by your app: closing the company, exiting QuickBooks or restoring a backup works again once the session closes, normally about 2 seconds after the last request. Send related requests back to back, and finish or abandon a cursor promptly, so the file is released quickly. If your customer sees QuickBooks refuse to close the file, send them [QuickBooks says the company file is being used by another application](https://www.desktopaccountingapi.com/docs/help/connection-errors/company-file-in-use/).

QuickBooks itself sets the speed of large reads and reports. Page large lists ([Pagination](https://www.desktopaccountingapi.com/docs/guides/pagination/)) and filter reports by date.

## Sync mode (default)

By default your HTTP call waits for QuickBooks' answer, up to `Daapi-Timeout-Seconds` seconds. The default is 90 seconds (60 for the health check); you can send any integer from 1 to 300.

| What happens | Response |
| --- | --- |
| QuickBooks answered | `200` or `201` with the result, or the mapped error |
| The end user has not finished setup | `409 INTEGRATION_CONNECTION_NOT_SET_UP`, immediately |
| The Web Connector has not checked in for over a minute | `503 INTEGRATION_CONNECTION_NOT_ACTIVE`, immediately, and nothing is queued |
| The Web Connector reported a QuickBooks error while your request waited (for example QuickBooks could not start, or the wrong file is open) | That error at once, such as `503 QBD_CANNOT_START`. Nothing was sent. `QBD_STARTING` keeps waiting instead. |
| The Web Connector opened a session but QuickBooks has not answered it for 60 seconds (20 seconds if an earlier session already went unanswered) | `503 QBD_QUICKBOOKS_NOT_RESPONDING` with `details.diagnosis`. Usually a dialog is open in QuickBooks. Nothing was sent. |
| The deadline passed before the request was sent | `504 REQUEST_TIMEOUT_NOT_SENT` with `details.diagnosis`. The request is canceled and never reaches QuickBooks. Safe to retry. |
| The deadline passed after the request was sent | `504 QBD_REQUEST_TIMEOUT` with `outcome: "pending"` and `details.requestId`. The request keeps running. Do not resend; read the result instead. |

The last row matters for writes. QuickBooks may still be applying your invoice when your HTTP call gives up. Resending would create a second invoice. Collect the result instead:

```sh
curl "https://api.desktopaccountingapi.com/v1/requests/req_01j9...?waitSeconds=60" \
  -H "Authorization: Bearer $DAAPI_SECRET_KEY"
```

`waitSeconds` (0 to 60) holds the call open until the request finishes, so you need no polling loop. The `result` field holds exactly the body the original call would have returned. The SDKs do this automatically when they receive `QBD_REQUEST_TIMEOUT`, and raise a pending error with the request ID only if their own timeout runs out first.

> **Note:**
> Set your HTTP client's timeout a little above `Daapi-Timeout-Seconds`, so the API's `504` reaches you instead of your client giving up first. The SDKs use 100 seconds against the default 90.

## Async mode

For imports, background syncs and anything else where nobody is waiting, send `Prefer: respond-async`. The API answers `202 Accepted` at once with the request resource, a `Location: /v1/requests/{id}` header and `Preference-Applied: respond-async`.

**curl**

```sh
curl https://api.desktopaccountingapi.com/v1/quickbooks-desktop/customers \
  -H "Authorization: Bearer $DAAPI_SECRET_KEY" \
  -H "Daapi-End-User-Id: eu_01j9..." \
  -H "Idempotency-Key: 0f8c2a3e-6d1b-4b8e-9a51-2f6c7d9e4a10" \
  -H "Prefer: respond-async" \
  -H "Daapi-Queue-Ttl-Seconds: 7200" \
  -H "Content-Type: application/json" \
  -d '{"name":"Northwind Traders"}'
```

```http
HTTP/1.1 202 Accepted
Location: /v1/requests/req_01j9x4m6v4c8k2t7q0r5s3w1zb
Preference-Applied: respond-async
Daapi-Request-Id: req_01j9x4m6v4c8k2t7q0r5s3w1zb
```

An async request waits for the connection even while the computer is off: its status changes to `waiting_for_connection` when the Web Connector goes offline and back to `queued` at the next check-in. `Daapi-Queue-Ttl-Seconds` (10 to 86400, default 3600) sets the latest moment it may still be sent. If the connection does not come back in time, the request ends as `failed` with `REQUEST_EXPIRED` and a diagnosis, never having reached QuickBooks. Async requests are not affected by the 60-second unanswered-session rule; they keep waiting until their queue lifetime ends or QuickBooks failed three sessions in a row.

Find out when it finishes in either of two ways:

- **Webhooks.** Subscribe to `request.succeeded` and `request.failed`. See [Webhooks](https://www.desktopaccountingapi.com/docs/guides/webhooks/).
- **Polling.** Call `GET /v1/requests/{id}?waitSeconds=60` until the status is final.

## The request resource

```json
{
  "id": "req_01j9x4m6v4c8k2t7q0r5s3w1zb",
  "objectType": "request",
  "createdAt": "2026-10-05T16:03:59.002Z",
  "projectId": "proj_01j9...",
  "endUserId": "eu_01j9...",
  "connectionId": "conn_01j9...",
  "operationId": "qbd.customers.create",
  "method": "POST",
  "path": "/v1/quickbooks-desktop/customers",
  "mode": "async",
  "idempotencyKeyPresent": true,
  "previousRequestId": null,
  "status": "succeeded",
  "waitingReason": null,
  "queuePosition": null,
  "outcome": "applied",
  "sentAt": "2026-10-05T16:04:02.517Z",
  "completedAt": "2026-10-05T16:04:03.140Z",
  "queueTtlExpiresAt": "2026-10-05T18:03:59.002Z",
  "durationMs": 4138,
  "timings": { "queuedMs": 3515, "quickbooksMs": 623, "totalMs": 4138 },
  "quickbooks": { "qbxmlVersion": "16.0", "messageSetId": "Vd9bq3kQx0m2R7yA1cT5wE", "statusCode": 0, "statusSeverity": "Info" },
  "warnings": [],
  "recovered": false,
  "timeline": [
    { "at": "2026-10-05T16:03:59.002Z", "status": "queued", "detail": "awaiting_check_in", "elapsedMs": 0 },
    { "at": "2026-10-05T16:04:02.517Z", "status": "sent", "detail": null, "elapsedMs": 3515 },
    { "at": "2026-10-05T16:04:03.140Z", "status": "succeeded", "detail": null, "elapsedMs": 4138 }
  ],
  "diagnosis": null,
  "error": null,
  "result": { "id": "80000042-1730313843", "objectType": "qbd_customer", "name": "Northwind Traders" },
  "resultExpired": false
}
```

### Statuses

| Status | Final | Meaning |
| --- | --- | --- |
| `queued` | No | Accepted and waiting for its turn or the next check-in |
| `waiting_for_connection` | No | Async only: the connection is offline or QuickBooks is unavailable; the request goes out when it recovers, or expires |
| `sent` | No | Handed to QuickBooks. It can no longer be canceled |
| `succeeded` | Yes | QuickBooks accepted it, possibly with warnings |
| `failed` | Yes | Definitive failure; `error` explains why |
| `canceled` | Yes | Canceled before it was sent, by you or by a sync deadline |
| `outcome_unknown` | Yes, for you | A write was sent and its result could not be confirmed. Verify before writing again. See [Idempotency and safe retries](https://www.desktopaccountingapi.com/docs/guides/idempotency/#uncertain-writes) |

New statuses may appear; treat unknown ones as not final.

### Waiting reasons

While a request waits, `waitingReason` says why and `queuePosition` says how many requests are ahead (1 means next).

| `waitingReason` | Meaning |
| --- | --- |
| `awaiting_check_in` | Waiting for the Web Connector's next check-in |
| `behind_other_requests` | Other requests for this company file go first |
| `opening_company_file` | QuickBooks is opening the file |
| `quickbooks_starting` | QuickBooks is starting |
| `connector_offline` | The computer or Web Connector is offline (async requests) |
| `quickbooks_unavailable` | The Web Connector checks in but QuickBooks cannot open the file; `error` previews the reason |
| `quickbooks_not_responding` | A Web Connector session is open but QuickBooks has not answered it for 20 seconds |
| `write_recovery_pending` | An earlier write's outcome is unknown; later writes wait until that session ends, reads continue |

### Timeline, timings and diagnosis

Each `timeline` step has `elapsedMs` since the request was created. Besides status changes, the timeline records `waiting` (the waiting reason changed), `diagnosed`, and steps for cursors and write holds. `timings` splits the duration into time in the queue (`queuedMs`) and time in QuickBooks (`quickbooksMs`).

While a request waits or runs long, and after it timed out or expired, `diagnosis` ranks the probable causes from what we observed of the Web Connector and QuickBooks, with fixes for you and your customer. See [Request diagnosis](https://www.desktopaccountingapi.com/docs/troubleshooting/).

### Outcome

`outcome` tells you whether a write changed the company file: `applied`, `not_applied`, `pending`, `unknown`, or `not_applicable` for reads. Errors carry the same field. It is the field to check before retrying a write.

### Warnings

QuickBooks sometimes accepts a request and adds a warning, for example when it adjusts a value. We return the result with a `Daapi-Warnings` header that counts them, and record each warning (`code`, `statusCode`, `message`, `path`) in the request's `warnings` array. The same array lists values the API had to read leniently from a damaged company file (`QBD_VALUE_UNREADABLE`, `QBD_MARKUP_REPAIRED`, with `statusCode: null` and the `path` of the value in `result`); see [Damaged and unusual data](https://www.desktopaccountingapi.com/docs/quickbooks/conventions/#damaged-and-unusual-data).

### Retention

Request metadata, timelines and errors stay readable for 30 days. Request and response bodies stay for 15 days, after which `result` is `null` and `resultExpired` is `true`. A project can turn body capture off in the dashboard; bodies are then kept only until 24 hours after the request completes. See [Security and data retention](https://www.desktopaccountingapi.com/docs/platform/security/).

## Listing and canceling requests

`GET /v1/requests` lists a project's requests newest first, filtered by `endUserId`, `status` (repeatable), `operationId`, `createdAfter` and `createdBefore`. Items are request summaries with an `errorCode` instead of the full error, timeline and result; read one request for the details. The list follows each status change within seconds.

`POST /v1/requests/{id}/cancel` cancels a request that is still `queued` or `waiting_for_connection`. It becomes `canceled` with `REQUEST_CANCELED`, and a sync caller still waiting on it receives that error. Canceling an already canceled request returns it unchanged. Any other status returns `409 REQUEST_NOT_CANCELABLE` with `details.status`, because a `sent` request may already be changing the company file.

## Errors in this guide

[`QBD_QUICKBOOKS_NOT_RESPONDING`](https://www.desktopaccountingapi.com/docs/errors/#qbd_quickbooks_not_responding), [`REQUEST_TIMEOUT_NOT_SENT`](https://www.desktopaccountingapi.com/docs/errors/#request_timeout_not_sent), [`QBD_REQUEST_TIMEOUT`](https://www.desktopaccountingapi.com/docs/errors/#qbd_request_timeout), [`REQUEST_EXPIRED`](https://www.desktopaccountingapi.com/docs/errors/#request_expired), [`REQUEST_CANCELED`](https://www.desktopaccountingapi.com/docs/errors/#request_canceled), [`REQUEST_NOT_CANCELABLE`](https://www.desktopaccountingapi.com/docs/errors/#request_not_cancelable), [`QBD_READ_INTERRUPTED`](https://www.desktopaccountingapi.com/docs/errors/#qbd_read_interrupted), [`INTEGRATION_CONNECTION_NOT_ACTIVE`](https://www.desktopaccountingapi.com/docs/errors/#integration_connection_not_active).
