SDK

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. 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. See authentication for how to mint tokens and what scopes they carry.

Create a client

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; 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. 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 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.
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.

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.

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 PUTcompleteUpload 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:

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<T>: { data, nextCursor }, with nextCursor: null on the last page. The standard drain loop:

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:

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), inspect error.fields for field-level validation failures, and log error.requestId for support:

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;
  }
}