> 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/issue-and-transfer.md).

# Issue & Transfer

<mark style="color:green;">`POST`</mark> `<base_url>/port/api/v1/commerce/srrs`

Please replace `<base_url>` as explained [here](/readme/url-per-environment.md).

{% hint style="success" %}
**Looking for the flow, a payload to copy, or decoding an error?**

* [Issuance Flow (End-to-End)](/issue-transfer-api/issue-and-transfer-srr-nft/issuance-flow.md) — flowcharts for single/bulk issuance with new or reused files.
* [Request Payloads & Recipes](/issue-transfer-api/issue-and-transfer-srr-nft/payloads.md) — ready-to-use bodies for every scenario (issue to self, issue-on-buyer, custom collection, NFC tags, attachments, bulk).
* [Errors & Troubleshooting](/issue-transfer-api/issue-and-transfer-srr-nft/errors.md) — every status code and message this endpoint can return, with causes and fixes.
  {% endhint %}

### Precaution

#### Multiple issuance

{% hint style="warning" %}
Feel free to include more than one issuance in a single request via the `payload` array. The minimum number of issue requests is one. Though there is no fixed upper limit for the number of issue requests you can submit at one time, we recommend limiting batches to no more than 250 issue requests for optimal processing.
{% endhint %}

#### issue to issuer

{% hint style="info" %}
If payload\[\*].to is not given, your SRR will be issued to your `luw` address
{% endhint %}

#### File URLs — every file must come from the signed-URL flow

{% hint style="danger" %}
**All file URLs in an issuance request must be the `finalUrl` returned by** [**`/commerce/signedUrls`**](/issue-transfer-api/issue-and-transfer-srr-nft/request-signed-url-for-upload-download-files.md)**.** The `finalUrl` is available immediately in the signed-URL response — no extra processing needed; our system converts it to a permanent IPFS URL at issuance. (URLs already on IPFS — `ipfs://…` or `https://cdn.startrail.io/ipfs/…` — are also accepted, but deriving them yourself requires polling `/commerce/fileMetadata` for the CID and building the URL manually, so prefer the `finalUrl`.) This applies to **all four** URL locations:

* `payload[*].metadata.thumbnailURL`
* `payload[*].metadata.image`
* `payload[*].metadata.contractTerms.fileURL`
* `payload[*].attachmentFiles[*].url`

**Why:** during issuance, Startrail converts Startbahn-storage URLs to permanent `ipfs://` URLs and publishes the files to IPFS. A URL on any other host **cannot be published to IPFS** — the certificate would forever reference a mutable third-party file with no permanence or integrity guarantee. External URLs are therefore **rejected with `400`** (see [Errors](/issue-transfer-api/issue-and-transfer-srr-nft/errors.md#request-level-errors-400)).

The full flow for every file (thumbnail, image, contract terms, attachments):

1. [Request a signed URL](/issue-transfer-api/issue-and-transfer-srr-nft/request-signed-url-for-upload-download-files.md) (`action: "write"`) — keep the returned `finalUrl`
2. Upload the file with HTTP `PUT` to the signed URL
3. [Poll `/commerce/fileMetadata`](/issue-transfer-api/issue-and-transfer-srr-nft/file-information-metadata.md) until the file's `hash` **and** `cid` are returned
4. Use the `finalUrl` in `metadata.thumbnailURL` / `image` / `contractTerms.fileURL` or `attachmentFiles[*].url`

Note: `metadata.external_url` is exempt — it is an external reference link by design and is never uploaded to IPFS.
{% endhint %}

{% hint style="warning" %}
**Metadata files must also be publicly accessible.** `thumbnailURL`, `image` and `contractTerms.fileURL` are served to certificate viewers, so they must be uploaded with a **public** file category in the [signed-URL request](/issue-transfer-api/issue-and-transfer-srr-nft/request-signed-url-for-upload-download-files.md): **`non_attachment_file`** (recommended for these fields) or **`artwork`**. Files uploaded with a **private** category (`certificate`, `for_authenticity`, `installation`) land in the private bucket, cannot be served or published to IPFS, and are **rejected with `400`** when referenced by these metadata fields. `attachmentFiles[*].url` may use any category, including private ones — attachments are folded into the metadata as content hashes, not URLs.
{% endhint %}

#### Headers

| Name                                               | Type   | Description                       |
| -------------------------------------------------- | ------ | --------------------------------- |
| commerce-api-key<mark style="color:red;">\*</mark> | string | Commerce API Key                  |
| issuer-address<mark style="color:red;">\*</mark>   | string | Contract Address of API Key owner |

#### Request Body

| Name                                                                | Type           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| requestId                                                           | string         | <p>A <code>requestId</code> given by the caller, to ensure requests are only processed once. If the <code>requestId</code> is known and processed before, api will not process this call again, and respond with an error. A good practice is using random UUID.</p><p>\*<code>requestId</code> must be unique for a given <code>issuer-address</code>. As a result any duplicate combination of <code>requestId</code> and <code>issuer-address</code> is instantly rejected with no impact on either.</p>                                                                                                                                                                                                            |
| payload<mark style="color:red;">\*</mark>                           | array          | Array of issue requests. Further constraints explained [above](#multiple-issuance).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| payload\[\*].externalId<mark style="color:red;">\*</mark>           | string         | An ID to identify the record in your system. We recommend to use UUID, but it can use any string as long it is unique in your system.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| payload\[\*].metadata<mark style="color:red;">\*</mark>             | object         | <p>The metadata to be issued as a complex object. Detailed schema specification can be found <a href="/pages/cfyaLly9LtHuauzFV359">here</a>.</p><p>The API accepts versions 2.0 and higher.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| payload\[\*].artistAddress<mark style="color:red;">\*</mark>        | string         | The ethereum address of the artist of the artwork.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| payload\[\*].isPrimaryIssuer<mark style="color:red;">\*</mark>      | boolean        | If you are the primary issuer of this NFT, set this to true.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| payload\[\*].lockExternalTransfer<mark style="color:red;">\*</mark> | boolean        | If you want to prevent your NFTs to be transferred on decentralized marketplaces, set this to true.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| payload\[\*].to                                                     | string         | <p>Ethereum address target the NFT should be sent to after minting (Issue on Buyer).</p><p>If none is given the NFT will be minted into your LUW by default.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| payload\[\*].attachmentFiles                                        | Array\<object> | Attachment files that will be included in SRR.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| payload\[\*].attachmentFiles\[\*].name                              | string         | <p>The name of file.</p><p>This is used for when the file is downloaded or shown. The extension is recommended to be the same as the actual uploaded file.</p>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| payload\[\*].attachmentFiles\[\*].category                          | string         | <p>Please refer to <a href="https://help.port.startrail.io/hc/en-us/articles/7720691967767-What-is-an-Artwork-File-">this page</a> to understand the difference among the categories.</p><p>Please note that contract terms and thumbnail are NOT attachment files. The URL for contract terms and thumbnail are needed for metadata. (See metadata attribute)</p>                                                                                                                                                                                                                                                                                                                                                     |
| payload\[\*].attachmentFiles\[\*].url                               | string         | <p>The URL must be under Startbahn's GCS bucket. Use the <code>finalUrl</code> field returned by the <a href="/pages/0iLTiPfX9AyKSw5AoPRc">signed URL endpoint</a>, or a URL that Startbahn has pre-provisioned for you. Any other URL is rejected without issuing the SRR.</p><p>For each <code>attachmentFiles</code> entry, the API resolves the URL to its GCS path, reads the precomputed SHA-256 from the storage object metadata, and folds <code>{category, hash}</code> entries into <code>metadata.digitalComponents</code> (<code>artwork</code> category) or <code>metadata.attachmentFiles</code> (other categories) before issuance. You do not need to populate those metadata sub-fields yourself.</p> |
| payload\[\*].collectionAddress                                      | string         | The address of collection that the SRR will belong to. This collection must be owned by the caller `issuer-address`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| payload\[\*].externalUrls                                           | Array\<string> | <p>Deprecated. Use the <code>external\_url</code> field in metadata.</p><p>To remain backward compatible the external URLs is still allowed and is simply passing the first string in the array to the metadata as <code>external\_url</code>). This mapping is redundant and will be removed.</p>                                                                                                                                                                                                                                                                                                                                                                                                                     |

{% tabs %}
{% tab title="201: Created " %}
The API responds with 201. see Response Body results\[\*].status for details of each entry.

{% tabs %}
{% tab title="Body" %}

<table><thead><tr><th>Body Attribute</th><th width="259.66666666666663">Description</th><th>Format</th></tr></thead><tbody><tr><td>results</td><td>results of the request</td><td>Array</td></tr><tr><td>results[*].srr</td><td>Minimal identifying details of the issued SRR. Richer information (issuer, artist, collection details, on-chain metadata, history, ownership) must be queried from the <a href="/pages/DEDTJpFrrjYII6dFBDUe">subgraph</a> once the transaction is mined.</td><td>object</td></tr><tr><td>results[*].srr.tokenId</td><td>NFT token ID of the SRR.</td><td>string</td></tr><tr><td>results[*].srr.metadataCID</td><td>IPFS CID of the SRR metadata. Resolve via the Startrail IPFS CDN gateway: <code>https://cdn.startrail.io/ipfs/&#x3C;cid></code>. See <a href="/pages/nsrhLznHxyKf7AAMT1A0">IPFS CDN gateway</a>.</td><td>string</td></tr><tr><td>results[*].srr.metadataURL</td><td>Convenience HTTPS URL that resolves the SRR metadata via the Startrail IPFS CDN gateway. Same content as <code>metadataCID</code> resolved through <code>https://cdn.startrail.io/ipfs/</code>.</td><td>string</td></tr><tr><td>results[*].srr.collectionContractAddress</td><td>Collection contract address the SRR belongs to. <code>null</code> if the SRR was not issued under a custom collection.</td><td>string | null</td></tr><tr><td>results[*].externalId</td><td>ID to identify the SRR. Defined by the client when calling.</td><td>string</td></tr><tr><td>results[*].status</td><td><code>waiting_for_mining</code> – wait for completion of blockchain mining. The status (and any further SRR data) can then be confirmed via the <a href="/pages/DEDTJpFrrjYII6dFBDUe">subgraph</a> or less favorably via the <a href="/pages/qlXBPGW7iA7bCtQeBfrc">Get SRR by Token Id</a> REST endpoint.</td><td>string</td></tr></tbody></table>

{% hint style="info" %}
**Response shape change**

The response intentionally returns only the minimum identifiers needed to track the issued SRR (`tokenId`, `metadataCID`, `metadataURL`, `collectionContractAddress`). Fields previously returned by this endpoint such as `srr.issuer`, `srr.artist`, `srr.collection.{name,symbol}`, `srr.isPrimaryIssuer`, `srr.issuedAt`, `srr.metadata.{json,originalJson,digest}`, `srr.createdAt` and `srr.updatedAt` are no longer part of the issuance response.

To read SRR data after issuance:

* Use the [subgraph](/subgraph/subgraph.md) for issuer, artist, collection, ownership, history and provenance information (authoritative source).
* Use the [Startrail IPFS CDN gateway](/subgraph/ipfs-cdn-gateway.md) (`https://cdn.startrail.io/ipfs/<cid>`) to fetch the SRR metadata JSON, attachment files and images by their CID.
  {% endhint %}
  {% endtab %}

{% tab title="Example" %}

```json
{
  "results": [
    {
      "status": "waiting_for_mining",
      "srr": {
        "tokenId": "227890056407",
        "metadataCID": "bafkreiabepvyxyetkcb3xbjo3ocuyfo6psv3rc3yj34hequwcsilyvjima",
        "metadataURL": "https://cdn.startrail.io/ipfs/bafkreiabepvyxyetkcb3xbjo3ocuyfo6psv3rc3yj34hequwcsilyvjima",
        "collectionContractAddress": "0x7942627305545AF0e6c826d54cC9b2c5D190A874"
      },
      "externalId": "c0456840-5bdf-4375-a6ce-106697b7dfb7"
    }
  ]
}
```

{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="400: Bad Request " %}
The API responds with 400 if request body invalid or some validation errors that can be instantly detected.

```json
// If the metadata is same with another previous SRR
{
  "statusCode": 400,
  "message": {
    "results": [
      {
        "status": "failed",
        "externalId": "c0456840-5bdf-4375-a6ce-106697b7dfb7",
        "message": "DUPLICATE_METADATA"
      }
    ]
  }
}

{
  "statusCode": 400,
  "message": {
    "results": [
      {
        "status": "failed",
        "externalId": "c0456840-5bdf-4375-a6ce-106697b7dfb7",
        "message": "DUPLICATE_CHIP"
      }
    ]
  }
}


{
    "statusCode": 400,
    "message": "Request Content Error: Current request already logged with status successful"
}


{
    "statusCode": 400,
    "message": {
        "results": [
            {
                "status": "failed",
                "externalId": "bulk20122fdredcd2dfve",
                "message": "UNKNOWN_ERROR: external ID: bulk20122fdredcd2dfve."
            }
        ]
    }
}

{
  "statusCode": 400,
  "message": {
    "results": [
      {
        "status": "failed",
        "externalId": "c0456840-5bdf-4375-a6ce-106697b7dfb7",
        "message": "UNKNOWN_ARTIST"
      }
    ]
  }
}

// If metadata with issuer and artist combination is already sent
{
  "statusCode": 400,
  "message": "Request Content Error: Current request already logged with status successful"
}

// DTO validation error
{
    "statusCode": 400,
    "message": [
        "payload.0.metadata is invalid. Check it against the metadata JSON schema. Details:  should have required property 'title' ({\"missingProperty\":\"title\"}).",
        "payload.0.artistAddress must be a valid Ethereum addresses",
        "payload.0.externalId should not be empty",
        "payload.0.externalId must be a string"
    ]
}
```

#### Multiple issuance

In case of multiple/bulk issuance as soon as one case failed, the rest of the payload will be definitely failed as well.

**Example of duplicated metadata**

```json
{
    "statusCode": 400,
    "message": {
        "results": [
            {
                "status": "failed",
                "externalId": "anId",
                "message": "DUPLICATE_METADATA"
            },
            {
                "status": "failed",
                "externalId": "anId",
                "message": "SRR_NOT_CREATED: Not created because there is SRR in the same batch that error"
            }
        ]
    }
}
```

**Example of DTO validation error**

```json
{
    "statusCode": 400,
    "message": [
        "payload.1.metadata is invalid. Check it against the metadata JSON schema. Details:  should have required property 'title' ({\"missingProperty\":\"title\"}).",
        "payload.1.artistAddress must be a valid Ethereum addresses",
        "payload.1.externalId should not be empty",
        "payload.1.externalId must be a string"
    ]
}
```

{% endtab %}

{% tab title="500: Internal Server Error " %}
The API responds with 5xx if there are other issues, such as deeper validation errors.

```json
// For example in case of collection ownership issue
{
  "statusCode": 500,
  "message": "STARTRAIL_ERROR: <reason>"
}

// In case there is unknown error. Please contact us.
{
  "statusCode": 500,
  "message": "UNKNOWN_ERROR: external ID: xxxx"
}
```

{% endtab %}
{% endtabs %}

## Swagger Endpoint (Test Environment)

[Swagger to test](https://api-stg.startrail.startbahn.jp/port/api#/public/CommerceController_)

## Required Permissions

Check the [parent page](/issue-transfer-api/issue-and-transfer-srr-nft.md).

## Request Body Example

```json
{
  "requestId": "0004f572-7769-4b8b-8108-a13a36cd88d4",
  "payload": [
    {
      "externalId": "0004f572-7769-4b8b-8108-a13a36cd88d4",
      "metadata": {
        "$schema": "https://api.startrail.io/api/v1/schema/registry-record-metadata.v2.1.schema.json",
        "$schemaIntegrity": "sha256-15f8e99eb9d4292287282942db2f2de9bbcc4761c555c6f7da23feec010c1221",
        "title": {
          "en": "A title",
          "ja": "タイトル",
          "zh": "一个标题"
        },
        "size": {
          "width": 200,
          "height": 400,
          "depth": 12.4,
          "unit": "pixel",
          "flexibleDescription": {
            "en": "flexibleDescription comes here",
            "ja": "自由だーーー"
          }
        },
        "medium": {
          "en": "Oil on canvas",
          "ja": "キャンバスに油彩",
          "zh": "布面油画"
        },
        "edition": {
          "uniqueness": "unique work",
          "proofType": "ED",
          "number": 1,
          "totalNumber": 3,
          "note": {
            "en": "some extra notes in 1 or more languages"
          }
        },
        "contractTerms": {
          "royaltyRate": 15.7,
          "fileURL": "https://static-files-stg.startrail.startbahn.jp/srr-images/contract-terms.pdf"
        },
        "note": {
          "en": "note",
          "zh": "注意"
        },
        "thumbnailURL": "https://static-files-stg.startrail.startbahn.jp/srr-images/thumbnail-example.jpg",
        "yearOfCreation": {
          "en": "around 2010-2020",
          "ja": "2010年から2020年頃"
        },
        "isDigital": true,
        "name": "some nft name",
        "description": "some nft description",
        "image": "https://static-files-stg.startrail.startbahn.jp/srr-images/thumbnail-example.jpg",
        "external_url": "https://openseacreatures.io/3"
      },
      "artistAddress": "0x36E9f4C26357FDb14AdF939a12AdBba92a209C01",
      "isPrimaryIssuer": true,
      "lockExternalTransfer": false,
      "to": "0x36E9f4C26357FDb14AdF939a12AdBba92a209C01",
      "collectionAddress": "0xfbF4C1A1eb4258aE0F74807f6c1e854918DC8ed3",
      "attachmentFiles": [
        {
          "name": "image-example.jpg",
          "url": "https://static-files-stg.startrail.startbahn.jp/srr-images/image-example.jpg",
          "category": "artwork"
        },
        {
          "name": "certificate-example.jpg",
          "url": "https://static-files-stg.startrail.startbahn.jp/srr-images/certificate-example.jpg",
          "category": "certificate"
        },
        {
          "name": "for_authenticity.jpg",
          "url": "https://static-files-stg.startrail.startbahn.jp/srr-images/for_authenticity.jpg",
          "category": "for_authenticity"
        },
        {
          "name": "installation.jpg",
          "url": "https://static-files-stg.startrail.startbahn.jp/srr-images/installation.jpg",
          "category": "installation"
        }
      ]
    }
  ]
}
```

## Code Example

Check [parent page](/issue-transfer-api/issue-and-transfer-srr-nft.md).

{% hint style="success" %}
**NFC / IC tags** — if you have a TAG for a physical artwork, add both `chipUIDs` **and** `startbahnCertICTagUIDs` at the same time, containing the **same** values. The value is an array of the Chip UIDs. For example:

```javascript
"chipUIDs": [
    "1234567890abcdef"
],
"startbahnCertICTagUIDs": [
    "1234567890abcdef"
],
```

In schema **v2.2** the two fields are **mutually required** — supplying only one fails metadata validation. `chipUIDs` is the current field; `startbahnCertICTagUIDs` is the deprecated alias kept in sync. Each UID consumes one unit of the LUW's NFC-tag allowance — exceeding it returns [`LUW missing NFC tag!`](/issue-transfer-api/issue-and-transfer-srr-nft/errors.md#nfc-tags-and-quota-errors). For a copy-paste example see [NFC tags recipe](/issue-transfer-api/issue-and-transfer-srr-nft/payloads.md#recipes).
{% endhint %}


---

# 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/issue-and-transfer.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.
