---
title: API pública de Upscale
icon: book-open
apiVersion: d796ac9
---

Acceso programático al trading de Upscale con una clave de API personal.
Solo los endpoints listados aquí aceptan una clave de API — todo lo demás requiere una sesión interactiva.

<a id="quick-start"></a>

## Inicio rápido

Abrir y cerrar una posición requiere cinco llamadas. Todo lo que aparece a continuación se ejecuta contra `https://api.upscale.trade` con una única
clave de API personal en el encabezado `Authorization` — consulta [Autenticación](/es/developers#authentication) para saber cómo obtener una.

| # | Llamada | Qué te ofrece |
|---|------|-------------------|
| 1 | `GET /accounts/with-risk-status` | Las cuentas en las que la clave puede operar, cada una con su fase, estado, saldo y equity. Toma `accountId` de la cuenta en la que se va a operar. |
| 2 | `GET /v2/markets?accountId=…` | Los mercados negociables con el precio, las comisiones y los límites de apalancamiento contra los que se comprueba una orden en esa cuenta. Toma `id` del mercado. |
| 3 | `POST /orders` con `type: "market"` | Abre la posición. `amount` es la reserva de cotización a gastar, `leverage` el multiplicador. |
| 4 | `GET /positions/{accountId}/active` | Las posiciones abiertas. Se necesitan porque la respuesta de la orden no lleva ningún identificador de posición. |
| 5 | `POST /orders` con `type: "take"` y `triggerPrice: "0"` | Cierra esa posición a mercado. `amount` es el tamaño base a cerrar. |

Tres cosas que resolver antes de escribir cualquier código:

- **Cada número es una cadena sin procesar fp9** — el valor escalado por 10⁹. 100 USD es `"100000000000"`, el apalancamiento de 10x es
  `"10000000000"`. Léelos y escríbelos con `BigInt` / `Decimal`; un float redondea silenciosamente los últimos dígitos.
- **`amount` cambia de unidad con la clase de orden.** En la orden `market` que abre una posición, es un importe de **cotización**
  — margen, comisión, spread y buffer reservados del saldo libre. En la orden `take` que la cierra,
  es el tamaño del **activo base** de la posición, y no se reserva nada. El paso 5 pasa el `size` de la posición directamente.
- **Cerrar es una orden `take`, no una orden de mercado en la dirección contraria.** Las posiciones se mantienen por dirección, así que una orden de mercado `short`
  colocada contra una `long` abierta abre una segunda posición en lugar de cerrar la primera. Una `take` con
  `triggerPrice: "0"` no lleva disparador y se ejecuta a mercado mientras la llamada sigue abierta; un `amount` mayor que
  la posición mantenida la cierra por completo. Para aplanar una cuenta en una sola llamada, usa `POST /positions/{accountId}/close-all`.

Ambos scripts a continuación están completos: establece `UPSCALE_API_KEY` en el entorno y ejecuta.

<a id="javascript-node-18"></a>

### JavaScript (Node 18+)

Guarda como `upscale.mjs` y ejecuta con `node upscale.mjs` — sin dependencias.

```js
import { randomUUID } from 'node:crypto';

const BASE_URL = 'https://api.upscale.trade';
const API_KEY = process.env.UPSCALE_API_KEY;

// Human decimal -> fp9 raw string: 100 -> "100000000000", "0.5" -> "500000000".
const toFp9 = (value) => {
  const [whole, fraction = ''] = String(value).split('.');
  return BigInt(whole + fraction.padEnd(9, '0').slice(0, 9)).toString();
};

// fp9 raw string -> human decimal string. Stays on BigInt: Number loses the low digits above ~9M USD.
const fromFp9 = (raw) => {
  const negative = raw.startsWith('-');
  const digits = (negative ? raw.slice(1) : raw).padStart(10, '0');
  return `${negative ? '-' : ''}${digits.slice(0, -9)}.${digits.slice(-9)}`;
};

const call = async (method, path, body) => {
  const response = await fetch(`${BASE_URL}${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      // One key per logical request; a retry of that request must send the same key again.
      ...(body ? { 'Content-Type': 'application/json', 'x-idempotency-key': randomUUID() } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const payload = response.status === 204 ? null : await response.json();
  if (!response.ok) {
    // `error` is the stable machine-readable code — branch on it, never on `message`.
    throw new Error(`${method} ${path} -> ${response.status} ${payload?.error}: ${payload?.message}`);
  }
  return payload;
};

// 1. Accounts. Authenticated with an API key the list holds only accounts that have api_trading enabled.
const accounts = await call('GET', '/accounts/with-risk-status');
const account = accounts.find((item) => item.status === 'active' && item.apiTrading && item.type === 'demo');
if (!account) {
  throw new Error('No active demo account with API trading enabled');
}
console.log(`account ${account.accountId}, equity ${fromFp9(account.riskStatus.currentEquity)}`);

// 2. Markets, priced by the shard that hosts this account — the state the order will be filled against.
const markets = await call('GET', `/v2/markets?accountId=${account.accountId}`);
const market = markets.find((item) => item.config.baseAsset === 'BTC');
if (!market) {
  throw new Error('BTC is not available on this account');
}
console.log(`${market.config.ticker} at ${fromFp9(market.state.indexPrice)}`);

// 3. Open a long: 100 USD of reserve at 10x, filled at market.
const order = await call('POST', '/orders', {
  accountId: account.accountId,
  marketId: market.id,
  type: 'market',
  direction: 'long',
  amount: toFp9(100),
  leverage: toFp9(10),
  expectedAmount: '0', // no slippage check; pass the base size you expect to enforce one
});
console.log(`order ${order.id} ${order.status}`);

// 4. Read the position back — the order response does not carry its identifier.
const positions = await call('GET', `/positions/${account.accountId}/active`);
const position = positions.find((item) => item.market === market.id && item.direction === 'long');
if (!position) {
  throw new Error('Position not found — the order may have been deferred or rejected at execution');
}
console.log(`position ${position.idx}, size ${fromFp9(position.size)} ${market.config.baseAsset}`);

// 5. Close it at market.
await call('POST', '/orders', {
  accountId: account.accountId,
  marketId: market.id,
  type: 'take',
  direction: position.direction, // a close order runs in the same direction as its position
  positionId: position.idx,
  amount: position.size, // base units
  triggerPrice: '0', // no trigger: fill now
});
console.log('closed');
```

<a id="python-3-10"></a>

### Python (3.10+)

Requiere `requests` (`pip install requests`).

```python
import os
import uuid
from decimal import Decimal

import requests

BASE_URL = 'https://api.upscale.trade'
API_KEY = os.environ['UPSCALE_API_KEY']
FP9 = Decimal(10) ** 9


def to_fp9(value) -> str:
    """Human decimal -> fp9 raw string: 100 -> "100000000000"."""
    return str(int(Decimal(str(value)) * FP9))


def from_fp9(raw: str) -> Decimal:
    """fp9 raw string -> Decimal. Never float — it drops the low digits."""
    return Decimal(raw) / FP9


def call(method: str, path: str, body: dict | None = None):
    headers = {'Authorization': f'Bearer {API_KEY}'}
    if body is not None:
        # One key per logical request; a retry of that request must send the same key again.
        headers['x-idempotency-key'] = str(uuid.uuid4())
    response = requests.request(method, f'{BASE_URL}{path}', json=body, headers=headers, timeout=30)
    payload = None if response.status_code == 204 else response.json()
    if not response.ok:
        # `error` is the stable machine-readable code — branch on it, never on `message`.
        raise RuntimeError(
            f'{method} {path} -> {response.status_code} {payload.get("error")}: {payload.get("message")}'
        )
    return payload


# 1. Accounts. Authenticated with an API key the list holds only accounts that have api_trading enabled.
accounts = call('GET', '/accounts/with-risk-status')
account = next((item for item in accounts if item['status'] == 'active' and item['apiTrading'] and item['type'] == 'demo'), None)
if account is None:
    raise SystemExit('No active demo account with API trading enabled')
print(f"account {account['accountId']}, equity {from_fp9(account['riskStatus']['currentEquity'])}")

# 2. Markets, priced by the shard that hosts this account — the state the order will be filled against.
markets = call('GET', f"/v2/markets?accountId={account['accountId']}")
market = next((item for item in markets if item['config']['baseAsset'] == 'BTC'), None)
if market is None:
    raise SystemExit('BTC is not available on this account')
print(f"{market['config']['ticker']} at {from_fp9(market['state']['indexPrice'])}")

# 3. Open a long: 100 USD of reserve at 10x, filled at market.
order = call('POST', '/orders', {
    'accountId': account['accountId'],
    'marketId': market['id'],
    'type': 'market',
    'direction': 'long',
    'amount': to_fp9(100),
    'leverage': to_fp9(10),
    'expectedAmount': '0',  # no slippage check; pass the base size you expect to enforce one
})
print(f"order {order['id']} {order['status']}")

# 4. Read the position back — the order response does not carry its identifier.
positions = call('GET', f"/positions/{account['accountId']}/active")
position = next((item for item in positions if item['market'] == market['id'] and item['direction'] == 'long'), None)
if position is None:
    raise SystemExit('Position not found — the order may have been deferred or rejected at execution')
print(f"position {position['idx']}, size {from_fp9(position['size'])} {market['config']['baseAsset']}")

# 5. Close it at market.
call('POST', '/orders', {
    'accountId': account['accountId'],
    'marketId': market['id'],
    'type': 'take',
    'direction': position['direction'],  # a close order runs in the same direction as its position
    'positionId': position['idx'],
    'amount': position['size'],  # base units
    'triggerPrice': '0',  # no trigger: fill now
})
print('closed')
```

<a id="where-to-go-next"></a>

### A dónde ir a continuación

- Las entradas diferidas (`limit`, `stop_market`, `stop_limit`) y las órdenes protectoras (`stop`, `trailing_stop`) toman la misma
  `POST /orders` ruta — la operación enumera lo que requiere cada tipo.
- Un stop-loss y un take-profit pueden acompañar a la orden de apertura como `stopTriggerPrice` y `takeTriggerPrice`.
- `GET /accounts/{accountId}/risk-status` es el endpoint que se debe consultar periódicamente para obtener el margen disponible de drawdown mientras una posición está abierta.


<a id="authentication"></a>

## Autenticación

Crea una clave en la aplicación Upscale (o con `POST /user/api-keys` desde una sesión autenticada) — la clave sin procesar
se muestra una sola vez, en el momento de la creación. Pásala como token de portador:

```
Authorization: Bearer usk_<your key>
```

Una clave hereda los permisos del usuario al que pertenece y permanece válida hasta que se elimina, se renueva o caduca.

El trading a través de la API se habilita por cuenta: una cuenta con `api_trading` desactivado responde `403`
(`api_trading_not_enabled`) a cada solicitud realizada con una clave, y las listas de cuentas lo omiten por completo.

<a id="conventions"></a>

## Convenciones

- Los importes, precios, apalancamiento y multiplicadores viajan como **fp9 cadenas de enteros sin procesar** — el valor escalado por 10⁹.
  `"1000000000"` es 1 USD, `"10000000000"` es 10x apalancamiento, `"50000000"` es 5%.
- fp9 fija la escala, no la unidad. Un importe de cotización es USD; un tamaño base son unidades del activo negociado. `amount` en una orden es
  cotización en órdenes de aumento y base en órdenes de cierre — cada campo dice cuál de los dos contiene.
- Los identificadores son uuid v4. Los mercados se direccionan por identificador al operar y por el ticker del activo base
  (`BTC`) en los endpoints de lectura por mercado.
- Las listas se paginan mediante `limit` y `offset` y devuelven el recuento total junto a la página.
- Las posiciones y las órdenes están acotadas a la fase en la que se encuentra la cuenta en este momento: lo que pertenece a una fase finalizada
  ya no se devuelve.

<a id="rate-limits"></a>

## Límites de velocidad

Los límites se cuentan por clave, en ventanas fijas, por separado para los endpoints de lectura (GET) y de escritura (todo lo demás).
Los valores predeterminados son 10 solicitudes por segundo y 60 por minuto
para lecturas, 10 por segundo y 60 por minuto para escrituras.

Superar un límite devuelve `429` (`api_key_rate_limit_exceeded`) con un encabezado `Retry-After` y un cuerpo
que contiene el bucket (`read` / `write`), la ventana que se activó, su límite y `retryAt`.

Los límites propios de la clave son los únicos a los que está sujeta una clave. Los límites por endpoint con los que se topa la aplicación Upscale
se cuentan por sesión interactiva y no se aplican a las solicitudes con clave de API, por lo que ningún endpoint aquí tiene un
límite más estricto propio.

<a id="errors"></a>

## Errores

Un fallo responde con `{ statusCode, message, error }`, donde `error` es un código estable legible por máquina.
Bifurque según ese código, no según el texto del mensaje.

- `400` — el cuerpo, la consulta o la ruta no superaron la validación del esquema (`validation_failed`), o se rechazó una precondición de trading
  (`insufficient_balance`, `position_not_available`, y los códigos por operación a continuación).
- `401` — la clave falta, está malformada, revocada o caducada (`session_expired`).
- `403` — la clave se usó en un endpoint que no acepta claves (`api_key_not_allowed`), la cuenta pertenece
  a otra persona (`account_access_denied`), el trading por API está desactivado (`api_trading_not_enabled`), la cuenta ya no
  opera (`challenge_closed`), no ha alcanzado una fase negociable (`account_phase_not_allowed`) o está bloqueada
  por el límite de capital gestionado (`funded_limit_trading_locked`), la orden ya no está activa
  (`order_not_active`), o el mercado está pausado (`market_paused`), solo cierre (`market_close_only`) o fuera de
  la categoría en la que la cuenta puede operar (`market_category_not_allowed`).
- `404` — no existe tal cuenta (`account_not_found`), orden (`order_not_found`), posición (`position_not_found`)
  o mercado (`market_not_found`).
- `409` — una solicitud con la misma `x-idempotency-key` todavía se está procesando (`idempotency_key_in_flight`),
  la cuenta no está cargada actualmente por el motor de trading (`account_not_loaded`), o el precio de mercado no
  se ha actualizado lo suficientemente recientemente como para operar con él (`market_price_stale`).
- `429` — se superó el límite de velocidad de la clave (`api_key_rate_limit_exceeded`).

Los rechazos a nivel de orden se devuelven como `400` con un código que nombra la combinación de campos en conflicto; cada operación de trading
enumera los que puede producir.


<a id="operations"></a>

## Operaciones

- [GET /accounts/{accountId}/equity-history — Obtener el historial de capital de la cuenta del trader](/es/developers/operations/getaccountequityhistory)

- [GET /accounts/{accountId}/risk-status — Obtener el estado de riesgo de la cuenta del trader](/es/developers/operations/getaccountriskstatus)

- [GET /accounts/{accountId}/stats — Obtener estadísticas de trading de la cuenta del trader](/es/developers/operations/getaccounttradingstats)

- [POST /accounts/{accountId}/close-all — Cerrar todas las posiciones y órdenes](/es/developers/operations/closeallpositionsandorders)

- [GET /accounts/events — Obtener los eventos de la cuenta del trader](/es/developers/operations/getaccountsevents)

- [GET /accounts/risk-status — Obtener el estado de riesgo de todas las cuentas del usuario actual.](/es/developers/operations/getaccountsriskstatus)

- [GET /accounts/with-risk-status — Obtener todas las cuentas del usuario actual con estado de riesgo](/es/developers/operations/getaccountswithriskstatus)

- [GET /v2/markets — Obtener lista de mercados](/es/developers/operations/getmarkets)

- [GET /v2/markets/{id} — Obtener mercado por id](/es/developers/operations/getmarket)

- [POST /orders — Crear nueva orden](/es/developers/operations/createorder)

- [POST /orders/{accountId}/close-all — Cerrar todas las órdenes](/es/developers/operations/closeallorders)

- [GET /orders/{accountId}/active — Obtener todas las órdenes activas](/es/developers/operations/getactiveorders)

- [GET /orders/{accountId}/{asset}/active — Obtener órdenes activas por ticker](/es/developers/operations/getactiveordersbyticker)

- [GET /orders/{accountId}/{asset}/history — Obtener el historial de órdenes por ticker](/es/developers/operations/getordershistorybyticker)

- [PATCH /orders/{orderId} — Cambiar el precio de activación de la orden](/es/developers/operations/updateorder)

- [DELETE /orders/{orderId} — Cancelar orden](/es/developers/operations/cancelorder)

- [GET /positions/{accountId}/active — Obtener todas las posiciones activas](/es/developers/operations/getactivepositions)

- [GET /positions/{accountId}/portfolio/history — Obtener el historial de todas las posiciones](/es/developers/operations/getpositionshistory)

- [GET /positions/{positionId}/history — Obtener todos los eventos por posición](/es/developers/operations/getpositionevents)

- [GET /positions/{positionId} — Obtener detalles de la posición](/es/developers/operations/getposition)

- [PATCH /positions/{positionId}/margin — Cambiar el margen de la posición](/es/developers/operations/changemargin)

- [POST /positions/{accountId}/close-all — Cerrar todas las posiciones](/es/developers/operations/closeallpositions)

- [GET /positions/{accountId}/{asset}/history — Obtener el historial de posiciones por ticker](/es/developers/operations/getpositionshistorybyticker)

- [GET /positions/{accountId}/{asset}/active — Obtener las posiciones activas por ticker](/es/developers/operations/getactivepositionsbyticker)

- [GET /positions/{accountId}/{asset}/open-notional — Obtener el interés abierto actual por ticker](/es/developers/operations/getopennotionalbyticker)

- [GET /positions/{accountId}/{asset}/chart-events — Obtener eventos de posición para indicadores de gráfico por ticker](/es/developers/operations/getchartevents)

- [GET /positions/{accountId}/{asset}/chart-events/buckets — Obtener eventos de posición para indicadores del gráfico agrupados por velas](/es/developers/operations/getcharteventbuckets)

- [GET /positions/{accountId}/{asset}/scalping-coefficient — Obtener el coeficiente de scalping para el ticker](/es/developers/operations/getscalpingcoefficient)