# SDKs and Tools

> MyStocks client source previews, multi-language examples, OpenAPI specifications, API Tester, and webhook utilities.

Use the client source previews for transport, structured errors, request IDs, timeouts, safe retries,
and webhook verification. They do not replace understanding the API contract: always read the
endpoint's authentication, scope, idempotency, and lifecycle requirements.

<Callout type="warn" title="Registry publication pending">
  `@mystocks-africa/partner-sdk` is not currently published on npm and `mystocks-partner` is not
  currently published on PyPI. Do not put either registry install command into a production build.
  Approved controlled-pilot partners receive a versioned artifact and checksum directly from
  MyStocks. Registry availability will be announced in the [changelog](/partners/docs/changelog).
</Callout>

Both clients now have a version-locked release pipeline. A signed `partner-sdk-vX.Y.Z` tag runs the
contract suites, builds the npm and Python distributions, verifies their metadata, produces SHA-256
checksums and build provenance, publishes through npm and PyPI trusted publishing, and attaches the
same artifacts to a GitHub release. Registry install commands will be added here only after that
release completes successfully.

## Client availability

| Language | Package | Status |
| --- | --- | --- |
| TypeScript / JavaScript | `@mystocks-africa/partner-sdk` | Source preview; tested and buildable, registry release pending |
| Python | `mystocks-partner` | Source preview; sync/async clients, registry release pending |
| Raw HTTP | `fetch`, `requests`, or any OpenAPI client | Public and recommended until the SDK registry releases |

## Initialize a client

The SDK examples below are for approved pilot artifacts. Everyone else can use the equivalent raw
HTTP tab today.

<CodeTabs
  node={`const BASE = "https://mystocks.africa/api/sandbox/v1/partner";
const apiKey = process.env.MYSTOCKS_API_KEY;

async function api(path, init = {}) {
  return fetch(BASE + path, {
    ...init,
    headers: { Authorization: \`Bearer \${apiKey}\`, "Content-Type": "application/json", ...init.headers },
  }).then(async (res) => {
    const body = await res.json();
    if (!res.ok) throw Object.assign(new Error(body.error?.message), body.error);
    return body;
  });
}`}
  python={`import os
import requests

BASE = "https://mystocks.africa/api/sandbox/v1/partner"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['MYSTOCKS_API_KEY']}"`}
  sdk={`import { MyStocksClient } from "@mystocks-africa/partner-sdk";

const client = new MyStocksClient({
  apiKey: process.env.MYSTOCKS_API_KEY!,
  environment: "sandbox",
});`}
/>

## Create a sub-account

<TryEndpoint id="createUsers" />

<CodeTabs
  curl={`curl -X POST https://mystocks.africa/api/sandbox/v1/partner/users \\
  -H "Authorization: Bearer $MYSTOCKS_API_KEY" \\
  -H "Content-Type: application/json" \\
  -d '{"externalId":"user_42","displayName":"Jane Doe","email":"jane@example.com"}'`}
  node={`const user = await api("/users", {
  method: "POST",
  body: JSON.stringify({ externalId: "user_42", displayName: "Jane Doe", email: "jane@example.com" }),
});`}
  python={`user = session.post(f"{BASE}/users", json={
    "externalId": "user_42", "displayName": "Jane Doe", "email": "jane@example.com"
}).json()`}
  sdk={`const user = await client.subAccounts.create({
  externalId: "user_42",
  displayName: "Jane Doe",
  email: "jane@example.com",
});`}
/>

## Quote and place a trade

<TryEndpoint id="createUserTrade" />

<CodeTabs
  node={`const quote = await api("/quote/SCOM.KE?type=BUY&quantity=10&subAccountId=" + user.subAccountId);
const order = await api("/users/" + user.subAccountId + "/trade", {
  method: "POST",
  headers: { "Idempotency-Key": "trade_user42_scom_001" },
  body: JSON.stringify({ symbol: "SCOM.KE", type: "BUY", quantity: 10, quoteId: quote.quoteId }),
});`}
  python={`quote = session.get(f"{BASE}/quote/SCOM.KE", params={
    "type": "BUY", "quantity": 10, "subAccountId": user["subAccountId"]
}).json()
order = session.post(
    f"{BASE}/users/{user['subAccountId']}/trade",
    headers={"Idempotency-Key": "trade_user42_scom_001"},
    json={"symbol": "SCOM.KE", "type": "BUY", "quantity": 10, "quoteId": quote["quoteId"]},
).json()`}
  sdk={`const quote = await client.trading.getQuote("SCOM.KE", {
  type: "BUY", quantity: 10, subAccountId: user.subAccountId,
});
const order = await client.subAccounts.trade(
  user.subAccountId,
  { symbol: "SCOM.KE", type: "BUY", quantity: 10, quoteId: quote.quoteId },
  { idempotencyKey: "trade_user42_scom_001" },
);`}
/>

## Verify a webhook

Always verify the exact, unparsed request bytes before decoding JSON.

<CodeTabs
  node={`const rawBody = req.body; // Buffer from express.raw({ type: "*/*" })
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const supplied = req.headers["x-mystocks-signature"] ?? "";
const valid = supplied.length === expected.length &&
  crypto.timingSafeEqual(Buffer.from(supplied), Buffer.from(expected));`}
  python={`raw_body = request.get_data(cache=False)
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(request.headers.get("x-mystocks-signature", ""), expected)`}
  sdk={`const rawBody = new Uint8Array(req.body); // keep the original bytes
const valid = await verifyWebhookSignature(
  rawBody,
  req.headers["x-mystocks-signature"],
  process.env.MYSTOCKS_WEBHOOK_SECRET!,
);`}
/>

## API tools

<Cards>
  <Card title="Interactive API Reference" href="/partners/docs/api-reference">
    Browse the machine-readable OpenAPI contract, schemas, examples, and operation IDs.
  </Card>
  <Card title="API Tester" href="/partners/docs/api-tester">
    Exercise sandbox or production requests from the browser with a partner key.
  </Card>
  <Card title="OpenAPI and Postman" href="/partners/docs/api-reference">
    Download the OpenAPI document or import the published Postman collection to generate clients and test workflows.
  </Card>
  <Card title="System Status" href="/status">
    Live uptime, active incidents, and 30-day incident history. Raw JSON at `GET /api/v1/status`.
  </Card>
</Cards>

## Operational awareness

You do not need to poll the status page. Incidents are pushed to your registered webhooks as
`incident.declared` / `incident.resolved` and streamed on `GET /stream`. During planned
maintenance the API returns `503 MAINTENANCE` with a `Retry-After` header. See
[Errors](/partners/docs/errors) for handling guidance, including read-only maintenance mode.

## SDK behavior to rely on

- Reads and idempotent writes may retry transient `429`, `500`, `502`, `503`, and `504` responses.
- Non-idempotent writes are not automatically retried.
- Preserve the same `Idempotency-Key` when retrying a write.
- Capture `X-Request-ID` in logs and support tickets.
- Verify webhooks using the raw body and the `x-mystocks-signature` header.

Begin with [Authentication](/partners/docs/auth) and the [Broker API quickstart](/partners/docs/getting-started-broker).
