> For the complete documentation index, see [llms.txt](https://docs.fluvion.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fluvion.io/api-trading/authentication.md).

# API Authentication

Private requests use an Ed25519 API signing key authorized by your wallet. After key creation, ordinary account reads and trades do not require a fresh wallet signature. Never substitute a wallet private key for an API secret.

## Signing format

Concatenate, with no separators:

```
timestamp_in_milliseconds + UPPERCASE_METHOD + pathname_and_query + exact_body_if_present
```

Sign the UTF-8 bytes using Ed25519 and encode the signature as base64url. Include query parameters in exactly the same order/encoding as the outgoing URL. Serialize JSON once; sign and send the same string.

| Header               | Value                                                                         |
| -------------------- | ----------------------------------------------------------------------------- |
| `orderly-account-id` | Trading account ID                                                            |
| `orderly-key`        | `ed25519:` plus base58 public key                                             |
| `orderly-timestamp`  | Timestamp in milliseconds                                                     |
| `orderly-signature`  | Base64url Ed25519 signature                                                   |
| `Content-Type`       | GET/DELETE: `application/x-www-form-urlencoded`; POST/PUT: `application/json` |

These protocol field names cannot be rebranded. The secret is used locally and is never transmitted as a header. Keep your clock synchronized and generate a new timestamp/signature for each request. The current protocol reference documents a 300-second timestamp tolerance; do not rely on that window for execution timing.

## Read-only Node.js example

Requires Node.js 20+. In a separate private integration project:

```bash
npm install @noble/curves@1.9.7 @scure/base@1.2.6
```

Save as `read-account.mjs`. Supply `FLUVION_ACCOUNT_ID`, `FLUVION_API_KEY`, `FLUVION_API_SECRET` via a secure environment. Do not put literal credentials in this file. Set `FLUVION_NETWORK=mainnet` explicitly for production; otherwise it defaults to testnet.

```javascript
import { ed25519 } from '@noble/curves/ed25519';
import { base58 } from '@scure/base';

const network = process.env.FLUVION_NETWORK ?? 'testnet';
if (!['mainnet', 'testnet'].includes(network)) throw new Error('Invalid network');
const base = network === 'mainnet'
  ? 'https://api.orderly.org'
  : 'https://testnet-api.orderly.org';
const account = process.env.FLUVION_ACCOUNT_ID;
const expectedKey = process.env.FLUVION_API_KEY;
const encodedSecret = process.env.FLUVION_API_SECRET;
if (!account || !expectedKey || !encodedSecret) throw new Error('Missing credentials');
const secret = base58.decode(encodedSecret.replace(/^ed25519:/, ''));
try {
  if (secret.length !== 32) throw new Error('Expected a 32-byte API seed');
  const key = `ed25519:${base58.encode(ed25519.getPublicKey(secret))}`;
  if (key !== expectedKey) throw new Error('API key and secret do not match');
  const path = '/v1/positions';
  const timestamp = String(Date.now());
  const signature = ed25519.sign(
    new TextEncoder().encode(`${timestamp}GET${path}`), secret,
  );
  const response = await fetch(base + path, {
    method: 'GET', redirect: 'error', signal: AbortSignal.timeout(15000),
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'orderly-account-id': account,
      'orderly-key': key,
      'orderly-timestamp': timestamp,
      'orderly-signature': Buffer.from(signature).toString('base64url'),
    },
  });
  const result = await response.json();
  if (!response.ok || result.success !== true) {
    throw new Error(`API failed: HTTP ${response.status}, code ${result.code ?? 'unknown'}`);
  }
  console.log({
    success: true,
    openPositions: result.data.rows.filter(row => Number(row.position_qty) !== 0).length,
  });
} finally {
  secret.fill(0);
}
```

Zeroing the decoded byte array is best effort, not a guarantee of wiping JavaScript strings from memory. This example deliberately does not log account details or place orders.


---

# 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.fluvion.io/api-trading/authentication.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.
