> For the complete documentation index, see [llms.txt](https://docs.startrail.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.startrail.io/issue-transfer-api/issue-and-transfer-srr-nft/issuance-flow.md).

# Issuance Flow (End-to-End)

This page shows the **recommended order of API calls** to issue SRRs, from file upload to mining confirmation. For payload bodies see [Request Payloads & Recipes](/issue-transfer-api/issue-and-transfer-srr-nft/payloads.md); for failures see [Errors & Troubleshooting](/issue-transfer-api/issue-and-transfer-srr-nft/errors.md).

{% hint style="info" %}
**The one rule that shapes the whole flow:** every file an SRR references (`thumbnailURL`, `image`, `contractTerms.fileURL`, `attachmentFiles[*].url`) must be uploaded via [`/commerce/signedUrls`](/issue-transfer-api/issue-and-transfer-srr-nft/request-signed-url-for-upload-download-files.md) and referenced by the returned **`finalUrl`** — see [File URLs](/issue-transfer-api/issue-and-transfer-srr-nft/issue-and-transfer.md#file-urls-every-file-must-come-from-the-signed-url-flow). The `finalUrl` is in the signed-URL response **immediately**, so you can build your issuance payload right away; you only **wait** (poll [`/fileMetadata`](/issue-transfer-api/issue-and-transfer-srr-nft/file-information-metadata.md)) for the file's `hash` + `cid` before actually calling the issue endpoint.
{% endhint %}

## The big picture

```mermaid
flowchart TD
    A([Start: issue one or more SRRs]) --> B{"New files, or reusing uploaded ones?"}
    B -- "New files" --> C["1 POST /commerce/signedUrls<br/>action: write - one entry per file<br/>(keep each finalUrl)"]
    C --> D["2 HTTP PUT each file<br/>to its signed url"]
    D --> E["3 POST /commerce/fileMetadata<br/>poll until hash + cid present<br/>for every file"]
    B -- "Reusing files" --> F["Use the stored finalUrl<br/>(files already have hash + cid)"]
    E --> G["4 POST /commerce/srrs<br/>requestId + payload (1..n entries)<br/>all file URLs = finalUrl"]
    F --> G
    G --> H["5 Response 201<br/>status: waiting_for_mining<br/>(tokenId + metadataCID per entry)"]
    H --> I["6 Webhook fires on<br/>mining success / failure"]
    I --> J([Read full SRR data<br/>from the subgraph])
```

The same six steps apply to **single and bulk** — bulk just carries more entries in the same two arrays (`signedUrls.payload[]` for files, `srrs.payload[]` for SRRs).

## Flow A — single SRR with a new file

```mermaid
sequenceDiagram
    autonumber
    participant C as Your server
    participant API as Commerce API
    participant GCS as Storage
    C->>API: POST /commerce/signedUrls {payload:[{filename, category}], action:"write"}
    API-->>C: results[0]: { url, finalUrl, contentType }
    C->>GCS: PUT file to url (Content-Type from response)
    loop until hash + cid present
        C->>API: POST /commerce/fileMetadata {payload:[{filename, category}]}
        API-->>C: { hash, cid } or STILL_IN_CALCULATION
    end
    C->>API: POST /commerce/srrs { requestId, payload:[ SRR with finalUrl ] }
    API-->>C: 201 { results:[{ status: waiting_for_mining, srr:{ tokenId, metadataCID } }] }
    API--)C: Webhook: issuance mined (or failed)
```

| # | Step               | Endpoint                                                                                                       | You provide                                                                                                                                                                                                           | You get / wait for                                                  |
| - | ------------------ | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| 1 | Request signed URL | `POST /commerce/signedUrls`                                                                                    | `filename` (unique per issuer, no spaces, with extension) + `category` (see the [category table](/issue-transfer-api/issue-and-transfer-srr-nft/request-signed-url-for-upload-download-files.md)) + `action: "write"` | `url` (upload target), **`finalUrl`** (store it!), `contentType`    |
| 2 | Upload             | HTTP `PUT` to `url`                                                                                            | the file bytes, `Content-Type` from step 1                                                                                                                                                                            | `200`                                                               |
| 3 | Wait for readiness | `POST /commerce/fileMetadata`                                                                                  | same `filename` + `category`                                                                                                                                                                                          | poll until **`hash` and `cid`** are present (usually a few minutes) |
| 4 | Issue              | `POST /commerce/srrs`                                                                                          | `requestId` (fresh UUID) + `payload[0]` with `metadata.thumbnailURL` / `image` (= `finalUrl`), optional `attachmentFiles`                                                                                             | `201` with `tokenId`, `metadataCID`, `status: waiting_for_mining`   |
| 5 | Confirm            | [Webhook](/issue-transfer-api/issue-and-transfer-srr-nft/webhook-setup.md) / [subgraph](/subgraph/subgraph.md) | —                                                                                                                                                                                                                     | mining outcome; full SRR data                                       |

{% hint style="warning" %}
Use the **public `non_attachment_file` category** for files going into `thumbnailURL` / `image` / `contractTerms.fileURL` — private categories are rejected for those fields. One file can serve several roles (e.g. the same `finalUrl` as `thumbnailURL` **and** `image`).
{% endhint %}

## Flow B — bulk issuance (many SRRs, many files)

Bulk is the **same flow with batched arrays** — do **not** loop the single flow per SRR.

```mermaid
flowchart LR
    subgraph "Files (one batch)"
        A["POST /signedUrls<br/>payload: N file entries"] --> B["PUT × N<br/>(parallel OK)"]
        B --> C["POST /fileMetadata<br/>payload: N entries<br/>poll until ALL ready"]
    end
    subgraph "Issuance (one request)"
        D["POST /srrs<br/>requestId + payload: M SRR entries<br/>(each with its finalUrls)"] --> E["201: M results<br/>waiting_for_mining"]
    end
    C --> D
```

Key differences from single:

| Concern                                      | Rule                                                                                                                                                                        |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Files per `signedUrls` / `fileMetadata` call | Any number (`payload[]` array) — batch them, upload in parallel                                                                                                             |
| SRRs per `srrs` call                         | 1..n in `payload[]`; **≤ 250 recommended** (no hard cap)                                                                                                                    |
| Atomicity                                    | **All-or-nothing**: one failed entry cancels the whole batch — fix the entry named in `results[]` and resubmit everything                                                   |
| Readiness                                    | Wait until **every** referenced file has `hash` + `cid` before issuing — one not-ready file fails the whole batch                                                           |
| NFC tags                                     | The chip **sum across all entries** must fit the LUW's NFC allowance — see [NFC errors](/issue-transfer-api/issue-and-transfer-srr-nft/errors.md#nfc-tags-and-quota-errors) |
| Idempotency                                  | One `requestId` for the whole batch; reuse is rejected (`Duplicate Request Error`)                                                                                          |

## Flow C — reusing previously uploaded files

Files stay in storage and keep their `finalUrl`, `hash` and `cid` forever — **upload once, reference many times**.

```mermaid
flowchart TD
    A{"Do you still have the finalUrl?"} -- Yes --> D["POST /commerce/srrs<br/>reference the stored finalUrl directly<br/>(no upload, no polling needed)"]
    A -- No --> B["POST /commerce/signedUrls<br/>same filename + category, action: read<br/>(returns the finalUrl again - no upload)"]
    B --> C["Optional: POST /commerce/fileMetadata<br/>to confirm the file still exists and is ready"]
    C --> D
```

* **Store `finalUrl`s** alongside your own records at upload time — that makes reuse a zero-step operation.
* A reused file needs **no re-upload and no polling**: its `hash`/`cid` were computed after the original upload.
* Reuse is natural when: many editions share one artwork image, a shared contract-terms PDF, or re-issuing after a failed batch.
* Filenames are **unique per `issuer-address`** — you cannot re-upload a new file under a used name; pick a new name for new content. Identical content under a different name resolves to the **same CID** on IPFS, so duplicates are harmless.

## Checklist (for humans and LLMs)

```
RECOMMENDED ISSUANCE FLOW (commerce API, base: <base_url>/port/api/v1)
Headers on every call: commerce-api-key, issuer-address, Content-Type: application/json

FOR EACH NEW FILE (batchable):
  1. POST /commerce/signedUrls   body: {payload:[{filename,category}], action:"write"}
     -> save results[i].finalUrl               (available immediately)
     -> category: non_attachment_file for thumbnail/image/contract-terms files,
        artwork for digital-artwork files, private categories for confidential attachments
  2. PUT <results[i].url>        body: file bytes, Content-Type: results[i].contentType
  3. POST /commerce/fileMetadata body: {payload:[{filename,category}]}
     -> repeat until every file returns hash AND cid   (do not issue before this)

FOR REUSED FILES: skip 1-3, use the stored finalUrl
  (lost it? POST /commerce/signedUrls with action:"read" returns it again)

ISSUE (single or bulk, same endpoint):
  4. POST /commerce/srrs body: {requestId:<fresh UUID>, payload:[1..n SRR entries]}
     -> every thumbnailURL / image / contractTerms.fileURL / attachmentFiles[].url
        MUST be a finalUrl; external URLs are rejected (400)
     -> batch is atomic; <=250 entries recommended
     -> response 201: results[i].srr.tokenId + metadataCID, status waiting_for_mining

CONFIRM:
  5. Webhook notifies mining success/failure; read full SRR data from the subgraph.
```

## Common pitfalls

| Pitfall                                      | What happens                                                               | Avoid by                                                                                                                                  |
| -------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Issuing before `cid` is ready                | `400` `… not ready for IPFS publication …` / `Attachment hash not ready …` | Step 3 — poll `/fileMetadata` first                                                                                                       |
| External file URL (your own CDN, Shopify, …) | `400` `File URLs must be Startbahn storage URLs …`                         | Always use the `finalUrl`                                                                                                                 |
| Private category for a thumbnail/image       | `400` `… must be publicly accessible …`                                    | Upload with `non_attachment_file`                                                                                                         |
| Reusing a `requestId`                        | `400` `Duplicate Request Error`                                            | Fresh UUID per request; reuse only intended as a retry-guard                                                                              |
| Retrying a failed bulk with the same body    | `400` `Request Content Error: … already logged`                            | Fix the failing entry, keep a new `requestId`                                                                                             |
| Assuming `201` = minted                      | SRR may still fail at mining                                               | Wait for the [webhook](/issue-transfer-api/issue-and-transfer-srr-nft/webhook-setup.md); verify via the [subgraph](/subgraph/subgraph.md) |

{% content-ref url="/pages/0jhQ5HkfFA9BLusH5SmK" %}
[Request Payloads & Recipes](/issue-transfer-api/issue-and-transfer-srr-nft/payloads.md)
{% endcontent-ref %}

{% content-ref url="/pages/oOU0grtYrgSIzFZqyMWb" %}
[Errors & Troubleshooting](/issue-transfer-api/issue-and-transfer-srr-nft/errors.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.startrail.io/issue-transfer-api/issue-and-transfer-srr-nft/issuance-flow.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
