v3
latestOpenAPI 3.1.02026-07-311,4541,5202.3 MBBulk Create Spans
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
Use your Respan API key for Respan API authentication. Enter only the Respan API key value; clients send Authorization: Bearer <RESPAN_API_KEY>. For /api/responses, provider credentials such as Perplexity, OpenAI, or Azure OpenAI go in Settings -> Providers or respan_params.credential_override in the request body, not in this authentication field.
Response
No response body
Empty response body