> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scripxhq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDKs and tools

> Official ScripX API clients for Python and TypeScript: resource namespaces over the full /v1 surface, typed responses, an exception hierarchy, auto pagination, idempotent writes, and webhook verification. Plus the live OpenAPI spec for generating clients in any language.

ScripX ships official clients for Python and TypeScript. Both wrap the same `/v1` surface with resource namespaces, default to the production base URL, send your key in `X-ScripX-Key`, auto-attach an `Idempotency-Key` on writes, retry transient failures with backoff, and map the [error envelope](/errors) onto a typed exception hierarchy.

<CardGroup cols={2}>
  <Card title="Python" icon="python">
    `pip install scripx`. Standard library only, no dependencies.
  </Card>

  <Card title="TypeScript" icon="js">
    `npm install @scripx/sdk`. `fetch`-based, ESM, zero runtime dependencies. Node 18+, browsers, edge.
  </Card>
</CardGroup>

## Install

<CodeGroup>
  ```bash Python theme={"dark"}
  pip install scripx
  ```

  ```bash TypeScript theme={"dark"}
  npm install @scripx/sdk
  ```
</CodeGroup>

## Authenticate

Construct a client with your key. The base URL is the same for production and sandbox, the key prefix decides which: `scripx_live_…` trades real money, `scripx_test_…` runs the identical code against the [sandbox](/environments) (a separate ledger, unmetered, clearly marked). See [Authentication](/authentication).

<CodeGroup>
  ```python Python theme={"dark"}
  from scripx import ScripxClient

  sx = ScripxClient("scripx_live_…")                 # or a scripx_test_… key for sandbox
  # options: base_url, timeout (seconds), max_retries
  ```

  ```typescript TypeScript theme={"dark"}
  import { Scripx } from "@scripx/sdk";

  const sx = new Scripx({ apiKey: "scripx_live_…" }); // or a scripx_test_… key for sandbox
  // options: baseUrl, timeout (ms), maxRetries
  ```
</CodeGroup>

<Note>
  Money is always integer paise and prices are bps of face, in the SDKs exactly as on the wire. See [Money and price](/annexure/money-and-price).
</Note>

## Resource namespaces

Every `/v1` operation lives under a namespace on the client:

`firms`, `credits`, `quotes`, `orders`, `positions`, `group`, `cross`, `market`, `rates`, `statements`, `earnings`, `usage`, `branding`, `keys`, `webhooks`, `audit`.

Responses are typed (Python `TypedDict`s, TypeScript `interface`s) and returned as plain data, so additive fields the API adds later still flow through.

## Get a quote, then place an order

The core trade loop: take a firm quote, then place an idempotent order at your price limit.

<CodeGroup>
  ```python Python theme={"dark"}
  # SELL side: price a whole face, then place the order with a price floor
  quote = sx.quotes.sell(scheme="RODTEP", face_paise=100_000_000)
  print(quote["price_bps"], quote["all_in_paise"])

  result = sx.orders.create(
      side="sell", scheme="RODTEP", amount_paise=100_000_000, min_pct_bps=9600,
  )
  order = result["order"]
  sx.orders.get(order["id"])
  ```

  ```typescript TypeScript theme={"dark"}
  // SELL side: price a whole face, then place the order with a price floor
  const quote = await sx.quotes.sell({ scheme: "RODTEP", face_paise: 100_000_000 });
  console.log(quote.price_bps, quote.all_in_paise);

  const { order } = await sx.orders.create({
    side: "sell", scheme: "RODTEP", amount_paise: 100_000_000, min_pct_bps: 9600,
  });
  await sx.orders.get(order!.id!);
  ```
</CodeGroup>

Order placement auto-attaches a fresh `Idempotency-Key`. Pin your own to make a retry at-most-once (see [Idempotency](/idempotency)):

<CodeGroup>
  ```python Python theme={"dark"}
  key = ScripxClient.new_idempotency_key()
  sx.orders.create(side="sell", scheme="RODTEP", amount_paise=100_000_000,
                   min_pct_bps=9600, idempotency_key=key)
  ```

  ```typescript TypeScript theme={"dark"}
  const key = Scripx.newIdempotencyKey();
  await sx.orders.create({ side: "sell", scheme: "RODTEP", amount_paise: 100_000_000,
                           min_pct_bps: 9600, idempotencyKey: key });
  ```
</CodeGroup>

## List a firm's scrips and credits

The direct-quote loop: list a connected firm's inventory, then price a specific credit by its scrip number. List endpoints auto-paginate.

<CodeGroup>
  ```python Python theme={"dark"}
  for credit in sx.credits.iter("0123456789"):        # follows next_cursor for you
      if credit["sellable"]:
          q = sx.quotes.sell(scrip_no=credit["scrip_no"])
          print(credit["scrip_no"], q["price_bps"])
  ```

  ```typescript TypeScript theme={"dark"}
  for await (const credit of sx.credits.iterate("0123456789")) { // follows next_cursor for you
    if (credit.sellable) {
      const q = await sx.quotes.sell({ scrip_no: credit.scrip_no });
      console.log(credit.scrip_no, q.price_bps);
    }
  }
  ```
</CodeGroup>

Trading on behalf of a client firm? Register and verify it first, its credits and trades unlock once its state is `connected`:

<CodeGroup>
  ```python Python theme={"dark"}
  sx.firms.create(iec="0123456789", name="Acme Exports", role="exporter")
  res = sx.firms.verify("0123456789", registration_id="ICE…", password="…")
  if res.get("next") == "authorize":
      sx.firms.authorize("0123456789", code="123456")   # one-time code sent to the firm
  ```

  ```typescript TypeScript theme={"dark"}
  await sx.firms.create({ iec: "0123456789", name: "Acme Exports", role: "exporter" });
  const res = await sx.firms.verify("0123456789", { registration_id: "ICE…", password: "…" });
  if (res.next === "authorize") {
    await sx.firms.authorize("0123456789", { code: "123456" }); // one-time code sent to the firm
  }
  ```
</CodeGroup>

## Register and verify a webhook

Register a callback, store the one-time signing secret, and verify every inbound delivery in your receiver on the raw request body:

<CodeGroup>
  ```python Python theme={"dark"}
  reg = sx.webhooks.register(url="https://example.com/hooks/scripx", event="settlement.settled")
  signing_secret = reg["signing_secret"]              # shown once, store it

  # in your receiver:
  from scripx import verify_webhook
  ok = verify_webhook(signing_secret, raw_body, request.headers["X-ScripX-Signature"])
  ```

  ```typescript TypeScript theme={"dark"}
  const reg = await sx.webhooks.register({ url: "https://example.com/hooks/scripx", event: "settlement.settled" });
  const signingSecret = reg.signing_secret!;          // shown once, store it

  // in your receiver:
  import { verifyWebhook } from "@scripx/sdk";
  const ok = await verifyWebhook(signingSecret, rawBody, req.headers["x-scripx-signature"]);
  ```
</CodeGroup>

See [Webhooks](/webhooks) for the event catalog and signature format.

## Pull statements and earnings

<CodeGroup>
  ```python Python theme={"dark"}
  sx.statements.get("mtd")                            # your draft bill for the period
  sx.earnings.get()                                   # desk P&L + withdrawable payable
  export = sx.audit.export("all")                     # tamper-evident event log + chain digest
  ```

  ```typescript TypeScript theme={"dark"}
  await sx.statements.get("mtd");                     // your draft bill for the period
  await sx.earnings.get();                            // desk P&L + withdrawable payable
  const auditExport = await sx.audit.export("all");   // tamper-evident event log + chain digest
  ```
</CodeGroup>

## Errors

Every failure raises a `ScripxError` subclass carrying `.status`, `.code`, `.request_id`, and `.detail`. Quote `request_id` to support.

<CodeGroup>
  ```python Python theme={"dark"}
  from scripx import ScripxError, AuthError, RateLimitError, ValidationError

  try:
      sx.orders.create(side="sell", scheme="RODTEP", amount_paise=100_000_000, min_pct_bps=9600)
  except RateLimitError as e:
      ...   # e.retry_after seconds; the SDK already retried within max_retries
  except ValidationError as e:
      print(e.code, e.message, e.detail)
  except AuthError as e:
      print("check your key or scopes:", e.message)
  except ScripxError as e:
      print("request", e.request_id, "failed:", e.message)
  ```

  ```typescript TypeScript theme={"dark"}
  import { ScripxError, AuthError, RateLimitError, ValidationError } from "@scripx/sdk";

  try {
    await sx.orders.create({ side: "sell", scheme: "RODTEP", amount_paise: 100_000_000, min_pct_bps: 9600 });
  } catch (e) {
    if (e instanceof RateLimitError) { /* e.retryAfter seconds */ }
    else if (e instanceof ValidationError) console.log(e.code, e.message, e.detail);
    else if (e instanceof AuthError) console.log("check your key or scopes:", e.message);
    else if (e instanceof ScripxError) console.log("request", e.requestId, "failed:", e.message);
    else throw e;
  }
  ```
</CodeGroup>

Classes map to status: `AuthError` (401/403), `ValidationError` (400/422), `NotFoundError` (404), `ConflictError` (409), `RateLimitError` (429), `ServerError` (5xx), plus `APIConnectionError` / `APITimeoutError` for transport. The clients retry `429`, `5xx`, and connection failures with exponential backoff (`max_retries=2` by default); non-idempotent writes retry only when an idempotency key is present. See the full [error reference](/errors) and [rate limits](/rate-limits).

## Live OpenAPI spec

The API serves its own machine-readable contract, public, no key. Fetch it in code, or generate a client in any language and import it into Postman or Insomnia:

<CodeGroup>
  ```python Python theme={"dark"}
  spec = sx.openapi()
  ```

  ```typescript TypeScript theme={"dark"}
  const spec = await sx.openapi();
  ```

  ```bash curl theme={"dark"}
  curl https://api.scripxhq.com/v1/openapi.json -o scripx-openapi.json
  ```
</CodeGroup>

The same spec backs every page under [API Reference](/api-reference/introduction).

## Related

* [Quick start](/quick-start), the same trade in five curl calls.
* [Use with AI](/use-with-ai), hand the spec to an agent.
* [Webhooks](/webhooks), what the verify helpers implement.
