ray.run: API and JavaScript SDK
npm install @rayrun/sdk

import { Rayrun } from '@rayrun/sdk';

const rayrun = new Rayrun({
  apiKey: process.env.RAYRUN_API_KEY,
});

const identity = await rayrun.identity.get();
console.log(identity.workspace, identity.scopes);

const { connection } = await rayrun.connections.create({
  kind: 'mcp',
  name: 'Linear',
  url: 'https://mcp.linear.app/mcp',
});

await rayrun.connections.setPolicy(connection.uid, 'ask-before-changes');
const { uid } = await rayrun.externalPrincipals.upsert({
  externalId: 'customer_123',
  metadata: { plan: 'pro' },
});

const session = await rayrun.externalPrincipals.createSession(uid, {
  runtimeContext: { accountId: 'customer_123', plan: 'pro' },
});

// Configure this user’s agent with only these short-lived values.
console.log(session.endpoint, session.sessionToken);

// When the user must authorize GitHub, Slack, Linear, or another connection:
const link = await rayrun.externalPrincipals.createConnectLink(uid, {
  connectionId,
});
console.log(link.url);
import { readFile } from 'node:fs/promises';
import { setTimeout as delay } from 'node:timers/promises';
import { Rayrun } from '@rayrun/sdk';

const apiKey = process.env.RAYRUN_API_KEY;
const manifest = JSON.parse(await readFile('./rayrun.json', 'utf8'));
const sourceProjectId = manifest.projectId;
const workspaceSecretUid = process.env.RAYRUN_INVOICE_SECRET_UID;
if (!apiKey || !sourceProjectId || !workspaceSecretUid) {
  throw new Error('Deployment credentials and source project ID are required.');
}
if (!/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/iu.test(sourceProjectId)) {
  throw new Error('rayrun.json projectId must be a UUID.');
}

const rayrun = new Rayrun({ apiKey });
const files = JSON.parse(await readFile('./source-files.json', 'utf8'));

const validation = await rayrun.deployments.validate(files);
const queued = await rayrun.deployments.deploySourceProject(sourceProjectId, {
  displayName: validation.name,
  files,
  secretBindings: {
    INVOICE_API_TOKEN: workspaceSecretUid,
  },
});

let deployment = await rayrun.deployments.get(queued.buildUid);
const timeoutMs = Number(process.env.RAYRUN_DEPLOY_TIMEOUT_MS ?? 60 * 60_000);
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error('Invalid deployment timeout.');
const deadline = Date.now() + timeoutMs;
while (
  ['queued', 'running'].includes(deployment.build.status) ||
  (deployment.build.status === 'succeeded' &&
    (!deployment.release || ['pending', 'verifying'].includes(deployment.release.status)))
) {
  if (Date.now() >= deadline) throw new Error('Deployment timed out.');
  await delay(2_000);
  deployment = await rayrun.deployments.get(queued.buildUid);
}

if (deployment.build.status !== 'succeeded' || deployment.release?.status !== 'active') {
  throw new Error(
    deployment.release?.errorMessage ?? deployment.build.errorMessage ?? 'Deployment failed.',
  );
}

const buildLog = await rayrun.deployments.getLog(queued.buildUid);
const releases = await rayrun.deployments.listReleases(queued.connectionId);
const { hook } = await rayrun.hooks.get(connectionId, toolUid);

const tested = await rayrun.hooks.test(connectionId, toolUid, {
  source: hook.draftSource,
  config: hook.draftConfig,
  arguments: { query: 'release' },
  mockResult: { items: [] },
});

const expectedBehavior =
  tested.before.status === 'completed' &&
  tested.before.outcome.action === 'continue' &&
  tested.after?.status === 'completed' &&
  tested.after.outcome.action === 'return';

if (!expectedBehavior) {
  throw new Error('The hosted hook did not produce the expected test outcomes.');
}

const saved = await rayrun.hooks.saveDraft(connectionId, toolUid, {
  source: hook.draftSource,
  config: hook.draftConfig,
  expectedVersion: hook.version,
  reason: 'Normalize upstream release results',
});

const history = await rayrun.hooks.listDraftVersions(connectionId, toolUid, { limit: 25 });
await rayrun.hooks.getDraftVersion(connectionId, toolUid, history.items[0].uid);

await rayrun.hooks.deploy(connectionId, toolUid, {
  expectedVersion: saved.hook.version,
  mode: 'shadow',
});
curl https://ray.run/v1/connections \
  --header "Authorization: Bearer $RAYRUN_API_KEY"
import { Rayrun, verifyWebhookSignature } from '@rayrun/sdk';

const rayrun = new Rayrun({ apiKey: process.env.RAYRUN_API_KEY });
const { secret, uid: webhookUid } = await rayrun.webhooks.create({
    batchMaxEvents: 100,
    deliveryMode: 'batch',
    eventTypes: ['activity.call'],
    forwardToolPayloads: true,
    url: 'https://example.com/rayrun-events',
});

const webhook = (await rayrun.webhooks.list()).items.find(({ uid }) => uid === webhookUid);
if (!webhook) throw new Error('Webhook was not found after creation.');
const updated = await rayrun.webhooks.update(webhookUid, {
  enabled: false,
  expectedVersion: webhook.configurationVersion,
  reason: 'Pause during receiver maintenance',
});

const history = await rayrun.webhooks.listConfigurationVersions(webhookUid, { limit: 25 });
await rayrun.webhooks.restoreConfigurationVersion(webhookUid, history.items.at(-1).uid, {
  expectedVersion: updated.configurationVersion,
  reason: 'Restore the reviewed destination',
});

const valid = verifyWebhookSignature({
  body: rawBody,
  secret,
  signature: request.headers['rayrun-signature'],
});