TypeScript SDK
Install `@getspinup/sdk`, create a client, and call Spinup's workspace-scoped public API from TypeScript or JavaScript.
Install the SDK from npm:
npm install @getspinup/sdkSpinup's SDK wraps Spinup's public API for workspace-scoped control-plane operations and agent-scoped runtime operations.
Create a client
The SDK does not read environment variables for you. Pass values from your own config layer.
In Node-based services, that might be process.env:
import { createSpinupClient } from "@getspinup/sdk";
const spinup = createSpinupClient({
apiKey: process.env.SPINUP_API_KEY,
defaultWorkspaceSlug: process.env.SPINUP_WORKSPACE,
});
const me = await spinup.me.get();
const workspaces = await spinup.workspaces.list();
const agents = await spinup.agents.list();
const supportAgent = await spinup.agents.get({
agentSlug: "support-agent",
});
console.log(supportAgent.agent.modelPolicy);
const capabilities = await spinup.agents.capabilities.list({
agentSlug: "support-agent",
});
console.log(capabilities.capabilities);In Cloudflare Workers or other edge runtimes, read bindings from the request handler's env argument and create the client there. The Env type is generated from your Worker bindings.
import { createSpinupClient } from "@getspinup/sdk";
export default {
async fetch(_request, env) {
const spinup = createSpinupClient({
apiKey: env.SPINUP_API_KEY,
defaultWorkspaceSlug: env.SPINUP_WORKSPACE,
});
const agents = await spinup.agents.list();
return Response.json({ agents: agents.agents });
},
} satisfies ExportedHandler<Env>;What works today
Current workspace-oriented control-plane methods:
me.get()workspaces.list()workspaces.secrets.list()workspaces.secrets.get({ secretId })workspaces.secrets.create({ name, defaultProjectionName, value })workspaces.secrets.update({ secretId, name, defaultProjectionName, value })workspaces.secrets.delete({ secretId })agents.list()agents.get({ agentSlug })agents.create({ name })agents.deploy({ agentSlug })agents.update({ agentSlug, name })agents.update({ agentSlug, primaryModel: { provider, model } })agents.update({ agentSlug, runtimePolicy: { allowedPackageInstallationMode } })agents.update({ agentSlug, runtimePolicy: { resourceLimits: { memoryMiB, diskGiB } } })agents.updateHarnesses({ agentSlug, defaultHarness })agents.instructions.get({ agentSlug })agents.instructions.set({ agentSlug, coreInstructions })agents.instructions.clear({ agentSlug })agents.setupCommand.get({ agentSlug })agents.setupCommand.set({ agentSlug, setupCommand })agents.setupCommand.clear({ agentSlug })agents.capabilities.list({ agentSlug })agents.capabilities.add({ agentSlug, capability })agents.capabilities.update({ agentSlug, capabilityId, capability })agents.capabilities.remove({ agentSlug, capabilityId })agents.runtimeKey.issue({ agentSlug })agents.schedules.list({ agentSlug })agents.schedules.create({ agentSlug, schedule })agents.schedules.update({ agentSlug, scheduleId, schedule })agents.schedules.disable({ agentSlug, scheduleId })agents.schedules.delete({ agentSlug, scheduleId })agents.schedules.preview({ agentSlug, schedule })agents.secretBindings.list({ agentSlug })agents.secretBindings.update({ agentSlug, bindings, expectedStateVersion })agents.proposedChanges.list({ agentSlug, status })agents.proposedChanges.get({ agentSlug, proposedChangeId })agents.proposedChanges.approve({ agentSlug, proposedChangeId, expectedBaseStateVersionId, expectedPayloadHash })agents.proposedChanges.reject({ agentSlug, proposedChangeId, reason })agents.delete({ agentSlug, confirmationSlug })
Current agent-runtime methods:
agents.status({ agentId })agents.runs.list({ agentId })agents.runs.create({ agentId, input, model })agents.runs.get({ agentId, runId })agents.runs.wait({ agentId, runId })
agents.runtimeKey.issue({ agentSlug }) is a control-plane method. It requires a personal/device key or workspace API key and returns a plaintext sk_agent_... runtime key once, plus agentId for runtime calls.
Configuration writes update the saved draft. agents.deploy({ agentSlug }) promotes that draft into the active release used by future runs, schedules, and Agent Chat. Deploy does not start runtime capacity by itself.
Per-call overrides still work when you need a different workspace or key:
await spinup.agents.get({
agentSlug: "research-agent",
apiKey: process.env.OTHER_SPINUP_API_KEY,
workspaceSlug: "labs",
});If you do not set defaultWorkspaceSlug, pass workspaceSlug explicitly on workspace-scoped reads.
Inspect an agent
agents.get({ agentSlug }) returns the public inspection shape for one agent:
- agent ID, slug, created time, and updated time
- deployment status, undeployed-change count, and active release metadata
- model, supported harnesses and default, capabilities, and runtime sizing
- environment status, attention, readiness time, and runtime reconciliation state
Environment variable values and secret references are intentionally omitted from the agent inspection response. Use workspaces.secrets.* for masked workspace secret metadata and agents.secretBindings.* for per-agent bindings.
Manage capabilities
See Capabilities for the concept. Use the SDK to keep capability settings with the agent for future runs.
The SDK can mark a capability as declared or disabled. Spinup records runtime fulfillment separately when the environment projects, validates, or fails a capability.
List capabilities:
const capabilities = await spinup.agents.capabilities.list({
agentSlug: "support-agent",
});Add a declared CLI requirement:
await spinup.agents.capabilities.add({
agentSlug: "support-agent",
capability: {
kind: "cli",
name: "ffmpeg",
source: "spinup-runtime",
status: "declared",
validationPlan: {
strategy: "executable",
executable: "ffmpeg",
versionArgs: ["-version"],
},
},
});Disable or remove a capability:
await spinup.agents.capabilities.update({
agentSlug: "support-agent",
capabilityId: "capability_01hxyz...",
capability: { status: "disabled" },
});
await spinup.agents.capabilities.remove({
agentSlug: "support-agent",
capabilityId: "capability_01hxyz...",
});Manage agents
Create an agent:
const created = await spinup.agents.create({
name: "Support Agent",
});Creation saves a draft-only agent. It cannot run until the draft is deployed:
const deployed = await spinup.agents.deploy({
agentSlug: created.agent.slug,
});
console.log(deployed.agent.deploymentStatus, deployed.agent.activeRelease?.version);Rename an agent:
const renamed = await spinup.agents.update({
agentSlug: created.agent.slug,
name: "Support Agent v2",
});Update the primary model and runtime sizing:
await spinup.agents.update({
agentSlug: created.agent.slug,
primaryModel: {
provider: "openai",
model: "gpt-5.4",
maxOutputTokens: 64000,
},
runtimePolicy: {
resourceLimits: {
memoryMiB: 8192,
diskGiB: 10,
},
},
});Public runtime sizing accepts memory values 4096, 8192, or 16384 MiB and disk values 4 or 10 GiB. primaryModel.maxOutputTokens caps one model response; omit it to use Spinup's default of 64000.
Update the default harness:
await spinup.agents.updateHarnesses({
agentSlug: renamed.agent.slug,
defaultHarness: "hermes",
});Supported harness values today are openclaw and hermes.
Updates, harness changes, Core Instructions, setup commands, capabilities, secret bindings, and runtime policy changes update draft configuration. Call agents.deploy({ agentSlug }) after the edits you want future runs, schedules, and Agent Chat to use.
Manage Core Instructions:
const currentInstructions = await spinup.agents.instructions.get({
agentSlug: renamed.agent.slug,
});
await spinup.agents.instructions.set({
agentSlug: renamed.agent.slug,
coreInstructions: [
currentInstructions.coreInstructions,
"Prefer concise answers and state uncertainty clearly.",
]
.filter(Boolean)
.join("\n"),
});
await spinup.agents.instructions.clear({
agentSlug: renamed.agent.slug,
});Add a skill capability:
await spinup.agents.capabilities.add({
agentSlug: renamed.agent.slug,
capability: {
kind: "skill",
name: "vercel-react-best-practices",
source: "vercel-labs/agent-skills",
installPlan: {
strategy: "skill",
manager: "skills_cli",
skillName: "vercel-react-best-practices",
source: "vercel-labs/agent-skills",
},
validationPlan: {
strategy: "skills_lock",
skillName: "vercel-react-best-practices",
source: "vercel-labs/agent-skills",
},
},
});For direct skill paths, leave installPlan.skillName unset or null; Spinup will run the pinned Skills CLI with the source only and will not add a --skill selector.
Create a weekly schedule:
await spinup.agents.schedules.create({
agentSlug: renamed.agent.slug,
schedule: {
name: "Friday newsletter",
input: "Draft the weekly newsletter",
timezone: "Europe/Amsterdam",
cadence: {
kind: "cron",
expression: "0 9 * * 5",
syntaxVersion: "spinup-cron-v1",
timezone: "Europe/Amsterdam",
},
},
});Scheduled dispatch starts normal Spinup runs. The agent needs an active deploy/release before a schedule can execute successfully; if there is no deployable active release, Spinup records the blocked scheduled occurrence and pauses the schedule for action. Later draft edits do not affect scheduled runs until you deploy again.
Manage secrets and bindings
Workspace secret methods return masked metadata only. Secret values are accepted on create or update and are not returned by reads.
const openAiApiKey = process.env.OPENAI_API_KEY;
if (!openAiApiKey) {
throw new Error("Missing OPENAI_API_KEY");
}
const secret = await spinup.workspaces.secrets.create({
name: "OpenAI production",
defaultProjectionName: "OPENAI_API_KEY",
value: openAiApiKey,
});
const secretBindings = await spinup.agents.secretBindings.list({
agentSlug: renamed.agent.slug,
});
await spinup.agents.secretBindings.update({
agentSlug: renamed.agent.slug,
bindings: [
...secretBindings.bindings.map((binding) => ({
secretId: binding.secret.id,
projectionName: binding.projectionName,
})),
{
secretId: secret.secret.id,
projectionName: "OPENAI_API_KEY",
},
],
expectedStateVersion: secretBindings.stateVersion,
});Manage setup commands and proposed changes
Setup commands store one durable multiline command object with the agent. Proposed changes are runtime-origin state changes that you can inspect, approve, or reject from application code.
await spinup.agents.setupCommand.set({
agentSlug: renamed.agent.slug,
setupCommand: {
script: "bun install",
cwd: ".",
timeoutSeconds: 600,
status: "enabled",
},
});
const proposedChanges = await spinup.agents.proposedChanges.list({
agentSlug: renamed.agent.slug,
status: "pending",
});Manage schedules and runtime keys
Schedules are control-plane configuration that trigger future agent runs. Runtime keys are one-agent credentials for direct status and run calls; agents.runtimeKey.issue returns the plaintext key once and replaces the previous mapped key for that agent.
const schedule = await spinup.agents.schedules.create({
agentSlug: renamed.agent.slug,
schedule: {
name: "Friday newsletter",
input: "Draft and send this week's newsletter.",
timezone: "Europe/Amsterdam",
cadence: {
kind: "cron",
expression: "0 9 * * 5",
syntaxVersion: "spinup-cron-v1",
timezone: "Europe/Amsterdam",
},
timeoutSeconds: 1800,
},
});
const preview = await spinup.agents.schedules.preview({
agentSlug: renamed.agent.slug,
schedule: {
timezone: "Europe/Amsterdam",
cadence: schedule.schedule.cadence,
},
});
const runtimeKey = await spinup.agents.runtimeKey.issue({
agentSlug: renamed.agent.slug,
});Use runtimeKey.apiKey with agents.status and agents.runs.*, and use runtimeKey.agentId as the runtime endpoint agent identifier.
Delete an agent:
await spinup.agents.delete({
agentSlug: renamed.agent.slug,
confirmationSlug: renamed.agent.slug,
});Agent creation, deletion, default harness changes, and capability changes follow the same control-plane behavior as the dashboard. Creation saves a draft-only agent. Harness changes choose the default used when a future active release does not specify harness; supported harnesses stay available for per-run overrides. Capability changes update what Spinup keeps with the draft. Deleting a ready agent first tears down its runtime environment state, and deletion is rejected while an environment lifecycle operation is in progress.
Runtime Status And Runs
Runtime helpers require an agent runtime key (sk_agent_...) scoped to the target agent ID:
const runtime = createSpinupClient({
apiKey: process.env.SPINUP_AGENT_API_KEY,
});
const status = await runtime.agents.status({
agentId: "agent_01hxyz...",
});
const createdRun = await runtime.agents.runs.create({
agentId: "agent_01hxyz...",
input: "Run the full repository audit.",
model: {
provider: "openrouter",
model: "anthropic/claude-sonnet-4-5",
maxOutputTokens: 32000,
},
timeoutSeconds: 1800,
idempotencyKey: "repo-audit-2026-06-14",
});
const terminalRun = await runtime.agents.runs.wait({
agentId: "agent_01hxyz...",
runId: createdRun.run.id,
timeoutMs: 30 * 60 * 1000,
});
console.log(terminalRun.run.status, terminalRun.run.output);
const runs = await runtime.agents.runs.list({
agentId: "agent_01hxyz...",
limit: 20,
});
const run = await runtime.agents.runs.get({
agentId: "agent_01hxyz...",
runId: runs.runs[0].id,
});
console.log(run.run.status, run.run.output);agents.runs.create starts durable work and returns the run handle quickly. agents.runs.wait polls agents.runs.get until the run reaches completed, failed, or timed_out, then returns the terminal run object.
Run history returns support-safe fields: lifecycle timestamps, harness selection, request options, final user-facing output, generic error state, warnings, token/tool counts, phase evidence, and bounded evidence counts. It does not return the original prompt input, raw stdout/stderr, raw provider payloads, billing cost details, audit events, actor IDs, internal session IDs, Worker Host IDs, microVM IDs, or secret/env maps.
Override base URLs carefully
For local or staging environments, you can override the SDK hosts:
const spinup = createSpinupClient({
apiBaseUrl: "https://api.staging.example.com/v1",
apiKey: process.env.SPINUP_API_KEY,
});Point apiBaseUrl at a plain origin or the final base path you want the SDK to resolve against. Do not include query strings or hash fragments.
Use the right credential
Prefer a dashboard-created workspace API key (sk_workspace_...) for SDK clients.
- Workspace API key (
sk_workspace_...): preferred for@getspinup/sdk, direct public API calls, and server-side automation in one workspace - Personal/device key (
sk_user_...): created byspinup login; user-scoped so the CLI can list and switch workspaces - Agent runtime key (
sk_agent_...): one-agent credential for SDK runtime helpers and direct/statusand/runscalls
Use a workspace or personal/device key for agents.runtimeKey.issue, then use the returned sk_agent_... key only with runtime status and run methods.
Device authorization is optional
If you already have a valid sk_workspace_... workspace API key, you can use the SDK directly without the device authorization flow.
Device authorization and personal/device key exchange are reserved for the Spinup CLI. Application code should use a dashboard-created workspace API key.