---
title: "Bulk Create Spans"
method: POST
path: "/api/request-logs/bulk/"
tags: ["spans"]
---

# Bulk Create Spans

`POST /api/request-logs/bulk/`

The single canonical mixin for bulk endpoints.

Every view in this codebase that processes a SET of items in one request
(``/api/.../bulk/``, ``/.../bulk-create/``, ``/.../bulk-delete/``) MUST
inherit this mixin. There is intentionally only one — strategy choice
(per-item loop vs batched query vs async dispatch vs criterion-delete)
lives in the SUBCLASS body of ``process_bulk``, not in the class
hierarchy. Helpers in ``utils/bulk/strategies.py`` cover the common
strategies; subclasses can also implement custom logic.

Why a single mixin: see ``BE_conventions/bulk.md``. Variation by
strategy was the road we explicitly avoided.

Subclass contract (required):
    ``bulk_serializer_class``       — DRF serializer for the request body
    ``get_bulk_items(validated_data) -> list``
    ``process_bulk(items, **ctx) -> BulkOperationResponse``

Subclass contract (optional):
    ``bulk_max_size``               — default 500
    ``get_bulk_context(request, validated_data) -> dict``
    ``get_bulk_status_code(response) -> int``

The mixin defines ``post()`` to call ``handle_bulk_request``. DELETE-shaped
bulk endpoints (criterion-based deletion) restrict ``http_method_names`` to
``["delete", "options"]`` and define ``delete()`` that delegates to
``handle_bulk_request``.

Status code policy (override ``get_bulk_status_code`` for custom):
    all-success     → 200
    partial-success → 207
    all-failure     → 400
    over-limit      → 422 (returned directly, never reaches process_bulk)

Usage::

    class MyBulkView(BulkOperationMixin, JWTAndAPIKeyAuthenticationViewMixin, APIView):
        bulk_serializer_class = MyRequestSerializer
        bulk_max_size = 100

        def get_bulk_items(self, validated_data):
            return validated_data["items"]

        def process_bulk(self, items, **ctx):
            from utils.bulk.strategies import run_bulk_loop

            def handle(*, item, index):
                self._do_one(item, **ctx)

            return run_bulk_loop(items, process_item=handle, is_atomic=True)

## Headers

- `Authorization` string, required

## Response `200`

No response body

- SpansBulkCreateSpansResponse200 — Empty response body

---

[API](https://skmtc.net/keywordsai/apis/api-reference.md) · [All operations](https://skmtc.net/keywordsai/apis/api-reference/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/keywordsai/api-reference/versions/c26d550029f8/schema)
