# Quickstart

Source: https://tenergy.me/docs/quickstart
Last updated: 2026-09-28

Five requests take you from a price to a delegated order: estimate, sign, quote, order, confirm. The first one needs no key, so you can run it before you have an account.

## Before you begin

| | Estimate | Quote | Order |
|---|---|---|---|
| Call | `GET /v1/estimate` | `POST /v1/quotes` | `POST /v1/orders` |
| API key | Not needed | Needed | Needed |
| Binding | No — the period may roll over | Yes, for 120 s (`expires_at`) | Charges your balance |
| Creates an object | Nothing | A quote (`qt_…`) | An order (`ord_…`) |

What each step needs:

| Steps | You need |
|---|---|
| 1 | A terminal with `curl`, Node.js 18+ or Python 3. Nothing else. |
| 2–5 | An account and an API key with the scopes those calls need. Keys are available right after signup; only the order in step 4 needs a balance, so create the key before the deposit. The [agent quickstart](https://tenergy.me/docs/agent-quickstart) walks through creating both; the key's permissions are the ones you choose — there is no default set. |

> [!NOTE]
> Rehearse on Nile first: `https://api-nile.tenergy.me/v1`, with its own account, keys (`ak_test_…`) and ledger, funded from the [public Nile faucet](https://nileex.io/join/getJoinPage). [Try it on Nile](https://tenergy.me/docs/environments#try-it-on-nile) is the five-step run; [Environments](https://tenergy.me/docs/environments) lists what Nile cannot prove.

## Five lines to your first order

### Step 1 — Price the order

`GET /v1/estimate` is public: what 65,000 energy for one hour costs right now, with activation added when the receiver has never been used on chain.

```bash title="cURL"
curl -sG https://api-nile.tenergy.me/v1/estimate \
  -d resource=energy -d amount=65000 -d tier=1h \
  -d receiver=TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE
```

```ts title="TypeScript"
const url = new URL("https://api-nile.tenergy.me/v1/estimate");
url.search = new URLSearchParams({
  resource: "energy",
  amount: "65000",
  tier: "1h",
  receiver: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
}).toString();

const estimate = await (await fetch(url)).json();
console.log(estimate.total_amount_sun, "SUN");
```

```python title="Python"
import json, urllib.parse, urllib.request

query = urllib.parse.urlencode({
    "resource": "energy",
    "amount": 65000,
    "tier": "1h",
    "receiver": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
})
with urllib.request.urlopen(f"https://api-nile.tenergy.me/v1/estimate?{query}") as res:
    estimate = json.load(res)
print(estimate["total_amount_sun"], "SUN")
```

| Field | Meaning |
|---|---|
| `price_sun_per_unit` | SUN per unit of energy for the whole tier, in the current day-part window. |
| `energy_amount_sun` | `amount × price_sun_per_unit`. |
| `activate_amount_sun` | Activation fee when `receiver` is not activated on chain; `0` without a `receiver`. |
| `total_amount_sun` | What an order would be charged now. 1 TRX = 1,000,000 SUN. |
| `receiver_activated` | `true` / `false`, or `null` when no `receiver` was sent. |
| `as_of` | When the estimate was computed. It is not binding. |

The live price table behind it is `GET /v1/prices`, also public. The API sells `5m` and `1h`; `1d` is switched off and answers `2003 tier_unavailable`. Check `available` on each row rather than hardcoding tiers.

### Step 2 — Sign requests with your key

Every other call carries three headers: `X-API-KEY`, `X-API-TIMESTAMP` and `X-API-SIGN = base64(HMAC-SHA256(secret, timestamp + METHOD + path + query + body))`. The helper below is the whole scheme; [Authentication](https://tenergy.me/docs/authentication) explains each part.

```bash title="cURL"
export TENERGY_KEY=ak_test_…      # the key id from the dashboard
export TENERGY_SECRET=sk_test_…   # shown once, when the key was created
BASE=https://api-nile.tenergy.me/v1

# tenergy METHOD PATH [BODY] — PATH is relative to /v1 and may carry a query string.
tenergy() {
  local method=$1 path=$2 body=${3:-}
  local ts sign
  ts=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
  sign=$(printf '%s' "$ts$method/v1$path$body" \
    | openssl dgst -sha256 -hmac "$TENERGY_SECRET" -binary | base64)
  curl -sS -X "$method" "$BASE$path" \
    -H "X-API-KEY: $TENERGY_KEY" -H "X-API-TIMESTAMP: $ts" -H "X-API-SIGN: $sign" \
    ${body:+-H "Content-Type: application/json" --data-raw "$body"}
}
```

```ts title="TypeScript"
import { createHmac } from "node:crypto";

const BASE = "https://api-nile.tenergy.me/v1";
const KEY = process.env.TENERGY_KEY!; // ak_test_… on Nile
const SECRET = process.env.TENERGY_SECRET!; // shown once, when the key was created

export async function tenergy(method: string, pathAndQuery: string, body?: unknown) {
  const raw = body === undefined ? "" : JSON.stringify(body); // sign the bytes you send
  const url = new URL(BASE + pathAndQuery);
  const ts = new Date().toISOString();
  const sign = createHmac("sha256", SECRET)
    .update(ts + method + url.pathname + url.search + raw)
    .digest("base64");
  const res = await fetch(url, {
    method,
    headers: {
      "X-API-KEY": KEY,
      "X-API-TIMESTAMP": ts,
      "X-API-SIGN": sign,
      ...(raw ? { "Content-Type": "application/json" } : {}),
    },
    body: raw || undefined,
  });
  return res.json();
}
```

```python title="Python"
import base64, hashlib, hmac, json, os, urllib.error, urllib.request
from datetime import datetime, timezone
from urllib.parse import urlsplit

BASE = "https://api-nile.tenergy.me/v1"
KEY, SECRET = os.environ["TENERGY_KEY"], os.environ["TENERGY_SECRET"]

def tenergy(method, path_and_query, body=None):
    raw = "" if body is None else json.dumps(body, separators=(",", ":"))  # sign the bytes you send
    url = BASE + path_and_query
    parts = urlsplit(url)
    ts = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    signed = ts + method + parts.path + (f"?{parts.query}" if parts.query else "") + raw
    sign = base64.b64encode(hmac.new(SECRET.encode(), signed.encode(), hashlib.sha256).digest()).decode()
    headers = {"X-API-KEY": KEY, "X-API-TIMESTAMP": ts, "X-API-SIGN": sign}
    if raw:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=raw.encode() or None, method=method, headers=headers)
    try:
        with urllib.request.urlopen(req) as res:
            return json.load(res)
    except urllib.error.HTTPError as err:
        return json.load(err)  # the error envelope: branch on error.slug
```

Check it with `GET /v1/balance`. A wrong secret answers `401` with `1002 invalid_signature`; an unknown or revoked key answers `1006 key_revoked`.

### Step 3 — Pin the price with a quote

Optional. A quote holds `total_amount_sun` for 120 seconds; skip it and pass `max_price_sun` on the order if a ceiling is all you need.

```bash title="cURL"
tenergy POST /quotes '{"resource":"energy","amount":65000,"tier":"1h","receiver":"TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"}'
```

```ts title="TypeScript"
const quote = await tenergy("POST", "/quotes", {
  resource: "energy",
  amount: 65000,
  tier: "1h",
  receiver: "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
});
```

```python title="Python"
quote = tenergy("POST", "/quotes", {
    "resource": "energy",
    "amount": 65000,
    "tier": "1h",
    "receiver": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
})
```

Read `id`, `total_amount_sun` and `expires_at`. An expired quote is refused with `3005 quote_expired` — take a fresh one.

### Step 4 — Place the order

Always send your own `client_order_id`. Repeating the identical request returns the original order with `200` and charges nothing, so a timeout is never a reason to buy twice.

```bash title="cURL"
tenergy POST /orders '{"quote_id":"qt_01J9Z5NB2K4R","client_order_id":"payout-8821"}'   # id from step 3
```

```ts title="TypeScript"
const order = await tenergy("POST", "/orders", {
  quote_id: quote.id,
  client_order_id: "payout-8821",
});
```

```python title="Python"
order = tenergy("POST", "/orders", {"quote_id": quote["id"], "client_order_id": "payout-8821"})
```

`201` means accepted and paid for, not delivered. `status` is usually `active` already; `allocating` means delivery is still running.

### Step 5 — Wait for confirmed

Poll the order by your own id, or register a webhook and receive `order.confirmed`.

```bash title="cURL"
tenergy GET /orders/cid:payout-8821
```

```ts title="TypeScript"
const current = await tenergy("GET", "/orders/cid:payout-8821");
if (current.partial) console.log("delivered", current.delivered_amount, "of", current.amount);
```

```python title="Python"
current = tenergy("GET", "/orders/cid:payout-8821")
if current.get("partial"):
    print("delivered", current["delivered_amount"], "of", current["amount"])
```

| `status` | What it means for you |
|---|---|
| `allocating`, `delegated` | Still on its way — poll again in a second. |
| `confirmed`, `active` | The energy is on the receiver. Treat both alike. |
| `failed` | Not delivered; any charge is reversed. |
| `expired`, `reclaimed` | The rental window is over. |

> [!WARNING]
> Check `partial`. A partly delivered order is still `confirmed`/`active`, with `delivered_amount` below `amount` and the difference already refunded — a field, not a separate state.

## Next steps

- [Authentication](https://tenergy.me/docs/authentication) — the canonical string, clock skew and key scopes.
- [Webhooks](https://tenergy.me/docs/webhooks) — `order.confirmed` instead of polling, and how to verify it.
- [Errors & rate limits](https://tenergy.me/docs/errors) — every code this flow can answer and whether to retry.
- [Orders reference](https://tenergy.me/docs/api/orders) — every field of `POST /v1/orders`.
