# Management client > Full typed access to the management API from @myna-sh/sdk/management, for servers and agents only. `@myna-sh/sdk/management` exports `createManagementClient`, a typed wrapper over the entire management API following the [management API conventions](/management-api/overview). It is server-only: it imports `node:crypto` and `node:fs`, and its token can mutate schema, entries, members, and billing. Never bundle it for a browser or expose its token to one — browser reads belong to the [public client](/sdk/public-client). See [authentication](/getting-started/authentication) for how to mint tokens and what scopes they carry. ## Create a client ```ts import { createManagementClient } from "@myna-sh/sdk/management"; const client = createManagementClient({ token: process.env.MYNA_TOKEN! }); ``` | Option | Type | Default | Description | | --- | --- | --- | --- | | `token` | `string` | — (required) | Management/CLI credential (`myna_sk_...`). | | `apiUrl` | `string` | `https://api.myna.sh` | API origin; the client appends `/v1`. | | `fetch` | `FetchLike` | global `fetch` | Custom fetch — also used for the presigned asset PUT. | | `retry` | `RetryOptions` | `{ maxRetries: 3, baseDelayMs: 200, maxDelayMs: 5000 }` | Retry policy (see idempotency below). | | `idempotencyKey` | `() => string` | `randomUUID` per call | Factory for the `Idempotency-Key` attached to every mutation. | ## Namespace tour | Namespace | Representative methods | Covers | | --- | --- | --- | | `organizations` | `list`, `create`, `get`, `update`, `delete`, `export`, `usage` | Organization lifecycle, data export, usage metering. | | `members` | `list`, `update`, `remove` | Organization membership and roles. | | `invitations` | `list`, `create`, `revoke`, `accept` | Inviting users; `accept(token)` redeems an invitation token. | | `projects` | `list`, `create`, `get`, `update`, `archive`, `export`, `corsCheck`, `access`, `views` | Projects within an organization; `corsCheck` and `access` diagnose a blank page and a denial respectively. | | `schema` | `collections`, `collection`, `versions`, `translations`, `diff`, `push` | Reading collection schemas, versions, and per-locale [translation coverage](/schema/defining-schemas); `diff`/`push` mirror `myna schema push` (with `allowDestructive`, `changeSummary`). | | `entries` | `list`, `create`, `get`, `update`, `delete`, `unpublish`, `restore`, `revisions`, `revision`, `restoreRevision` | Draft entries, revision history, restore. Writes stage into [change sets](/concepts/change-sets). `list` accepts `q` for full-text search. | | `changeSets` | `list`, `create`, `get`, `update`, `diff`, `validate`, `publish`, `close`, `checks`, `runChecks`, `reportCheck` | The staging/publish unit; `publish` sends `{ confirm: true }`, `diff` returns per-field before/after. | | `checks` | `list`, `declare`, `remove` | The [external checks](/concepts/change-sets) a project expects, and which block publishing. | | `previews` | `create`, `list`, `revoke` | Preview tokens for a change set or a single entry (max 7-day expiry). | | `assets` | `upload`, `createUpload`, `completeUpload`, `list`, `get`, `usage`, `similar`, `update`, `replace`, `delete` | Binary assets; `upload` runs the whole presigned flow, `similar` finds [near-duplicate images](/concepts/assets). | | `apiKeys` | `listForOrganization`, `createForOrganization`, `revokeForOrganization`, `listForProject`, `createForProject`, `revokeForProject` | Org- and project-scoped API keys. | | `webhooks` | `list`, `create`, `update`, `delete`, `deliveries`, `retry` | Webhook endpoints, delivery history, redelivery. | | `billing` | `status`, `checkout`, `portal` | Billing state, checkout sessions, customer portal. | | `activity()` | `activity(project, { actorType?, action?, targetType?, from?, to?, limit?, cursor? })` | Paginated audit log (a method, not a namespace). | Most methods take the parent resource id/slug first (`project` or `organization`), then the resource id, then a typed body. Reads accept an optional `AbortSignal`. ## Worked example: the agent-safe loop Every entry write stages into a change set — nothing reaches the published surface until an explicit publish. This is the loop an agent should run: stage, validate, hand back a preview, and publish only when authorized. ```ts const project = "my-site"; // 1. Open a change set to stage work in. const changeSet = await client.changeSets.create(project, { title: "March launch posts", description: "Two posts plus hero image.", }); // 2. Create an entry inside it. const entry = await client.entries.create(project, { collection: "posts", slug: "march-launch", data: { title: "March launch", body: "..." }, changeSetId: changeSet.id, }); // 3. Upload an asset (create → presigned PUT → complete, in one call) // and reference it from the entry. const hero = await client.assets.upload(project, "./hero.png"); await client.entries.update(project, entry.id, { data: { ...entry.data, heroImage: hero.id }, expectedRevisionId: entry.draftRevisionId ?? undefined, changeSetId: changeSet.id, }); // 4. Validate the whole change set against the current schema. const result = await client.changeSets.validate(project, changeSet.id); if (!result.valid) throw new Error(JSON.stringify(result.errors, null, 2)); // 5. Mint a preview for human review. const preview = await client.previews.create(project, { changeSetId: changeSet.id, expiresInSeconds: 86_400, }); console.log("Review at:", preview.url); // 6. Publish — only with the content:publish scope and explicit approval. const { publishedEntryIds } = await client.changeSets.publish(project, changeSet.id); ``` `validate` returns `{ valid, errors }` where each error names `resourceType`, `resourceId`, `path`, `message`, and an optional `code`. `previews.create` takes exactly one of `changeSetId` or `entryId`. Publishing requires the `content:publish` scope; tokens without it can run the entire loop through step 5, which is the recommended posture for autonomous agents — see [change sets](/concepts/change-sets). `assets.upload(project, input, meta?)` accepts a filesystem path (`string`), `Uint8Array`, or `ArrayBuffer`. It computes an MD5 checksum, guesses the content type from the filename (override with `meta.contentType` / `meta.filename` / `meta.byteSize`), then performs create → presigned `PUT` → `completeUpload` and returns the finished `AssetContract`. The presigned `PUT` goes to the storage host directly and throws a plain `Error` (not `MynaApiError`) on failure. ## Optimistic concurrency `entries.update` accepts `expectedRevisionId`. Pass the revision you last read; if someone else has written in the meantime the API rejects the update with a 409 conflict instead of silently overwriting: ```ts import { isMynaApiError } from "@myna-sh/sdk/management"; const current = await client.entries.get(project, entryId); try { await client.entries.update(project, entryId, { data: { ...current.data, title: "Updated title" }, expectedRevisionId: current.draftRevisionId ?? undefined, }); } catch (error) { if (isMynaApiError(error) && error.status === 409) { // Re-read, re-apply, retry — or surface the conflict. } else { throw error; } } ``` Always send `expectedRevisionId` from agents: it turns lost-update races into explicit, recoverable conflicts. ## Pagination Paginated surfaces — `entries.list`, `changeSets.list`, `assets.list`, and `activity()` — return `Page`: `{ data, nextCursor }`, with `nextCursor: null` on the last page. The standard drain loop: ```ts const all = []; let cursor: string | undefined; do { const page = await client.entries.list(project, { collection: "posts", status: "draft", limit: 100, cursor, }); all.push(...page.data); cursor = page.nextCursor ?? undefined; } while (cursor); ``` List-shaped methods without cursors (`organizations.list`, `schema.collections`, `previews.list`, `webhooks.list`, `entries.revisions`, and similar) return plain arrays. ## Idempotency and retries Every mutation automatically carries an `Idempotency-Key` header, generated by the `idempotencyKey` factory (default: a fresh `randomUUID()` per call). Because the key is present, mutations are eligible for the same automatic retry policy as reads: statuses 408, 425, 429, 500, 502, 503, 504 and network failures are retried up to `maxRetries` times with exponential backoff and jitter, honoring `Retry-After`. The server deduplicates replays by key, so a retried `create` cannot double-create. To make a mutation replay-safe across process restarts (not just across in-process retries), supply a stable key derived from your own operation identity: ```ts const client = createManagementClient({ token: process.env.MYNA_TOKEN!, idempotencyKey: () => `import-posts-${jobId}`, }); ``` Note the factory is client-wide and called once per mutation — use a per-operation client, or vary the value it returns, when different mutations need different stable keys. ## Typed errors Failed API calls throw the same `MynaApiError` as the public client, re-exported from `@myna-sh/sdk/management` alongside `isMynaApiError`. Branch on the stable `error.code` (see [error codes](/management-api/errors)), inspect `error.fields` for field-level validation failures, and log `error.requestId` for support: ```ts try { await client.entries.create(project, { collection: "posts", data: {} }); } catch (error) { if (isMynaApiError(error) && error.code === "VALIDATION_FAILED") { for (const field of error.fields ?? []) { console.error(`${field.path}: ${field.message}`); } } else { throw error; } } ```