@plane/sdk/client
The API as promises. One key, one environment.
import { createPlaneClient, SessionNotFoundError } from "@plane/sdk/client"
const client = createPlaneClient({
baseUrl: "https://plane.example.com",
apiKey: process.env.PLANE_API_KEY, // required; this is also the default
headers: { "x-request-id": id }, // optional
fetch: myFetch // optional
})A key is required. createPlaneClient throws immediately without one, naming both
ways to supply it, rather than letting every call come back Unauthorized from the far
end of a round trip. An explicit headers.authorization wins and satisfies the check,
for a caller already behind their own auth proxy.
There is no environment option and there will not be one. The key is the
environment.
Nothing connects until the first call, so constructing a client at module scope in a
serverless handler is free. close() releases the transport.
Surface
await client.me() // who this key is, and where it points
await client.agents.list()
await client.agents.get({ id })
await client.agents.upsert({ slug, definition, expectedVersion })
await client.agents.versions({ id })
await client.agents.getVersion({ id, version })
await client.agents.imageStatus({ id }) // sandboxed agents; a read, never builds
await client.agents.rebake({ id })
await client.agents.promote({ agentId, to, mapping, dryRun })
await client.sessions.create({ agentId, initialMessage, metadata, identity, overrides })
await client.sessions.send({ id, message, mode, metadata, attachments, idempotencyKey })
await client.sessions.get({ id })
await client.sessions.list({ agentId, status, filter, limit, cursor })
await client.sessions.events({ id, after, limit })
await client.sessions.children({ id })
await client.sessions.annotate({ id, seq, metadata, mode })
await client.sessions.updateMetadata({ id, mode, metadata })
await client.sessions.pending({ id }) // approvals waiting
await client.sessions.decide({ id, toolCallId, decision, input, message })
await client.sessions.authorizations({ id })
await client.sessions.interrupt({ id })
await client.sessions.streamToken({ id, format, ttlSeconds, include })
await client.providers.upsert({ name, type, baseUrl, secret })
await client.providers.list()
await client.providers.models({ name })
await client.providers.test({ name })
await client.connections.upsert({ name, url, auth, hooksUrl, hookEvents })
await client.connections.test({ name })
await client.connections.jwks()
await client.connections.authorize({ connectionId, identity, redirectUri, sessionId, scopes })
await client.connections.grants.list({ identity })
await client.connections.grants.revoke({ id })
await client.connections.grants.import({ connectionId, mode, grants })
await client.secrets.upsert({ secret: { name, value, description } })
await client.secrets.list()
await client.secrets.delete({ name })
await client.hooks.stats({ since })
await client.hooks.deliveries({ status, connectionId, sessionId, since, cursor })
await client.hooks.redeliver({ id })
await client.hooks.redeliverAll({ status: "dead" })
await client.hooks.discard({ id })
await client.sandboxes.list({ sessionId })
await client.sandboxes.get({ id })
await client.sandboxes.snapshot({ id })
await client.sandboxes.resume({ id })
await client.sandboxes.release({ id })
await client.previews.mint({ sessionId, port, identity })
await client.previews.list({ sessionId })
await client.previews.revoke({ id })
await client.blobs.upload({ filename, contentType, data, sessionId })
await client.traces.forSession({ id, includeDescendants })
await client.usage.environment({ period, groupBy })
await client.usage.keys({ period })
await client.usage.session({ id })
await client.apiKeys.create({ key })
await client.apiKeys.list()
await client.apiKeys.update({ id, spend })
await client.apiKeys.revoke({ id })
await client.environments.list()
await client.environments.create({ slug, kind, group })
await client.environments.update({ slug, spend, retention })
await client.environments.archive({ slug })
await client.organizations.list()
await client.channels.issueToken({ kind: "dev", name: "jordan" })
await client.close()The complete list, with every field and every error, is the generated reference.
Streaming, server-side
const controller = new AbortController()
for await (const event of client.sessions.stream(session.id, {
after: 12,
signal: controller.signal
})) {
console.log(event.type)
}For a browser, mint a token and use @plane/sdk/browser.
Results and errors
Results are plain JSON: timestamps are ISO strings, ids are plain strings, and absent
optional fields are absent rather than undefined.
Write-only secrets — providers.upsert, a bearer connection, the tokens on
grants.import — are passed as plain strings and wrapped for the wire internally.
Omitting a provider's secret on an upsert keeps whatever Plane already stores, which
is what makes a deploy script idempotent without holding the credential.
Typed failures throw PlaneError subclasses carrying the wire tag and the server's
fields under data:
AgentNotFoundError, AgentNotReadyError, AgentNotPromotableError,
SessionNotFoundError, EventNotFoundError, VersionConflictError,
ConnectionNotFoundError, ConnectionNameTakenError, McpConnectionError,
ProviderNotFoundError, ProviderNameTakenError, ProviderInUseError,
ProviderRequestError, OverrideNotAllowedError, IdentityRequiredError,
IdentityNotSwitchableError, IdentityScopeNotAllowedError,
IdentityAttributesTooLargeError, GrantNotFoundError, RedirectUriNotAllowedError,
OAuthFlowError, MetadataTooLargeError, SpendLimitExceededError,
SpendCapExceedsParentError, PromotionUnresolvedError, DecisionNotPendingError,
DecisionExpiredError.
A tag this version does not know still arrives as a PlaneError with that tag, so a
contract addition does not break an old SDK. Transport and validation problems are
PlaneTransportError and PlaneValidationError.