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

# Batch Track Usage

> Enqueue up to 1000 usage events for asynchronous processing. Items are validated synchronously up front; validated items are then enqueued via SQS for background deduction by workers. The response returns 202 immediately and does not include balance information. On partial enqueue failure (some items fail to enqueue, others succeed), the endpoint still returns 202 and logs the failures server-side; clients should NOT retry, because retrying re-enqueues the already-succeeded items. A 503 is returned only when zero items were successfully enqueued (queue entirely unavailable) — that case is safe to retry.

export const DynamicResponseExample = ({json, statusCode = "200"}) => {
  const toCamelCase = str => {
    return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
  };
  const convertKeysToCamelCase = obj => {
    if (Array.isArray(obj)) {
      return obj.map(item => convertKeysToCamelCase(item));
    }
    if (obj !== null && typeof obj === "object") {
      return Object.keys(obj).reduce((acc, key) => {
        const camelKey = toCamelCase(key);
        acc[camelKey] = convertKeysToCamelCase(obj[key]);
        return acc;
      }, {});
    }
    return obj;
  };
  const [isTypeScript, setIsTypeScript] = useState(() => {
    if (typeof window !== "undefined") {
      try {
        const lang = localStorage.getItem("code");
        return JSON.parse(lang) === "typescript";
      } catch {
        return true;
      }
    }
    return true;
  });
  useEffect(() => {
    const onMintlifyStorage = event => {
      if (event.detail?.key === "code") {
        try {
          const value = JSON.parse(event.detail.value);
          setIsTypeScript(value === "typescript");
        } catch {}
      }
    };
    const pollInterval = setInterval(() => {
      try {
        const lang = localStorage.getItem("code");
        const value = JSON.parse(lang);
        setIsTypeScript(value === "typescript");
      } catch {}
    }, 300);
    document.addEventListener("mintlify-localstorage", onMintlifyStorage);
    return () => {
      document.removeEventListener("mintlify-localstorage", onMintlifyStorage);
      clearInterval(pollInterval);
    };
  }, []);
  const camelCaseJson = useMemo(() => convertKeysToCamelCase(json), [json]);
  const snakeCaseString = JSON.stringify(json, null, 2);
  const camelCaseString = JSON.stringify(camelCaseJson, null, 2);
  return <ResponseExample>
			{isTypeScript ? <CodeBlock language="json" filename={statusCode}>
					{camelCaseString}
				</CodeBlock> : <CodeBlock language="json" filename={statusCode}>
					{snakeCaseString}
				</CodeBlock>}
		</ResponseExample>;
};

export const DynamicResponseField = ({children, name, ...props}) => {
  const convertToCamelCase = str => {
    if (typeof str !== "string") return str;
    return str.replace(/[_-](\w)/g, (_, c) => c.toUpperCase());
  };
  const [lang, setLang] = useState(() => {
    if (typeof window !== "undefined") {
      const stored = localStorage.getItem("code");
      return stored || '"typescript"';
    }
    return '"typescript"';
  });
  useEffect(() => {
    const onMintlifyStorage = event => {
      const key = event.detail?.key;
      if (key === "code") {
        setLang(event.detail.value);
      }
    };
    const pollInterval = setInterval(() => {
      const current = localStorage.getItem("code");
      if (current && current !== lang) {
        setLang(current);
      }
    }, 500);
    document.addEventListener("mintlify-localstorage", onMintlifyStorage);
    return () => {
      document.removeEventListener("mintlify-localstorage", onMintlifyStorage);
      clearInterval(pollInterval);
    };
  }, [lang]);
  const resolvedName = useMemo(() => {
    try {
      const value = JSON.parse(lang);
      const useCamelCase = value === "typescript";
      return useCamelCase ? convertToCamelCase(name) : name;
    } catch {
      return name;
    }
  }, [name, lang]);
  return <ResponseField name={resolvedName} {...props}>
			{children}
		</ResponseField>;
};

export const DynamicParamField = ({children, body, path, ...props}) => {
  const convertToCamelCase = str => {
    if (typeof str !== "string") return str;
    return str.replace(/[_-](\w)/g, (_, c) => c.toUpperCase());
  };
  const [lang, setLang] = useState(() => {
    if (typeof window !== "undefined") {
      const stored = localStorage.getItem("code");
      return stored || '"typescript"';
    }
    return '"typescript"';
  });
  useEffect(() => {
    const onMintlifyStorage = event => {
      const key = event.detail?.key;
      if (key === "code") {
        setLang(event.detail.value);
      }
    };
    const pollInterval = setInterval(() => {
      const current = localStorage.getItem("code");
      if (current && current !== lang) {
        setLang(current);
      }
    }, 500);
    document.addEventListener("mintlify-localstorage", onMintlifyStorage);
    return () => {
      document.removeEventListener("mintlify-localstorage", onMintlifyStorage);
      clearInterval(pollInterval);
    };
  }, [lang]);
  const resolvedBody = useMemo(() => {
    try {
      const value = JSON.parse(lang);
      const useCamelCase = value === "typescript";
      return useCamelCase ? convertToCamelCase(body) : body;
    } catch {
      return body;
    }
  }, [body, lang]);
  const resolvedPath = useMemo(() => {
    try {
      const value = JSON.parse(lang);
      const useCamelCase = value === "typescript";
      return useCamelCase ? convertToCamelCase(path) : path;
    } catch {
      return path;
    }
  }, [path, lang]);
  return <ParamField body={resolvedBody} path={resolvedPath} {...props}>
			{children}
		</ParamField>;
};

<Note>
  Batch track enqueues up to **1000 usage events** in a single request. Items are validated synchronously, then enqueued for asynchronous processing. The response returns **202 immediately** without balance information — balances are deducted by background workers.

  Use this when you're sending high volumes of tracking events and don't need an immediate balance read for each one.
</Note>

### Common Use Cases

<CodeGroup>
  ```typescript Batch many customers theme={null}
  await autumn.balances.batchTrack([
    { customerId: "cus_alice", featureId: "ai_messages", value: 1 },
    { customerId: "cus_bob",   featureId: "ai_messages", value: 1 },
    { customerId: "cus_carol", featureId: "ai_messages", value: 3 },
  ]);
  ```

  ```typescript Mixed features and entities theme={null}
  await autumn.balances.batchTrack([
    { customerId: "cus_123", featureId: "ai_messages", value: 5 },
    { customerId: "cus_123", featureId: "api_calls",   value: 12 },
    { customerId: "cus_123", featureId: "seats",       entityId: "team_a", value: 1 },
  ]);
  ```
</CodeGroup>

### Partial-Failure Semantics

Batch track is designed for fire-and-forget metering. On partial failure, **the endpoint still returns 202** and logs the failed items server-side. Clients should NOT retry the batch — retrying re-enqueues the already-succeeded items, which causes double-deduction. The trade-off is silent loss of the small subset that didn't enqueue vs. duplicate processing of the much larger subset that did. For event-logging workloads, gaps are preferable to duplicates.

A 503 is returned only when **zero items were successfully enqueued** (the queue is entirely unavailable). In that case the whole request is safe to retry.

If your workload requires per-item delivery guarantees, use the [single-event track endpoint](/api-reference/core/track) with client-side retry semantics instead.

### Limits

* **Maximum batch size:** 1000 items per request
* **Minimum batch size:** 1 item
* **Rate limit:** 10 requests/second per organization (separate bucket from the single `/v1/balances.track` limiter)

### Body Parameters

<DynamicParamField body="items" type="object">
  Array item

  <Expandable title="properties">
    <DynamicParamField body="customer_id" type="string" required>
      The ID of the customer.
    </DynamicParamField>

    <DynamicParamField body="feature_id" type="string">
      The ID of the feature to track usage for. Required if event\_name is not provided.
    </DynamicParamField>

    <DynamicParamField body="entity_id" type="string">
      The ID of the entity for entity-scoped balances (e.g., per-seat limits).
    </DynamicParamField>

    <DynamicParamField body="event_name" type="string">
      Event name to track usage for. Use instead of feature\_id when multiple features should be tracked from a single event.
    </DynamicParamField>

    <DynamicParamField body="value" type="number">
      The amount of usage to record. Defaults to 1. Use negative values to credit balance (e.g., when removing a seat).
    </DynamicParamField>

    <DynamicParamField body="properties" type="object">
      Additional properties to attach to this usage event.
    </DynamicParamField>

    <DynamicParamField body="timestamp" type="integer">
      Unix timestamp in milliseconds to use for the usage event. Defaults to the current time.
    </DynamicParamField>

    <DynamicParamField body="async" type="boolean">
      If true, enqueue the event for asynchronous processing and return 204 immediately. The response will not include balance information.
    </DynamicParamField>

    <DynamicParamField body="lock" type="object">
      <Expandable title="properties">
        <DynamicParamField body="lock_id" type="string" required>
          A unique identifier for this lock. Used to finalize the lock later via balances.finalize.
        </DynamicParamField>

        <DynamicParamField body="enabled" type="any" required>
          Must be true to enable locking.
        </DynamicParamField>

        <DynamicParamField body="expires_at" type="number">
          Unix timestamp (ms) when the lock automatically expires and releases the held balance.
        </DynamicParamField>
      </Expandable>
    </DynamicParamField>
  </Expandable>
</DynamicParamField>


## OpenAPI

````yaml openapi POST /v1/balances.batch_track
openapi: 3.1.0
info:
  title: Autumn API
  version: 2.3.0
servers:
  - url: https://api.useautumn.com
    description: Production server
security:
  - secretKey: []
paths:
  /v1/balances.batch_track:
    post:
      description: >-
        Enqueue up to 1000 usage events for asynchronous processing. Items are
        validated synchronously up front; validated items are then enqueued via
        SQS for background deduction by workers. The response returns 202
        immediately and does not include balance information. On partial enqueue
        failure (some items fail to enqueue, others succeed), the endpoint still
        returns 202 and logs the failures server-side; clients should NOT retry,
        because retrying re-enqueues the already-succeeded items. A 503 is
        returned only when zero items were successfully enqueued (queue entirely
        unavailable) — that case is safe to retry.
      operationId: batchTrack
      parameters:
        - name: x-api-version
          in: header
          required: true
          schema:
            type: string
            default: 2.3.0
          x-speakeasy-globals-hidden: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              minItems: 1
              maxItems: 1000
              items:
                type: object
                properties:
                  customer_id:
                    type: string
                    description: The ID of the customer.
                  feature_id:
                    type: string
                    description: >-
                      The ID of the feature to track usage for. Required if
                      event_name is not provided.
                  entity_id:
                    type: string
                    description: >-
                      The ID of the entity for entity-scoped balances (e.g.,
                      per-seat limits).
                  event_name:
                    type: string
                    minLength: 1
                    description: >-
                      Event name to track usage for. Use instead of feature_id
                      when multiple features should be tracked from a single
                      event.
                  value:
                    type: number
                    description: >-
                      The amount of usage to record. Defaults to 1. Use negative
                      values to credit balance (e.g., when removing a seat).
                  properties:
                    type: object
                    propertyNames:
                      type: string
                    additionalProperties: {}
                    description: Additional properties to attach to this usage event.
                  timestamp:
                    type: integer
                    minimum: 0
                    maximum: 9007199254740991
                    exclusiveMinimum: 0
                    description: >-
                      Unix timestamp in milliseconds to use for the usage event.
                      Defaults to the current time.
                  async:
                    type: boolean
                    description: >-
                      If true, enqueue the event for asynchronous processing and
                      return 204 immediately. The response will not include
                      balance information.
                  lock:
                    type: object
                    properties:
                      lock_id:
                        type: string
                        maxLength: 256
                        description: >-
                          A unique identifier for this lock. Used to finalize
                          the lock later via balances.finalize.
                      enabled:
                        const: true
                        description: Must be true to enable locking.
                      expires_at:
                        type: number
                        description: >-
                          Unix timestamp (ms) when the lock automatically
                          expires and releases the held balance.
                    required:
                      - lock_id
                      - enabled
                required:
                  - customer_id
              title: BatchTrackParams
              examples:
                - - customer_id: cus_123
                    feature_id: messages
                    value: 1
                  - customer_id: cus_123
                    event_name: message.sent
                    value: 1
            example:
              - customer_id: cus_123
                feature_id: messages
                value: 1
              - customer_id: cus_123
                event_name: message.sent
                value: 1
      responses:
        '202':
          description: >-
            Batch accepted. All items passed synchronous validation. Enqueue is
            best-effort: partial failures (some items enqueued, some not) are
            logged server-side and are NOT surfaced in the response body;
            clients must not retry on 202. See the endpoint description for full
            partial-failure semantics.
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    const: true
                required:
                  - success
                examples:
                  - success: true
              example:
                success: true
      x-codeSamples:
        - lang: typescript
          label: Typescript (SDK)
          source: |-
            import { Autumn } from 'autumn-js'

            const autumn = new Autumn()

            const result = await autumn.batchTrack([
              {
                customerId: "cus_123",
                featureId: "messages",
                value: 1,
              },
              {
                customerId: "cus_123",
                eventName: "message.sent",
                value: 1,
              },
            ]);
        - lang: python
          label: Python (SDK)
          source: |-
            from autumn_sdk import Autumn

            autumn = Autumn(secret_key="am_sk_test...")

            res = autumn.batch_track(
                request=[
                    {
                        "customer_id": "cus_123",
                        "feature_id": "messages",
                        "value": 1,
                    },
                    {
                        "customer_id": "cus_123",
                        "event_name": "message.sent",
                        "value": 1,
                    },
                ],
            )
components:
  securitySchemes:
    secretKey:
      type: http
      scheme: bearer
      bearerFormat: JWT

````