Skip to content

Document

The document module stores files/documents attached to product entities (charitable cause, commodity financing, investment, transaction) via the polymorphic product_type + product_id columns. The /v1/documents endpoint handles direct upload (multipart) or registers files already uploaded via the file-manager presigned flow. The /v1/official-documents endpoint stores official identities (passport, KTP, SIM, NPWP) separately because the lifecycle and fields differ.

PropertyValue
Base URL{HOST}/v1
AuthBearer JWT (header Authorization) or cookie access_token
Content-Typeapplication/json (or multipart/form-data for direct upload)
Error envelope{ "message": string | string[], "statusCode": number, "error": string }
ValidationGlobal ValidationPipe · whitelist: true, forbidNonWhitelisted: true · unknown field → 400
Related modulesfile-manager, charitable-cause, commodity-financing, investments, transactions
Document versionv1 · 2026-05-20
AudienceInternal FE devs (mobile + web)

Primary upload path: direct upload (POST /v1/documents/upload, multipart) — the server performs the S3 upload and stores metadata in one transaction. POST /v1/documents still exists for registering an object that was uploaded through another channel (side-channel); most FE callers don’t need it. Once registered, a presigned (expiring) view URL can be retrieved via /v1/documents/:id/view-url. Official identities (KTP, passport, SIM, NPWP) use the separate /v1/official-documents endpoint.

MethodPathAuthSummary
POST/v1/documents/uploadbearer upload-documentDirect upload (multipart) — server-side to S3
POST/v1/documentsbearer create-documentRegister a document for an existing S3 object (side-channel)
GET/v1/documentsbearer read-documentList all documents
GET/v1/documents/type/:typebearer read-documentList documents by type
GET/v1/documents/:idbearer read-documentDetail of a single document
GET/v1/documents/:id/view-urlbearer read-documentPresigned URL to view document contents
PATCH/v1/documents/:idbearer update-documentUpdate document metadata
DELETE/v1/documents/:idbearer delete-documentDelete a document record
POST/v1/official-documentsbearer create-official-documentCreate an official identity
GET/v1/official-documentsbearer read-official-documentList official identities
GET/v1/official-documents/:idbearer read-official-documentDetail of a single official identity
PATCH/v1/official-documents/:idbearer update-official-documentUpdate an official identity
DELETE/v1/official-documents/:idbearer delete-official-documentDelete an official identity

Documents attach to a parent via two columns on the documents row: product_type (which kind of parent — charitable_cause, commodity_financing, investment, transaction) and product_id (the parent’s UUID). There is no photo_document_id on the parent — the relationship is reversed. The FE flow is therefore create parent first, then upload docs referencing the parent ID.


POST /v1/documents/upload bearer upload-document RESOURCE_CREATED

Section titled “POST /v1/documents/upload ”

Upload a file directly via multipart. The server performs the S3 upload and stores the document metadata in one transaction (atomic — if S3 fails, no DB row is created).

Request body — UploadDocumentDto (multipart/form-data)

Section titled “Request body — UploadDocumentDto (multipart/form-data)”
FieldTypeRequiredNotes
filefileyesBinary file. Limit 50 MB.
document_typeenum DocumentTypeyeslegal_agreement, signature, cause_report, donation_certificate, receipt, image, video
product_typeenum ProductTypeyescharitable_cause, commodity_financing, investment, transaction
product_idUUIDyesID of the parent row the document is attached to
visibilityenum DocumentVisibilityoptionalpublic / private (default private)
POST /v1/documents/upload
Content-Type: multipart/form-data; boundary=...
--boundary
Content-Disposition: form-data; name="file"; filename="receipt.pdf"
Content-Type: application/pdf
<binary>
--boundary
Content-Disposition: form-data; name="document_type"
receipt
--boundary
Content-Disposition: form-data; name="product_type"
transaction
--boundary
Content-Disposition: form-data; name="product_id"
550e8400-e29b-41d4-a716-446655440000
--boundary--
{
"status": "success",
"statusCode": 200,
"message": "Document uploaded successfully",
"data": {
"document": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "documents/2026/receipt.pdf",
"document_type": "receipt",
"category_id": "550e8400-e29b-41d4-a716-446655440000",
"public_url": "https://cdn.example.com/documents/receipt.pdf",
"created_by": "550e8400-e29b-41d4-a716-446655440000",
"updated_by": "550e8400-e29b-41d4-a716-446655440000",
"deleted_by": null,
"created_at": "2026-05-20T08:30:00.000Z",
"updated_at": "2026-05-20T08:30:00.000Z"
}
}
}
StatusWhen
400 Bad RequestFile not provided, file > 50 MB, or DTO validation failed
401 UnauthorizedBearer/cookie invalid

POST /v1/documents bearer create-document RESOURCE_CREATED

Section titled “POST /v1/documents ”

Register a Documents row for an object that already exists in S3 (e.g. uploaded via a side-channel). Most FE callers should use POST /v1/documents/upload instead — this endpoint is only for special cases.

FieldTypeRequiredNotes
urlstringyesS3 file key of an object that already exists in the bucket
document_typeenum DocumentTypeyesSee enum on the reference slide
product_typeenum ProductTypeoptionalParent kind
product_idUUIDoptionalUUID of the parent row
{
"url": "legal-agreements/agreement-123.pdf",
"document_type": "legal_agreement",
"product_type": "charitable_cause",
"product_id": "550e8400-e29b-41d4-a716-446655440000"
}
{
"status": "success",
"statusCode": 201,
"message": "document registered successfully",
"data": {
"document": { "...": "same shape as the upload response" }
}
}
StatusWhen
400 Bad Requesturl empty, unknown enum, invalid UUID
401 UnauthorizedBearer/cookie invalid

GET /v1/documents bearer read-document RESOURCE_FETCHED

Section titled “GET /v1/documents ”

List all documents visible to the user. Ownership filtering happens at the service layer.

{
"status": "success",
"statusCode": 200,
"message": "all documents fetched successfully",
"data": {
"documents": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "documents/2026/receipt.pdf",
"document_type": "receipt",
"category_id": "550e8400-e29b-41d4-a716-446655440000",
"public_url": "https://cdn.example.com/documents/receipt.pdf",
"created_by": "550e8400-e29b-41d4-a716-446655440000",
"updated_by": "550e8400-e29b-41d4-a716-446655440000",
"deleted_by": null,
"created_at": "2026-05-20T08:30:00.000Z",
"updated_at": "2026-05-20T08:30:00.000Z"
}
]
}
}
StatusWhen
401 UnauthorizedBearer/cookie invalid

GET /v1/documents/type/:type bearer read-document RESOURCE_FETCHED

Section titled “GET /v1/documents/type/:type ”

List documents filtered by document_type.

ParamTypeNotes
typeenum DocumentTypeOne of legal_agreement, signature, cause_report, donation_certificate, receipt, image, video
{
"status": "success",
"statusCode": 200,
"message": "documents fetched successfully",
"data": {
"documents": [ "..." ]
}
}
StatusWhen
401 UnauthorizedBearer/cookie invalid

GET /v1/documents/:id bearer read-document RESOURCE_FETCHED

Section titled “GET /v1/documents/:id ”

Detail of a single document by ID.

ParamTypeNotes
idUUIDDocument ID
{
"status": "success",
"statusCode": 200,
"message": "document fetched successfully",
"data": {
"document": { "...": "DocumentDto shape" }
}
}
StatusWhen
401 UnauthorizedBearer/cookie invalid
404 Not FoundDocument not found

GET /v1/documents/:id/view-url bearer read-document RESOURCE_FETCHED

Section titled “GET /v1/documents/:id/view-url ”

Retrieve a presigned (expiring) S3 URL to display the document contents. Use response.data.view_url as the href for an image/iframe — the link is time-limited.

ParamTypeNotes
idUUIDDocument ID
{
"status": "success",
"statusCode": 200,
"message": "presigned url to view document fetched successfully",
"data": {
"document_id": "550e8400-e29b-41d4-a716-446655440000",
"file_key": "documents/2026/passport.pdf",
"view_url": "https://s3.example.com/documents/passport.pdf?X-Amz-Signature=..."
}
}
StatusWhen
401 UnauthorizedBearer/cookie invalid
404 Not FoundDocument not found

PATCH /v1/documents/:id bearer update-document RESOURCE_UPDATED

Section titled “PATCH /v1/documents/:id ”

Update document metadata (e.g. move to another product, change document_type). All fields are optional — send only what changed.

ParamTypeNotes
idUUIDDocument ID

Request body — UpdateDocumentDto (PartialType of CreateDocumentDto)

Section titled “Request body — UpdateDocumentDto (PartialType of CreateDocumentDto)”
FieldTypeNotes
urlstringNew file key
document_typeenum DocumentTypeChange type
product_typeenum ProductTypeChange parent kind
product_idUUIDMove to a different parent row
{
"document_type": "donation_certificate"
}
{
"status": "success",
"statusCode": 200,
"message": "document updated successfully",
"data": {
"document": { "...": "DocumentDto shape" }
}
}
StatusWhen
401 UnauthorizedBearer/cookie invalid
404 Not FoundDocument not found

DELETE /v1/documents/:id bearer delete-document RESOURCE_DELETED

Section titled “DELETE /v1/documents/:id ”

Delete a document record (soft delete — deleted_at + deleted_by are set).

ParamTypeNotes
idUUIDDocument ID
{
"status": "success",
"statusCode": 200,
"message": "document removed successfully",
"data": {}
}
StatusWhen
401 UnauthorizedBearer/cookie invalid
404 Not FoundDocument not found

POST /v1/official-documents bearer create-official-document

Section titled “POST /v1/official-documents ”

Create an official identity record (passport, KTP, SIM, NPWP). The CreateOfficialDocumentDto is currently empty in the repo — the actual fields are determined by the service layer (likely accepts number, type, issue/expiry date, file_key). Confirm with backend before FE implementation.

Request body — CreateOfficialDocumentDto

Section titled “Request body — CreateOfficialDocumentDto”

The DTO is currently empty (no fields). Check the service layer for the actual field conventions.

{
"status": "success",
"statusCode": 201,
"message": "official document created",
"data": { "...": "shape determined by service" }
}
StatusWhen
400 Bad RequestValidation failed
401 UnauthorizedBearer/cookie invalid

GET /v1/official-documents bearer read-official-document

Section titled “GET /v1/official-documents ”

List all accessible official identities.

{
"status": "success",
"statusCode": 200,
"message": "official documents fetched",
"data": { "official_documents": [ "..." ] }
}
StatusWhen
401 UnauthorizedBearer/cookie invalid

GET /v1/official-documents/:id bearer read-official-document

Section titled “GET /v1/official-documents/:id ”

Detail of a single official identity.

ParamTypeNotes
idstring (numeric)Official identity ID. Service casts to +id (numeric).
{
"status": "success",
"statusCode": 200,
"message": "official document fetched",
"data": { "official_document": { "..." } }
}
StatusWhen
401 UnauthorizedBearer/cookie invalid
404 Not FoundNot found

PATCH /v1/official-documents/:id bearer update-official-document

Section titled “PATCH /v1/official-documents/:id ”

Update an official identity. UpdateOfficialDocumentDto = PartialType of the create DTO (currently empty).

ParamTypeNotes
idstring (numeric)Official identity ID
{
"status": "success",
"statusCode": 200,
"message": "official document updated",
"data": { "official_document": { "..." } }
}

DELETE /v1/official-documents/:id bearer delete-official-document

Section titled “DELETE /v1/official-documents/:id ”

Delete an official identity.

ParamTypeNotes
idstring (numeric)Official identity ID
{
"status": "success",
"statusCode": 200,
"message": "official document removed",
"data": {}
}
StatusWhen
401 UnauthorizedBearer/cookie invalid
404 Not FoundNot found

  • legal_agreement
  • signature
  • cause_report
  • donation_certificate
  • receipt
  • image
  • video
  • charitable_cause
  • commodity_financing
  • investment
  • transaction
  • public
  • private (default)
  • passport
  • national_id
  • driving_license
  • taxpayer_id
{
"message": "Document not found",
"statusCode": 404,
"error": "Not Found"
}

message can be an array of strings for multi-field validation errors.

  • 400 body/param validation
  • 401 invalid Bearer
  • 403 permission mismatch
  • 404 document not found
  • 413 file > 50 MB (direct upload)
  • 500 internal — show a generic toast
  • Direct upload limit: 50 MB per file.
  • The S3 file key in the url column is encrypted at rest. The public_url field in the response is already resolved.