{
  "info": {
    "name": "QuickBooks Online — read and delete transactions",
    "description": "The four calls a bulk delete needs, in the shape we use them: query a set, read one record for its current SyncToken, delete one record, and delete up to 30 in a batch.\n\nSet `realmId` to the company id and `accessToken` to a bearer token from an OAuth 2.0 flow with the scope `com.intuit.quickbooks.accounting`. Swap `baseUrl` to `https://sandbox-quickbooks.api.intuit.com` to run against a sandbox company.\n\nProvided as a starting point, not a tested product. Observe the two caveats we found: the batch endpoint caps at 30 operations per call and rejects the whole call beyond that (fault 1040), and it does not verify the SyncToken you send — so re-read and compare the token yourself if you must delete exactly the version you reviewed. See https://multidelete.com/quickbooks-online-api-delete-findings",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    { "key": "baseUrl", "value": "https://quickbooks.api.intuit.com" },
    { "key": "realmId", "value": "REPLACE_WITH_COMPANY_ID" },
    { "key": "accessToken", "value": "REPLACE_WITH_BEARER_TOKEN" },
    { "key": "minorversion", "value": "75" },
    { "key": "entity", "value": "invoice" },
    { "key": "recordId", "value": "123" },
    { "key": "syncToken", "value": "0" }
  ],
  "item": [
    {
      "name": "1. Query the set (paged)",
      "request": {
        "method": "GET",
        "header": [
          { "key": "Authorization", "value": "Bearer {{accessToken}}" },
          { "key": "Accept", "value": "application/json" }
        ],
        "url": {
          "raw": "{{baseUrl}}/v3/company/{{realmId}}/query?query=select * from Invoice where TxnDate > '2026-01-01'&minorversion={{minorversion}}",
          "host": ["{{baseUrl}}"],
          "path": ["v3", "company", "{{realmId}}", "query"],
          "query": [
            { "key": "query", "value": "select * from Invoice where TxnDate > '2026-01-01'" },
            { "key": "minorversion", "value": "{{minorversion}}" }
          ]
        },
        "description": "Read the set. Note the silent cap: without a page size the query returns at most 100 rows and does not say so. Set `&query=...&minorversion=...` and page with `startposition`, checking each page against the response's `totalCount`."
      }
    },
    {
      "name": "2. Read one record (current SyncToken)",
      "request": {
        "method": "GET",
        "header": [
          { "key": "Authorization", "value": "Bearer {{accessToken}}" },
          { "key": "Accept", "value": "application/json" }
        ],
        "url": {
          "raw": "{{baseUrl}}/v3/company/{{realmId}}/{{entity}}/{{recordId}}?minorversion={{minorversion}}",
          "host": ["{{baseUrl}}"],
          "path": ["v3", "company", "{{realmId}}", "{{entity}}", "{{recordId}}"],
          "query": [{ "key": "minorversion", "value": "{{minorversion}}" }]
        },
        "description": "Re-read immediately before deleting, to get the record's current SyncToken. Compare it against the token captured when the set was reviewed; skip on a mismatch instead of deleting."
      }
    },
    {
      "name": "3. Delete one record",
      "request": {
        "method": "POST",
        "header": [
          { "key": "Authorization", "value": "Bearer {{accessToken}}" },
          { "key": "Accept", "value": "application/json" },
          { "key": "Content-Type", "value": "application/json" }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"Id\": \"{{recordId}}\",\n  \"SyncToken\": \"{{syncToken}}\"\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/v3/company/{{realmId}}/{{entity}}?operation=delete&minorversion={{minorversion}}",
          "host": ["{{baseUrl}}"],
          "path": ["v3", "company", "{{realmId}}", "{{entity}}"],
          "query": [
            { "key": "operation", "value": "delete" },
            { "key": "minorversion", "value": "{{minorversion}}" }
          ]
        },
        "description": "The single-record delete shape: the entity in the path, `operation=delete`, and the `{Id, SyncToken}` body. Use this to isolate a record when a whole batch call fails."
      }
    },
    {
      "name": "4. Batch delete (up to 30)",
      "request": {
        "method": "POST",
        "header": [
          { "key": "Authorization", "value": "Bearer {{accessToken}}" },
          { "key": "Accept", "value": "application/json" },
          { "key": "Content-Type", "value": "application/json" }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"batchItemRequest\": [\n    {\n      \"bId\": \"1\",\n      \"operation\": \"delete\",\n      \"Invoice\": { \"Id\": \"123\", \"SyncToken\": \"0\" }\n    },\n    {\n      \"bId\": \"2\",\n      \"operation\": \"delete\",\n      \"Invoice\": { \"Id\": \"124\", \"SyncToken\": \"1\" }\n    }\n  ]\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/v3/company/{{realmId}}/batch?minorversion={{minorversion}}",
          "host": ["{{baseUrl}}"],
          "path": ["v3", "company", "{{realmId}}", "batch"],
          "query": [{ "key": "minorversion", "value": "{{minorversion}}" }]
        },
        "description": "Up to 30 operations per call. More than 30 is rejected atomically (HTTP 400, fault 1040). Read the per-item response: one bad item does not fail the others. The batch endpoint throttles at roughly 50 consecutive calls (429, fault 3001, Retry-After: 60s)."
      }
    }
  ]
}
