# SDK usage: Python and TypeScript

> Python and TypeScript clients for your bot: shadow rulings, executions, bounds, halt, receipts; usage, errors and retries. Apache-2.0.

Canonical page: https://nevarn.com/docs/sdk
Updated: 2026-11-02

Two thin clients over the strategy contract, one for Python and one for TypeScript, with the same calls for a bot: proposeIntent, executeIntent, getExecution, getMandate, getReceipt and halt. They add no authority - a key can do exactly what its scopes and limits allow - and they refuse a session token, an insecure URL or an intent that carries a secret before anything is sent.

> The packages are not yet published to PyPI or npm, and their source repository is not public yet. Until they are, everything they do is available over the HTTP API in the strategy contract; the SDKs add local checks and typed errors on top.

> The SDKs and the MCP server are free to download and use under the Apache License 2.0. Using the Nevarn platform they call is licensed on the platform, by governed actions or by subscription.

## Set up a client

Install the package for your language (see Install and downloads), put your API key in NEVARN_API_KEY, and give the client the platform's address: https://api.nevarn.com. The address is required and has no default, so a test can never reach production by leaving it out.

```python
import nevarn

# The key comes from NEVARN_API_KEY (or apiKey=...). Use it as a context
# manager, or call client.close() when your bot stops.
client = nevarn.Client(baseUrl="https://api.nevarn.com")
mandate = client.getMandate()
print(mandate.tradingEnabled, mandate.executions)
```

```ts
import { Client } from "@nevarn/sdk";

// The key comes from NEVARN_API_KEY (or { apiKey }). Uses the global fetch.
const client = new Client({ baseUrl: "https://api.nevarn.com" });
const mandate = await client.getMandate();
console.log(mandate.limits, mandate.executions);
```

## Propose an intent: a shadow ruling

Needs a key with intents. Nothing is sent to a venue; the ruling says whether the intent would have executed and, if not, why. intentKey is your idempotency key: send the same key with the same terms to retry, never with other terms.

```python
from datetime import datetime, timezone

ruling = client.proposeIntent(
    strategy="polymarket",
    intentKey="momentum-1",
    intent={
        "token_id": "<outcome token id>",
        "side": "BUY",
        "maker_amount": 5_000_000,    # 5 pUSD, in 6-decimal units
        "taker_amount": 10_000_000,   # for 10 shares
        "observed_at": datetime.now(timezone.utc),
    },
)
print(ruling.verdict, ruling.wouldExecute, ruling.reasons)  # executed is always False
```

```ts
const ruling = await client.proposeIntent({
  strategy: "polymarket",
  intentKey: "momentum-1",
  intent: {
    token_id: "<outcome token id>",
    side: "BUY",
    maker_amount: 5_000_000,
    taker_amount: 10_000_000,
    observed_at: new Date()
  }
});
console.log(ruling.verdict, ruling.wouldExecute, ruling.reasons);
```

## Execute an intent

Needs a key with execute, and takes the same terms. Where it goes is your account's environment, never the bot's choice: in Sandbox the result is a paper fill (state paper_filled) with the shadow rulings; in Production it is a queued order, which you read back until it settles.

```python
import time

terms = {...}  # an intent, shaped as in proposeIntent above
execution = client.executeIntent(strategy="polymarket", intentKey="momentum-2", intent=terms)
print(execution.environment, execution.state)   # "sandbox", "paper_filled" - or "production", "queued"

# Production: queued, prepared, sending, submitted, then confirmed - or rejected, blocked or expired.
while execution.state in ("queued", "prepared", "sending", "submitted", "unknown"):
    time.sleep(2)
    execution = client.getExecution(execution.id)
print(execution.state, execution.reasons, execution.receiptId)
```

```ts
let execution = await client.executeIntent({ strategy: "polymarket", intentKey: "momentum-2", intent: terms });
console.log(execution.environment, execution.state);

const pending = new Set(["queued", "prepared", "sending", "submitted", "unknown"]);
while (pending.has(execution.state)) {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  execution = await client.getExecution(execution.id);
}
console.log(execution.state, execution.receiptId);
```

Once an execution is confirmed, its receiptId is set, and getReceipt (a read key) returns the receipt: the Decionis ruling, the order, the fill transaction, settlement, the ledger entries and the balance after.

## Halt

Needs a key with halt. It turns all of your account's signing off at once and stops every execution your bots asked for that has not been sent. Calling it twice is harmless. There is no resume method: only an owner can turn signing back on, from the console.

```python
halted = client.halt(reason="drawdown limit reached")
print(halted.halted, halted.executionsStopped)
```

```ts
const halted = await client.halt({ reason: "drawdown limit reached" });
console.log(halted.halted, halted.executionsStopped);
```

## Errors and retries

Every error carries only its HTTP status, the platform's error code and the route - never the key, the request or what the platform wrote - so it can be logged or shown to a model safely. All of them derive from NevarnError.

```python
try:
    execution = client.executeIntent(strategy="polymarket", intentKey=key, intent=terms)
except nevarn.RateLimited as error:
    time.sleep(error.retryAfter or 1)  # then send the same intentKey and terms again
except nevarn.ExecutionRefused as error:
    print("not executed:", error.code)  # e.g. key_daily_limit_exceeded; nothing was sent
except nevarn.TransportError:
    pass  # not reached: retrying the same intentKey and terms returns the first execution
```

| Error | When | Retry? |
| --- | --- | --- |
| InvalidInput | The client refused to send: a field is malformed, or the intent carries something named like a secret or shaped like a private key | No: fix the input |
| ConfigurationError, SessionTokenRefused | No https base URL (plain http only on loopback), or a session token instead of an nvk_ key | No |
| Unauthorized, Forbidden | The key is unknown or revoked (401), or lacks the scope (403) | No |
| IntentConflict | An intent key reused for other terms, or shadow mode off for this deployment (409) | No |
| ExecutionRefused | Not executed, and code says why: a key or account limit, a halt, a replay, a key minted without a wallet asking for Production (key_minted_without_wallet), or a policy not yet released (409) | Not with other terms to get around it |
| RateLimited | 429; retryAfter says how many seconds to wait | Yes, after retryAfter |
| Rejected | 422: terms outside the strategy's shape, a stale or future observed_at | Only with fresh terms and a new intent key |
| ServerError, TransportError | 5xx, or the platform could not be reached | Yes, with the same intent key and terms |
| NotFound | No such execution or receipt for this account (404) | No |

## Methods

| Method | Route | Scope | Returns |
| --- | --- | --- | --- |
| proposeIntent | POST /machine/intents | intents | The shadow ruling. executed is always false. |
| executeIntent | POST /machine/executions | execute | The execution: a paper fill in Sandbox, a queued order in Production |
| getExecution | GET /machine/executions/{id} | execute | One execution and its current state |
| getMandate | GET /machine/mandate | read | Limits, signing grants, payables policy, intent policies, your environment, the key's limits, governed actions |
| getReceipt | GET /machine/receipts/{id} | read | One execution receipt, by the execution's id |
| halt | POST /machine/halt | halt | Signing is off. There is no resume method. |
| requestSpend | POST /machine/invoices | invoices | The supplier invoice and the payables policy's decision |

## Guarantees

- The base URL is required and must be https; plain http is accepted only on 127.0.0.1, localhost or [::1].
- The key comes only from the constructor or NEVARN_API_KEY, must be an nvk_ key, and never appears in a repr, JSON, an error or a log.
- Redirects are refused and every call times out after 5 seconds.
- There is no method for limits, resume, keys, suppliers or approvals.
