Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.arkor.ai/llms.txt

Use this file to discover all available pages before exploring further.

Deployments

A deployment publishes a trained adapter (or a base model) at a dedicated subdomain (https://<slug>.arkor.app/v1/chat/completions) that speaks the OpenAI Chat Completions wire format. Pointed at it from any OpenAI-compatible client (the official openai SDK, LangChain, etc.) and it just works. CloudApiClient is the typed entry point for managing these deployments programmatically (creating them, rotating API keys, deleting them) from a Node.js script or from your own server. The Studio dashboard covers the common create / toggle / key-management actions, but several operations are SDK-only today (re-targeting an existing deployment, configuring non-default run retention, bulk scripting); see the Studio Endpoints page for the current parity boundary. This page covers the SDK surface.

When to use the SDK

  • CI / IaC: provision deployments alongside your other infra. A trained job’s final adapter goes from “saved checkpoint” to “live URL” in one script step.
  • Custom dashboards: embed deployment management in your own admin UI.
  • Bulk operations: script revoking + re-issuing keys across many deployments after an incident.
For interactive day-to-day use, prefer Studio or the upcoming arkor endpoints CLI; the SDK is the lower-level surface those tools sit on top of.

Setup

import {
  CloudApiClient,
  defaultArkorCloudApiUrl,
  ensureCredentials,
} from "arkor";

// `ensureCredentials` reads the existing `~/.arkor/credentials.json`
// or bootstraps a fresh anonymous identity if none exists.
const credentials = await ensureCredentials();

const client = new CloudApiClient({
  // `defaultArkorCloudApiUrl(credentials)` resolves the right control
  // plane: the `ARKOR_CLOUD_API_URL` env var first, then the URL the
  // credentials were issued against (anonymous tokens carry it from
  // signup; OAuth tokens carry it from `arkor login`), then the
  // production endpoint as a fallback.
  baseUrl: defaultArkorCloudApiUrl(credentials),
  credentials,
});
CloudApiClient accepts the same Credentials shape used everywhere else in the SDK: an OAuth access token (after arkor login) or an anonymous CLI token. The API surface is identical for both. The baseUrl you hand it must match the control plane the credentials were issued against — passing an OAuth token from staging into a production CloudApiClient (or vice versa) will 401 even though the token itself is well-formed. defaultArkorCloudApiUrl(credentials) reads the auth-time URL stamped onto the credentials so this is handled automatically; older credentials (persisted before the URL was stamped) fall through to the production default and need either a re-login or ARKOR_CLOUD_API_URL set explicitly. Every deployment lives inside a (orgSlug, projectSlug) scope; pass that scope on every call:
const scope = { orgSlug: "my-org", projectSlug: "my-project" };

Create a deployment

const { deployment } = await client.createDeployment(scope, {
  slug: "support-bot",                         // → support-bot.arkor.app
  target: {
    kind: "adapter",
    adapter: { kind: "final", jobId: "<uuid>" },
  },
  authMode: "fixed_api_key",                   // or "none" for public
});
target is a discriminated union:
type DeploymentTarget =
  | { kind: "adapter"; adapter:
      | { kind: "final"; jobId: string }
      | { kind: "checkpoint"; jobId: string; step: number };
    }
  | { kind: "base_model"; baseModel: string };
authMode is either "fixed_api_key" (clients send Authorization: Bearer … or x-api-key) or "none" (the URL is open; only use for public demos / models). runRetentionMode and runRetentionDays are optional; omitting them uses the server defaults (days / 7 days). Pass "unlimited" to retain indefinitely or "disabled" to skip persistence entirely. The slug must match [a-z0-9][a-z0-9-]*[a-z0-9] (2-50 chars). Reserved labels (www, api, admin, etc.) are rejected by the server with a 400.

Issue an API key

const { key } = await client.createDeploymentKey(deployment.id, scope, {
  label: "production",
});

console.log(key.plaintext); // ← shown EXACTLY ONCE; store now
The plaintext is returned only on creation. Subsequent listDeploymentKeys() calls return the label and a display prefix (ark_live_abcd1234…), never the full key. If a key is lost, issue a new one and revoke the old. To use the key:
curl https://support-bot.arkor.app/v1/chat/completions \
  -H "Authorization: Bearer $ARK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"ignored","messages":[{"role":"user","content":"hi"}]}'
The model field in the request body is ignored, since the deployment already pins the target. Standard OpenAI SDKs work out of the box; just point baseURL at https://<slug>.arkor.app/v1.

List, get, update, delete

// List all deployments in this project
const { deployments } = await client.listDeployments(scope);

// Look up one
const { deployment } = await client.getDeployment(id, scope);

// Re-target without changing the URL; clients keep working
await client.updateDeployment(id, scope, {
  target: { kind: "adapter", adapter: { kind: "final", jobId: newJobId } },
});

// Or temporarily disable
await client.updateDeployment(id, scope, { enabled: false });

// Delete (cascades to keys; the URL goes 404)
await client.deleteDeployment(id, scope);
updateDeployment is partial; any field you omit is left unchanged. The URL itself is determined by slug and is not mutable. If you need a different slug, create a new deployment.

Manage keys

const { keys } = await client.listDeploymentKeys(id, scope);
// keys: [{ id, label, prefix, enabled, createdAt, lastUsedAt }, ...]

// Revoke. `enabled` flips to false on the row; the edge service
// stops accepting the key shortly afterwards. The exact propagation
// window is not part of the public contract, so don't gate sensitive
// flows on it — pair revoke with a server-side block when the
// disable must be immediate.
await client.revokeDeploymentKey(id, keyId, scope);
lastUsedAt is updated best-effort by the edge service. It may lag arbitrarily under traffic, may not appear at all between edge instance restarts, and is not part of any timing guarantee — treat it as approximate. Use it for “has anyone used this key recently?” rather than precise audit.

Error handling

Non-2xx responses surface as CloudApiError:
import { CloudApiClient, CloudApiError } from "arkor";

try {
  await client.createDeployment(scope, { slug: "taken", /* … */ });
} catch (err) {
  if (err instanceof CloudApiError && err.status === 409) {
    console.error("Slug already taken; pick another");
  } else {
    throw err;
  }
}
Common statuses:
StatusWhenSuggested action
400Schema validation failure (bad slug, malformed target)Surface err.message to the user
403Caller is not a member of the org / projectRe-resolve scope; check arkor whoami
404Deployment / key / job not found in scopeConfirm UUIDs match the scope you passed
409Slug collision on createRetry with a different slug
426SDK is too oldUpgrade arkor per the message body

Reference

MethodReturns
listDeployments(scope){ deployments: DeploymentDto[] }
getDeployment(id, scope){ deployment: DeploymentDto }
createDeployment(scope, input){ deployment: DeploymentDto }
updateDeployment(id, scope, input){ deployment: DeploymentDto }
deleteDeployment(id, scope)void (HTTP 204)
listDeploymentKeys(id, scope){ keys: DeploymentKeyDto[] }
createDeploymentKey(id, scope, input){ key: CreateDeploymentKeyResult } (plaintext only here)
revokeDeploymentKey(id, keyId, scope)void (HTTP 204)
Runtime exports (classes, importable as values): CloudApiClient, CloudApiError. Type-only exports (importable with import type): CloudApiClientOptions, DeploymentTarget, DeploymentAuthMode, DeploymentRunRetentionMode, DeploymentDto, DeploymentKeyDto, DeploymentScope, CreateDeploymentInput, UpdateDeploymentInput, CreateDeploymentKeyInput, CreateDeploymentKeyResult.