# Promo — API reference

Source: https://tenergy.me/docs/api/promo
Last updated: 2026-10-09

The tenergy.app promo token: an address we dropped the token on claims one free energy grant, by signing a
message (no transaction, so its free bandwidth stays for the transfer) or by forwarding the token. Mainnet only.

| Method | Path | Summary |
|---|---|---|
| `GET` | `/v1/promo` | The promo token's current terms |
| `POST` | `/v1/promo/redeem/challenge` | The message to sign to claim the promo grant |
| `POST` | `/v1/promo/redeem` | Claim the promo grant with the signed challenge |
| `POST` | `/v1/promo/dev/challenge` | The message to sign to claim a tenergy.dev promo code |
| `POST` | `/v1/promo/dev/claim` | Claim the wallet's personal tenergy.dev promo code |
| `GET` | `/v1/promo/redemptions/{address}` | An address's claim and its grant |

Base URL `https://api.tenergy.me/v1`, or `https://api-nile.tenergy.me/v1` on Nile ([Environments](https://tenergy.me/docs/environments)). Every request below is signed as in [Authentication](https://tenergy.me/docs/authentication) unless its **Auth** line says otherwise.

## The promo token's current terms

`GET /v1/promo` · `getPromo`

**Auth:** Public — no credentials.

Whether the promo is open now, today's cap and what is left of it, the grant, the token and the address a
holder may forward it to. `state: off` on a network with no promo (Nile); every other field is then null.
Cached for 15 s.

### Responses

| Status | Meaning |
|---|---|
| `200` | The terms. |
| `429` | Too many requests. |
| `500` | Something broke on our side. |

#### Response fields (`Promo`)

| Field | Type | Required | Description |
|---|---|---|---|
| `network` | enum: `mainnet`, `nile` | yes |  |
| `state` | enum: `active`, `cap_reached_today`, `not_started`, `ended`, `off` | yes | `off`: no promo on this network; every field below is then null. |
| `starts_at` | string (date-time) \| null | yes |  |
| `ends_at` | string (date-time) \| null | yes |  |
| `cap_per_day` | integer \| null | yes | Grants per UTC day. |
| `left_today` | integer \| null | yes |  |
| `resets_at` | string (date-time) \| null | yes | The next UTC midnight. |
| `grant` | object \| null | yes |  |
| `grant.resource` | enum: `energy` | yes |  |
| `grant.amount` | integer | yes |  |
| `grant.duration_seconds` | integer | yes |  |
| `token` | object \| null | yes |  |
| `token.id` | string | yes | The TRC10 token id. |
| `token.name` | string | yes |  |
| `forward_address` | string \| null | yes | Forward any amount of the token here instead of signing (the page asks for exactly 1). |

## The message to sign to claim the promo grant

`POST /v1/promo/redeem/challenge` · `createPromoRedeemChallenge`

**Auth:** Public — no credentials.

The address challenge (see `POST /accounts/challenge`) under purpose `promo_redeem`. The claim is checked
BEFORE anything is signed — `3038 promo_not_eligible`, `3039 promo_already_redeemed`,
`3040 promo_redeem_unavailable` — so a wallet is never asked to sign for nothing.

### Request body

JSON, required.

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |

### Responses

| Status | Meaning |
|---|---|
| `201` | Challenge issued. |
| `400` | Malformed request — bad JSON, unknown field, wrong type. |
| `403` | Authenticated, but this key is not allowed to do it. |
| `409` | The request contradicts the current state of the object. |
| `429` | Too many requests. |
| `500` | Something broke on our side. |

#### Response fields (`AccountChallenge`)

| Field | Type | Required | Description |
|---|---|---|---|
| `nonce` | string | yes | Single-use |
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |
| `purpose` | enum: `signup`, `login`, `recovery`, `link`, `promo_redeem`, `promo_dev_code` |  |  |
| `message` | string | yes | The exact string to sign, TIP-191 personal-sign style. Domain-bound and human-readable, so that a person signing it in a wallet can see what it says. Sign these bytes verbatim — do not re-wrap, trim or re-encode them. |
| `issued_at` | string (date-time) | yes |  |
| `expires_at` | string (date-time) | yes | Ten minutes after issue. An expired nonce gives `1010 challenge_invalid`. |

## Claim the promo grant with the signed challenge

`POST /v1/promo/redeem` · `redeemPromo`

**Auth:** Public — no credentials.

One grant per address, whichever way it claims (signature or forwarding the token). A refused claim
records nothing. The grant is delegated within about a minute: poll `GET /promo/redemptions/{address}`.

### Request body

JSON (`AccountChallengeVerifyRequest`), required.

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |
| `nonce` | string | yes |  |
| `signature` | string | yes | Hex signature over `message`. Must recover `address`; `0x` prefix optional. |

### Responses

| Status | Meaning |
|---|---|
| `201` | Claimed; the grant is on its way. |
| `400` | Malformed request — bad JSON, unknown field, wrong type. |
| `401` | Missing, malformed or rejected credentials. |
| `403` | Authenticated, but this key is not allowed to do it. |
| `409` | The request contradicts the current state of the object. |
| `429` | Too many requests. |
| `500` | Something broke on our side. |

#### Response fields (`PromoRedeemResult`)

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |
| `method` | enum: `signature`, `forward` | yes |  |
| `redeemed_at` | string (date-time) | yes |  |
| `status` | enum: `granting`, `delegated`, `failed` | yes | `failed`: no energy could be delegated; an operator sees it. A grant is never made twice. |
| `energy` | integer | yes |  |
| `expires_at` | string (date-time) \| null | yes | When the delegation ends |
| `has_account` | boolean | yes | The wallet already opens an account (its own, or one it is linked to). The page offers "go to the dashboard"; otherwise "create an account with this wallet" or "sign in another way". |
| `wallet_ticket` | object | yes | The signature just checked, usable ONCE within ten minutes: `POST /session/wallet-ticket` signs in with this wallet; after signing in another way, `POST /session/identities/wallet/ticket` links it. Keep it in memory only; never in a URL or storage. |
| `wallet_ticket.token` | string | yes |  |
| `wallet_ticket.expires_at` | string (date-time) | yes |  |

## The message to sign to claim a tenergy.dev promo code

`POST /v1/promo/dev/challenge` · `createPromoDevChallenge`

**Auth:** Public — no credentials.

For a wallet we dropped the tenergy.dev token on, on Nile. Checked before anything is signed:
`3038 promo_not_eligible` when Nile has no such drop; `3040 promo_redeem_unavailable` (state `off`) where
this promo is not set up; `5003 chain_unavailable` when the Nile drop log cannot be read. A wallet that
already has its code may sign again to see it. Purpose `promo_dev_code`.

### Request body

JSON, required.

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |

### Responses

| Status | Meaning |
|---|---|
| `201` | Challenge issued. |
| `400` | Malformed request — bad JSON, unknown field, wrong type. |
| `403` | Authenticated, but this key is not allowed to do it. |
| `409` | The request contradicts the current state of the object. |
| `429` | Too many requests. |
| `500` | Something broke on our side. |

#### Response fields (`AccountChallenge`)

| Field | Type | Required | Description |
|---|---|---|---|
| `nonce` | string | yes | Single-use |
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |
| `purpose` | enum: `signup`, `login`, `recovery`, `link`, `promo_redeem`, `promo_dev_code` |  |  |
| `message` | string | yes | The exact string to sign, TIP-191 personal-sign style. Domain-bound and human-readable, so that a person signing it in a wallet can see what it says. Sign these bytes verbatim — do not re-wrap, trim or re-encode them. |
| `issued_at` | string (date-time) | yes |  |
| `expires_at` | string (date-time) | yes | Ten minutes after issue. An expired nonce gives `1010 challenge_invalid`. |

## Claim the wallet's personal tenergy.dev promo code

`POST /v1/promo/dev/claim` · `claimPromoDevCode`

**Auth:** Public — no credentials.

One personal code per wallet: a 3 % routing fee for 30 days from when an account enters it
(`POST /account/promo`), to be entered by `activate_by`. The lower of the code and the account's volume tier
applies. 201 when issued now, 200 with the same code on any later claim. Never energy: a fee acts only on paid
orders.

### Request body

JSON (`AccountChallengeVerifyRequest`), required.

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |
| `nonce` | string | yes |  |
| `signature` | string | yes | Hex signature over `message`. Must recover `address`; `0x` prefix optional. |

### Responses

| Status | Meaning |
|---|---|
| `200` | The code issued earlier. |
| `201` | Issued now. |
| `400` | Malformed request — bad JSON, unknown field, wrong type. |
| `401` | Missing, malformed or rejected credentials. |
| `403` | Authenticated, but this key is not allowed to do it. |
| `409` | The request contradicts the current state of the object. |
| `429` | Too many requests. |
| `500` | Something broke on our side. |

#### Response fields (`PromoDevCode`)

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | string | yes |  |
| `fee_pct` | number | yes |  |
| `duration_days` | integer | yes | Counted from when an account enters the code. |
| `activate_by` | string (date-time) \| null | yes | The last moment the code can be entered. |
| `used` | boolean | yes | An account has entered it already (single use). |

## An address's claim and its grant

`GET /v1/promo/redemptions/{address}` · `getPromoRedemption`

**Auth:** Public — no credentials.

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `address` | path | string | yes |  |

### Responses

| Status | Meaning |
|---|---|
| `200` | The claim. |
| `400` | Malformed request — bad JSON, unknown field, wrong type. |
| `404` | No such object, or it belongs to another account. The two are not distinguished. |
| `429` | Too many requests. |
| `500` | Something broke on our side. |

#### Response fields (`PromoRedemption`)

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |
| `method` | enum: `signature`, `forward` | yes |  |
| `redeemed_at` | string (date-time) | yes |  |
| `status` | enum: `granting`, `delegated`, `failed` | yes | `failed`: no energy could be delegated; an operator sees it. A grant is never made twice. |
| `energy` | integer | yes |  |
| `expires_at` | string (date-time) \| null | yes | When the delegation ends |
