Skip to content

Bucket

A bucket groups already-extracted documents (one or more request_uids, with optional selection of specific doc_uids). The bucket_uid is the only input for all post-extraction operations (chunking, embedding, vector store): these modules do not accept request_uids directly — even for a single document a bucket must be created.

Extraction files remain untouched under {user_uid}/{request_uid}/{document_uid}/, while all post-extraction pipeline outputs go under {user_uid}/{bucket_uid}/.

Bucket shape

{
  "bucket_uid": "bkt_uuid",
  "user_uid":   "usr_uuid",
  "name":       "AI Papers Q1",
  "created_at": "2024-01-01T10:00:00Z",
  "sources": [
    { "request_uid": "req_1", "doc_uids": ["doc_1", "doc_3"] },
    { "request_uid": "req_2", "doc_uids": null }
  ]
}

A new bucket starts with only {bucket_uid, name, user_uid, created_at}; the sources array is added lazily on the first POST .../sources call. doc_uids: null = all documents for that request. Ownership is verified on every read — a bucket that does not exist or belongs to another user returns 404.

How downstream consumers resolve sources

Each source maps to the document's Storage paths as follows:

source { request_uid: "req_1", doc_uids: ["doc_1", "doc_3"] }
  → looks for markdown under: {user_uid}/req_1/doc_1/  and  {user_uid}/req_1/doc_3/

source { request_uid: "req_2", doc_uids: null }
  → looks for markdown under: {user_uid}/req_2/   (recursive search)

Firebase Storage layout (cross-module)

{user_uid}/
├── {request_uid}/{document_uid}/   ← extraction output (unchanged after extraction)
└── {bucket_uid}/                   ← post-extraction pipeline output
    ├── chunking.msgpack            ← written by Chunking
    ├── embeddings.msgpack          ← written by Embedding
    ├── chunking_statistics.json    ← written by POST /api/v1/chunk/analyze
    └── chunking_distribution.png   ← written by POST /api/v1/chunk/analyze

Input validation

name is validated at the HTTP boundary (422 on schema violation). request_uid and doc_uids must be valid UUIDs — critical because they are interpolated into Storage paths (cross-tenant path-traversal prevention).

Endpoints

POST /api/v1/management/buckets

Creates an empty bucket. name is required, 1 ≤ len(name) ≤ 200 (control characters rejected) — violations → 422.

Request

{ "name": "AI Papers Q1" }

Response — 201 (CreateBucketResponse)

{ "bucket_uid": "bkt_uuid", "name": "AI Papers Q1" }

POST /api/v1/management/buckets/{bucket_uid}/sources

Appends sources to the bucket. sources must be a non-empty list; each request_uid and every doc_uid is parsed as a UUID (422 on violation). doc_uids: null = all documents of that request.

Request

{
  "sources": [
    { "request_uid": "req_1", "doc_uids": ["doc_1", "doc_3"] },
    { "request_uid": "req_2", "doc_uids": null }
  ]
}

Response — 204 No Content

GET /api/v1/management/buckets

Lists the authenticated user's buckets (summary).

Response — 200 (ListBucketsResponse)

{
  "buckets": [
    { "bucket_uid": "bkt_uuid", "name": "AI Papers Q1", "created_at": "2024-01-01T10:00:00Z", "sources_count": 2 }
  ]
}

created_at may be null for older records; sources_count is the length of the bucket's sources array (0 for an empty bucket).

GET /api/v1/management/buckets/{bucket_uid}

Full bucket detail. 404 if it does not exist or does not belong to the user.

Response — 200 (BucketDetail)

{
  "bucket_uid": "bkt_uuid",
  "name": "AI Papers Q1",
  "user_uid": "user-42",
  "created_at": "2024-01-01T10:00:00Z",
  "sources": [
    { "request_uid": "req_1", "doc_uids": ["doc_1", "doc_3"] },
    { "request_uid": "req_2", "doc_uids": null }
  ]
}

sources is [] for a bucket with no sources yet.

DELETE /api/v1/management/buckets/{bucket_uid}

Deletes the bucket. 404 if it does not exist or does not belong to the user.