# Token document uploads

Source: https://docs.settlemint.com/docs/api-reference/tokens/token-documents
Upload, confirm, list, download, and delete token or asset documents through the DALP API, SDK, and CLI.



The token document API manages files attached to an asset: a prospectus, term
sheet, regulatory filing, compliance report, certificate, reserve audit, or other
supporting file. The API uses the same two-step pattern as other upload flows:

1. Request a presigned upload URL for a token document.
2. Upload the file directly to storage using the returned method and headers.
3. Confirm the upload so DALP records the file against the token.
4. List, download, or delete the record later through the token document API.

This flow covers token or asset documents. Use the KYC document upload guide when
the file belongs to a user's KYC profile instead of an asset.

## Before you start [#before-you-start]

You need:

* a DALP API key with access to the token document operations.
* the token contract address for the asset that owns the document.
* the asset profile, because DALP validates `documentType` against the profile.
* the file name, MIME type, size in bytes, and visibility level before requesting
  an upload URL.

## Flow at a glance [#flow-at-a-glance]

The upload URL and confirm calls are separate because the file bytes go
directly to storage. DALP records the file only after you confirm the
returned `objectKey`.

<Mermaid
  chart="sequenceDiagram
    participant App as Integration app
    participant API as DALP API
    participant Storage as Document storage

    App->>API: POST /api/v2/tokens/{tokenAddress}/document-uploads
    API-->>App: uploadUrl, method, headers, objectKey, expiresAt
    App->>Storage: PUT file bytes with returned headers
    Storage-->>App: Upload accepted
    App->>API: POST /api/v2/tokens/{tokenAddress}/documents with objectKey
    API-->>App: Token document record with id, groupId, fileHash
    App->>API: List, download, or delete document records"
/>

If the direct storage upload fails, do not confirm. Request a fresh upload URL
when the returned `expiresAt` time has passed or when storage rejects the
returned headers.

## Endpoints [#endpoints]

The token document API exposes these operations:

* `GET /api/v2/tokens/{tokenAddress}/documents` lists token documents with filtering and sorting (paginated).
* `POST /api/v2/tokens/{tokenAddress}/document-uploads` returns a presigned upload URL.
* `POST /api/v2/tokens/{tokenAddress}/documents` confirms an uploaded file and creates the document record.
* `POST /api/v2/tokens/{tokenAddress}/documents/{documentId}/downloads` returns a secure download URL.
* `DELETE /api/v2/tokens/{tokenAddress}/documents/{documentId}` deletes a token document.

## Request an upload URL [#request-an-upload-url]

Request an upload URL before sending the file bytes. The request describes the
file and how it should be classified on the asset.

```ts fixture=dalp-client group=token-documents
const upload = await client.token.documents.getUploadUrl({
  params: {
    tokenAddress: "0xTOKEN",
  },
  body: {
    documentType: "prospectus",
    fileName: "bond-prospectus.pdf",
    fileSize: 2_400_000,
    mimeType: "application/pdf",
    visibility: "public",
    title: "Bond prospectus",
    description: "Published prospectus for investor review",
  },
});
```

The upload URL request accepts these fields:

| Field          | Required | Description                                                                  |
| -------------- | -------- | ---------------------------------------------------------------------------- |
| `documentType` | Yes      | Token document type. Use a value allowed by the asset profile.               |
| `fileName`     | Yes      | File name, up to 255 characters.                                             |
| `fileSize`     | Yes      | Integer size in bytes. The value must be positive and no larger than 50 MiB. |
| `mimeType`     | Yes      | One of the supported MIME types below.                                       |
| `visibility`   | Yes      | `public`, `holders`, or `restricted`.                                        |
| `title`        | No       | Display title, up to 500 characters.                                         |
| `description`  | No       | Description, up to 2,000 characters.                                         |

### Choose the document type for the asset profile [#choose-the-document-type-for-the-asset-profile]

`documentType` is validated against the asset profile, not only against the
shared document-type list. Use the value that matches the asset being filed.
For reserve-backed assets, choose a type that describes the external file or
attestation you are attaching to the token record. The asset profile also accepts
common legal and regulatory types, including compliance filings. See the full
table below.

| Asset profile    | Reserve or asset evidence document types                                                                        |
| ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `stablecoin`     | `reserve_audit`, `attestation_report`, `reserve_composition`, `certificate`, or `other`.                        |
| `precious-metal` | `assay_certificate`, `storage_receipt`, `chain_of_custody`, `insurance_certificate`, `certificate`, or `other`. |

Use the stablecoin values for reserve audits, verifier attestations, or
reserve-composition files. Use the precious-metal values for assay certificates,
storage receipts, custody-chain files, or insurance certificates. For legal,
regulatory, or compliance files, keep the more specific common type when the
asset profile accepts it. Use `other` only when no accepted value fits, then
set a clear `title` and `description` so reviewers can identify the file later.

Supported MIME types are:

* `application/pdf`
* `image/jpeg`
* `image/png`
* `image/webp`
* `application/vnd.openxmlformats-officedocument.wordprocessingml.document`
* `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`

The upload URL response includes:

| Field       | Description                                               |
| ----------- | --------------------------------------------------------- |
| `uploadUrl` | Presigned URL for the direct file upload.                 |
| `objectKey` | Storage object key to send in the confirm request.        |
| `expiresAt` | Expiry timestamp for the presigned URL.                   |
| `method`    | HTTP method for the upload. Token documents use `PUT`.    |
| `headers`   | Headers that must be sent with the direct storage upload. |

## Upload the file bytes [#upload-the-file-bytes]

Upload the file directly to the returned URL using the returned method and
headers. Some storage backends add provider-specific headers to the URL
response.

```ts group=token-documents
await fetch(upload.data.uploadUrl, {
  method: upload.data.method,
  headers: upload.data.headers,
  body: fileBytes,
});
```

Do not replace the returned headers with only `Content-Type`. Provider-specific
headers are part of the upload contract.

## Confirm the uploaded file [#confirm-the-uploaded-file]

After the file upload succeeds, confirm it with the returned `objectKey`.
DALP creates the token document record and returns the stored file metadata.

```ts group=token-documents
const document = await client.token.documents.confirmUpload({
  params: {
    tokenAddress: "0xTOKEN",
  },
  body: {
    objectKey: upload.data.objectKey,
    documentType: "prospectus",
    fileName: "bond-prospectus.pdf",
    fileSize: 2_400_000,
    mimeType: "application/pdf",
    visibility: "public",
    title: "Bond prospectus",
    description: "Published prospectus for investor review",
  },
});
```

The confirm request repeats the file metadata and adds `objectKey`. It also
accepts `replaceGroupId` when the new file replaces an earlier document in the
same version group.

The response includes the document `id`, `groupId`, `versionNumber`, `isLatest`,
`fileHash`, `uploadedAt`, and uploader details.

DALP calculates `fileHash` from the uploaded bytes when you confirm. Store that
value in downstream systems to verify the asset document still points to the
expected file.

## On-chain file hash claims [#on-chain-file-hash-claims]

Upload records and asset-level claims serve different purposes:

* The token document upload flow stores the file metadata, version group, and
  `fileHash` that belongs to the uploaded file.
* An asset-level document-hash claim can record a SHA-256 hash, document type,
  and file name without placing the file contents on-chain.

Use the upload flow to manage access, download links, and versioning. Use an
asset-level document-hash claim when an asset also needs an on-chain hash
anchor for a specific file.

## Choose document type and visibility [#choose-document-type-and-visibility]

Choose the document type from the token's asset profile, not from the full token
catalog. DALP uses the asset type to narrow the choices shown in the Console
upload dialog: a stablecoin shows reserve options; a precious metal shows assay,
storage, custody-chain, and insurance options.

| Asset profile   | Document type choices                                                                                                                                                                                             |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bonds           | `prospectus`, `term_sheet`, `legal_opinion`, `regulatory_filing`, `compliance_report`, `annual_report`, `financial_statement`, `credit_rating`, `covenant_agreement`, `interest_schedule`, `certificate`, `other` |
| Equity          | `prospectus`, `term_sheet`, `legal_opinion`, `regulatory_filing`, `compliance_report`, `annual_report`, `financial_statement`, `shareholder_agreement`, `certificate`, `other`                                    |
| Funds           | `prospectus`, `term_sheet`, `legal_opinion`, `regulatory_filing`, `compliance_report`, `annual_report`, `financial_statement`, `fund_fact_sheet`, `subscription_agreement`, `nav_report`, `certificate`, `other`  |
| Stablecoins     | `legal_opinion`, `regulatory_filing`, `compliance_report`, `reserve_audit`, `attestation_report`, `reserve_composition`, `certificate`, `other`                                                                   |
| Deposits        | `term_sheet`, `legal_opinion`, `regulatory_filing`, `compliance_report`, `financial_statement`, `interest_schedule`, `certificate`, `other`                                                                       |
| Real estate     | `legal_opinion`, `regulatory_filing`, `compliance_report`, `appraisal`, `property_deed`, `survey_report`, `environmental_assessment`, `title_insurance`, `insurance_certificate`, `certificate`, `other`          |
| Precious metals | `legal_opinion`, `regulatory_filing`, `compliance_report`, `assay_certificate`, `storage_receipt`, `chain_of_custody`, `insurance_certificate`, `certificate`, `other`                                            |

For API and CLI integrations, send a `documentType` value that belongs to the
asset profile you are updating. For example, use `reserve_audit`,
`attestation_report`, or `reserve_composition` for stablecoin reserve files,
and use `assay_certificate`, `storage_receipt`, or `chain_of_custody` for
precious metal files. Use `other` only when the file does not fit a more
specific type.

The `visibility` field controls who can access the file:

* `public`: visible to anyone who can access the token document surface.
* `holders`: visible to token holders.
* `restricted`: limited to explicitly allowed access paths.

Choose the narrowest value that fits the operating process and regulatory
basis for the file. When in doubt, restrict access and widen it later.

## List documents [#list-documents]

Use the list endpoint to reconcile published files or populate an asset's
document table. Call it from a background job or on demand when a user requests
the file list for an asset.

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/documents?page[limit]=50&sort=-uploadedAt" \
  -H "X-Api-Key: sm_dalp_xxxxxxxxxxxxxxxx"
```

The list endpoint supports sorting, filtering, and facets with standard
pagination. Sort by `fileName`, `documentType`, `visibility`, `fileSize`, or
`uploadedAt`. Filter by `fileName`, `documentType`, `visibility`,
`mimeType`, `isLatest`, `fileSize`, or `uploadedAt`.

Use the standard collection filter shape to narrow results. For example, request
the latest public prospectuses uploaded after a cutoff time:

```bash
curl --globoff "https://your-platform.example.com/api/v2/tokens/0xTOKEN/documents?filter[documentType][eq]=prospectus&filter[visibility][eq]=public&filter[uploadedAt][gte]=2026-01-01T00:00:00.000Z" \
  -H "X-Api-Key: sm_dalp_xxxxxxxxxxxxxxxx"
```

The API returns only the latest non-deleted records visible to the caller. Public
files are accessible through the token document surface. Holder-scoped files
require a caller with an asset role; restricted files require governance or admin
rights.

The list response uses the standard shape with `data`, `meta`, and `links`. Each
row includes the document identifiers, token address, type, access level, version
group fields, file metadata, optional title and description, `fileHash`,
`uploadedAt`, and uploader details.

## Download or delete [#download-or-delete]

To retrieve a file, request a secure download URL:

```ts group=token-documents
const download = await client.token.documents.getDownloadUrl({
  params: {
    tokenAddress: "0xTOKEN",
    documentId: document.data.id,
  },
});
```

To remove a file from the asset's record, call delete. Only do this when the
file should no longer appear on the API surface:

```ts group=token-documents
await client.token.documents.delete({
  params: {
    tokenAddress: "0xTOKEN",
    documentId: document.data.id,
  },
});
```

Keep your delete reasoning and approval records in your operating logs. The
call removes the file from the API surface but does not replace your regulated
record-keeping process.

## CLI commands [#cli-commands]

The DALP CLI exposes the same operations:

```bash
dalp tokens documents list 0xTOKEN

dalp tokens documents get-upload-url \
  --address 0xTOKEN \
  --fileName bond-prospectus.pdf \
  --fileSize 2400000 \
  --mimeType application/pdf \
  --documentType prospectus \
  --visibility public

dalp tokens documents confirm-upload \
  --address 0xTOKEN \
  --objectKey uploads/token-documents/example-object-key \
  --documentType prospectus \
  --fileName bond-prospectus.pdf \
  --fileSize 2400000 \
  --mimeType application/pdf \
  --visibility public

dalp tokens documents get-download-url \
  --address 0xTOKEN \
  --documentId doc_123

dalp tokens documents delete \
  --address 0xTOKEN \
  --documentId doc_123
```

Use the API or SDK for the direct file upload step. The CLI returns the presigned URL and object key but does not upload local file bytes.

## See also [#see-also]

* [KYC document uploads](/docs/api-reference/compliance/kyc-document-uploads)
* [Token lifecycle](/docs/api-reference/tokens/token-lifecycle)
* [Token holders and transfers](/docs/api-reference/tokens/token-holders-transfers)
