# Session — API reference

Source: https://tenergy.me/docs/api/session
Last updated: 2026-09-27

The **dashboard** session: the credential a browser holds, and the only one it ever
holds. An API key is a machine credential and is never used from a page.

Signing in is one action for the person: the page fetches a challenge
(`POST /v1/accounts/challenge`), the wallet signs it — free, no transaction, nothing
leaves the wallet — and the signature comes to `POST /v1/session`, which sets an
httpOnly cookie. The session survives reloads and navigation, slides forward as it is
used, and is restored silently with `GET /v1/session`.

Because the credential is a cookie, every unsafe method additionally needs the
`X-CSRF-Token` header, whose value is the `csrf_token` of the session response. A
cross-site page cannot read that response, so it cannot produce the header.

| Method | Path | Summary |
|---|---|---|
| `GET` | `/v1/session` | Restore the session silently |
| `POST` | `/v1/session` | Sign in to the dashboard |
| `DELETE` | `/v1/session` | Sign out |
| `POST` | `/v1/session/refresh` | Extend the session |
| `POST` | `/v1/session/revoke-all` | Sign out everywhere |
| `GET` | `/v1/session/history` | Recent sign-ins |

Generated from [`openapi.yaml`](https://tenergy.me/openapi.yaml) at build time. 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.

## Restore the session silently

`GET /v1/session` · `getSession`

**Auth:** Dashboard session cookie + `X-CSRF-Token`.

What the dashboard calls on every load. Answers with the live session, or
`1013 session_expired` when the browser has none — which is not an error condition to
report to the person, only the signal to show the sign-in screen.

### Responses

| Status | Meaning |
|---|---|
| `200` | OK |
| `401` | `1013 session_expired` — no session, or it expired or was signed out. |
| `500` | Something broke on our side. |

#### Response fields (`Session`)

| Field | Type | Required | Description |
|---|---|---|---|
| `account_id` | string | yes | For the "Account ID (for support)" field. Nothing in a UI has to show it anywhere else — the account's title is `display_name`, or the shortened `owner_address`. |
| `account_status` | enum: `unfunded`, `active`, `suspended`, `closed` | yes |  |
| `display_name` | string \| null |  |  |
| `owner_address` | string \| null |  | The wallet that owns this account; signing in with it is how it is recovered. |
| `address` | string | yes | The address that signed in. The owner's, unless a member was invited. |
| `role` | enum: `owner`, `editor`, `viewer` \| null | yes | This signer's role on the account. It, and nothing else, decides what they may do. |
| `network` | enum: `mainnet`, `nile` |  |  |
| `csrf_token` | string | yes | Echo in `X-CSRF-Token` on every POST, PATCH and DELETE of this session. |
| `expires_at` | string (date-time) | yes |  |
| `created_at` | string (date-time) |  |  |
| `ttl_seconds` | integer |  | The full session lifetime, in seconds, as renewal resets it. |

## Sign in to the dashboard

`POST /v1/session` · `createSession`

**Auth:** Public — no credentials.

Verifies a challenge signed by a TRON address and opens a browser session, returned as
an httpOnly cookie. If the address owns no account on this brand yet, one is created
here — `status: unfunded`, the same account `POST /v1/accounts` would have made — so a
first visit and a return visit are the same single action.

Signing the challenge is **free and is not a transaction**: no funds move and no key
leaves the wallet.

The response body carries `csrf_token`. Send it as `X-CSRF-Token` on every subsequent
`POST`, `PATCH` or `DELETE` made with this session.

### Request body

JSON (`SessionRequest`), required.

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |
| `nonce` | string | yes | From `POST /v1/accounts/challenge`. Single use. |
| `signature` | string | yes | The wallet's signature over the challenge `message`. |

### Responses

| Status | Meaning |
|---|---|
| `201` | Signed in. The session cookie is set. |
| `400` | Malformed request — bad JSON, unknown field, wrong type. |
| `401` | `1010 challenge_invalid` — unknown, expired or reused nonce, or a signature that does not recover the address. |
| `429` | Too many requests. |
| `500` | Something broke on our side. |

#### Response fields (`Session`)

| Field | Type | Required | Description |
|---|---|---|---|
| `account_id` | string | yes | For the "Account ID (for support)" field. Nothing in a UI has to show it anywhere else — the account's title is `display_name`, or the shortened `owner_address`. |
| `account_status` | enum: `unfunded`, `active`, `suspended`, `closed` | yes |  |
| `display_name` | string \| null |  |  |
| `owner_address` | string \| null |  | The wallet that owns this account; signing in with it is how it is recovered. |
| `address` | string | yes | The address that signed in. The owner's, unless a member was invited. |
| `role` | enum: `owner`, `editor`, `viewer` \| null | yes | This signer's role on the account. It, and nothing else, decides what they may do. |
| `network` | enum: `mainnet`, `nile` |  |  |
| `csrf_token` | string | yes | Echo in `X-CSRF-Token` on every POST, PATCH and DELETE of this session. |
| `expires_at` | string (date-time) | yes |  |
| `created_at` | string (date-time) |  |  |
| `ttl_seconds` | integer |  | The full session lifetime, in seconds, as renewal resets it. |

## Sign out

`DELETE /v1/session` · `deleteSession`

**Auth:** Dashboard session cookie + `X-CSRF-Token`.

Revokes this session server-side and clears the cookie. Other sessions of the same
account — another browser, another device — are untouched.

### Responses

| Status | Meaning |
|---|---|
| `204` | Signed out. No body. |
| `401` | Missing, malformed or rejected credentials. |
| `403` | `1014 csrf_token_invalid` — the `X-CSRF-Token` header is missing or wrong. |
| `500` | Something broke on our side. |

## Extend the session

`POST /v1/session/refresh` · `refreshSession`

**Auth:** Dashboard session cookie + `X-CSRF-Token`.

Slides the expiry forward and reissues the cookie with a fresh lifetime. The session is
also renewed automatically as it is used, so this is for a page that has been open a
long time without making a request.

### Responses

| Status | Meaning |
|---|---|
| `200` | Extended |
| `401` | Missing, malformed or rejected credentials. |
| `403` | `1014 csrf_token_invalid`. |
| `500` | Something broke on our side. |

#### Response fields (`Session`)

| Field | Type | Required | Description |
|---|---|---|---|
| `account_id` | string | yes | For the "Account ID (for support)" field. Nothing in a UI has to show it anywhere else — the account's title is `display_name`, or the shortened `owner_address`. |
| `account_status` | enum: `unfunded`, `active`, `suspended`, `closed` | yes |  |
| `display_name` | string \| null |  |  |
| `owner_address` | string \| null |  | The wallet that owns this account; signing in with it is how it is recovered. |
| `address` | string | yes | The address that signed in. The owner's, unless a member was invited. |
| `role` | enum: `owner`, `editor`, `viewer` \| null | yes | This signer's role on the account. It, and nothing else, decides what they may do. |
| `network` | enum: `mainnet`, `nile` |  |  |
| `csrf_token` | string | yes | Echo in `X-CSRF-Token` on every POST, PATCH and DELETE of this session. |
| `expires_at` | string (date-time) | yes |  |
| `created_at` | string (date-time) |  |  |
| `ttl_seconds` | integer |  | The full session lifetime, in seconds, as renewal resets it. |

## Sign out everywhere

`POST /v1/session/revoke-all` · `revokeAllSessions`

**Auth:** Dashboard session cookie + `X-CSRF-Token`.

Revokes every session **of the signed-in wallet** on this account — every browser and
device, this one included — and clears the cookie. Sessions of other members are not
touched. Writes an audit row (`session.revoke_all`). **Dashboard session only**, member
role `viewer` or above; every API key is refused.

### Responses

| Status | Meaning |
|---|---|
| `204` | Signed out everywhere. No body. |
| `401` | Missing, malformed or rejected credentials. |
| `403` | `1014 csrf_token_invalid`. |
| `500` | Something broke on our side. |

## Recent sign-ins

`GET /v1/session/history` · `getSessionHistory`

**Auth:** Dashboard session cookie + `X-CSRF-Token`.

The last ten dashboard sign-ins **of the signed-in wallet** on this account, newest
first: when, from which IP, with which wallet. A sign-in is recorded when
`POST /v1/session` issues a session; the history starts on 2026-09-25.

Sign-ins of other members are not listed: showing one member's IP addresses to another
is an access decision that has not been taken. **Dashboard session only**, member role
`viewer` or above.

### Responses

| Status | Meaning |
|---|---|
| `200` | OK |
| `401` | Missing, malformed or rejected credentials. |
| `500` | Something broke on our side. |

#### Response fields

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | array of object (SignIn) | yes |  |
| `data[].signed_in_at` | string (date-time) | yes |  |
| `data[].ip` | string \| null | yes |  |
| `data[].address` | string | yes | Base58Check TRON address (starts with `T`, 34 characters). |

Example `200` response, from the contract (illustrative values — live numbers come from the API):

```json
{
  "data": [
    {
      "signed_in_at": "2026-09-25T09:41:12.000Z",
      "ip": "203.0.113.10",
      "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"
    }
  ]
}
```
