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
Response — 201 (CreateBucketResponse)
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.