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

# SDKs

> Typed TypeScript and Python clients for the API

## Overview

Two official clients wrap the REST API. Both are generated from the same
OpenAPI document as this reference, so every endpoint and field is typed, and
both add the parts you would otherwise write yourself:

* **Auth and version pinning**: pass your `mk_live_` key once. Each SDK release
  pins `Marky-Version` to the API version it was built from, so upgrading the
  API never changes the response shapes your code sees.
* **Typed errors**: every non-2xx response becomes an error you can catch by
  class (`NotFoundError`, `RateLimitError`, ...) with the stable `code`,
  `request_id` and `param`.
* **Safe retries**: rate limits (429) are retried after `Retry-After`. Server
  errors and network failures are retried only when repeating cannot duplicate
  anything, and create calls carry an automatic `Idempotency-Key`, so a retry
  after a timeout returns the original post instead of making a second one.
* **Webhook verification**: one call checks the `X-Marky-Signature` HMAC and the
  `X-Marky-Timestamp` replay window, then returns the event.

| Language | Package | Requires |
| - | - | - |
| TypeScript / JavaScript | `mymarky` (npm) | Node 20+ |
| Python | `mymarky` (PyPI), import `mymarky` | Python 3.11+ |

Agencies that need a fully white-label integration can call the REST API
directly; the SDKs are a convenience over the same endpoints.

## TypeScript

```bash theme={null}
npm install mymarky
```

```ts theme={null}
import { Marky, NotFoundError } from "mymarky";

const api = new Marky({ apiKey: process.env.MARKY_API_KEY! });
const workspaceId = process.env.MARKY_WORKSPACE_ID!;

const { items: workspaces } = await api.listWorkspaces();
const workspace = await api.getWorkspace(workspaceId);
const { items: accounts } = await api.listIntegrations(workspaceId);
for (const a of accounts) console.log(a.platform, a.username, a.status);

// A draft: status NEW and no scheduled_publish_time, so nothing is published.
const draft = await api.createPost(workspaceId, { caption: "Hello from the SDK", status: "NEW" });

// Every route is typed on api.client and throws an ApiError on any non-2xx.
const path = { business_id: workspaceId, post_id: draft.id };
const { data: post } = await api.client.GET("/api/businesses/{business_id}/posts/{post_id}", {
  params: { path },
});
await api.client.DELETE("/api/businesses/{business_id}/posts/{post_id}", { params: { path } });

try {
  await api.client.GET("/api/businesses/{business_id}/posts/{post_id}", { params: { path } });
} catch (err) {
  if (!(err instanceof NotFoundError)) throw err; // deleted: 404 resource_not_found
}
```

This is the flow we run against the live API before every release: it creates a
draft (status `NEW`), so nothing is published, and deletes it at the end. To
attach an image, upload it with `uploadMediaFromUrl({ workspace_id, url })` and
pass `media_ids: [media.id]`; to schedule, send `status: "SCHEDULED"` with a
future `scheduled_publish_time`.

Named helpers cover the core workflow: `listWorkspaces`, `getWorkspace`,
`listIntegrations`, `listPosts`, `createPost`, `getPost`, `updatePost`,
`deletePost`, `schedulePost`, `uploadMediaFromUrl`, `createWebhook`,
`listWebhooks`, `deleteWebhook` and `paginate` (`getPost`, `updatePost` and
`deletePost` since 0.1.1). Every other endpoint is available, fully typed, on
`api.client`:

```ts theme={null}
const { data } = await api.client.GET("/api/businesses/{business_id}/topics", {
  params: { path: { business_id: workspaceId } }, // business_id is the workspace ID
});
```

## Python

```bash theme={null}
pip install mymarky
```

```python theme={null}
import os

from mymarky import Marky, NotFoundError
from mymarky.generated.api.businesses import get_workspace, list_workspaces
from mymarky.generated.api.integrations import list_connected_social_accounts
from mymarky.generated.api.posts import create_post, delete_post, get_post
from mymarky.generated.models import PostCreateRequest, PostResponse

workspace_id = os.environ["MARKY_WORKSPACE_ID"]

with Marky(api_key=os.environ["MARKY_API_KEY"]) as marky:
    workspaces = list(marky.paginate(list_workspaces.sync))
    workspace = get_workspace.sync(workspace_id, client=marky.client)
    accounts = list_connected_social_accounts.sync(workspace_id, client=marky.client)

    # A draft: status NEW and no scheduled_publish_time, so nothing is published.
    draft = create_post.sync(
        workspace_id,
        client=marky.client,
        body=PostCreateRequest(caption="Hello from the SDK", status="NEW"),
    )
    assert isinstance(draft, PostResponse)

    post = get_post.sync(workspace_id, draft.id, client=marky.client)
    delete_post.sync(workspace_id, draft.id, client=marky.client)

    try:
        get_post.sync(workspace_id, draft.id, client=marky.client)
    except NotFoundError as err:
        print(err.status, err.code)  # 404 resource_not_found
```

Each endpoint is a module under `mymarky.generated.api.<group>` with
`sync`, `sync_detailed`, `asyncio` and `asyncio_detailed` functions. Pass
`client=marky.client` to any of them. Workspace operations live in the
`businesses` group, and routes inside a workspace name their first argument
`business_id`: pass the workspace ID.

## Handling errors

```ts theme={null}
import { RateLimitError } from "mymarky";

try {
  await api.createPost(workspaceId, { caption: "Hi" });
} catch (err) {
  if (err instanceof RateLimitError) await sleep((err.retryAfter ?? 1) * 1000);
  else throw err;
}
```

Match on `err.code` (see [Error codes](https://docs.mymarky.ai/errors.md)), never on the message text.

## Verifying webhooks

Pass the **raw** request body exactly as received, plus the request headers.
Parsing and re-serializing the JSON changes the bytes and the signature will not
match. The SDKs implement the scheme on [Webhooks](https://docs.mymarky.ai/webhooks.md): HMAC-SHA256 over
`"{X-Marky-Timestamp}." + raw body`, and deliveries more than 5 minutes old are
rejected (pass `toleranceSeconds` / `tolerance_seconds` to change that).

```ts theme={null}
import express from "express";
import { constructEvent } from "mymarky";

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const event = constructEvent(req.body, req.headers, process.env.WEBHOOK_SECRET!);
  // event.event === "post.published", event.post_id, event.business_id, ...
  res.sendStatus(200);
});
```

```python theme={null}
from mymarky import construct_event

event = construct_event(raw_body, request.headers, webhook_secret)
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.