Scrydon
Client SDK

TypeScript / JavaScript SDK

The official TypeScript / JavaScript client SDK for Scrydon — OAuth/PKCE auth and a typed surface for workflows, knowledge, storage, chat, and webhooks

@scrydon/sdk is the official client SDK for browser, TanStack Start / Next.js, and Node.js apps. It authenticates end-users through OAuth 2.0 with PKCE and exposes typed modules for workflows, chat, knowledge, storage, and webhooks, plus the platform data / ai / action SDK surface.

This SDK is for first-party apps acting on behalf of a user (OAuth flow). For server-to-server execution with a static API key — backends, cron jobs, CI — call the workflow API directly with x-api-key (see Execution).

Installation

bun add @scrydon/sdk

Quick Start

import { ScrydonClient } from '@scrydon/sdk'

const client = new ScrydonClient({
  baseUrl: 'https://scrydon.com',
  clientId: 'your-oauth-client-id',
  redirectUri: 'https://your-app.com/auth/callback',
})

// 1. Start the OAuth/PKCE flow — returns the authorization URL
const authUrl = await client.auth.signIn()
window.location.assign(authUrl)

// 2. On your callback page, exchange the code for a session
const session = await client.auth.handleCallback() // reads window.location by default

// 3. Call typed APIs
const result = await client.workflows.trigger({
  workflowId: 'wf_…',
  inputs: { message: 'Hello, world!' },
})
console.log(result.executionId, result.status, result.outputs)

Authentication — OAuth 2.0 + PKCE

The SDK uses the PKCE flow so it's safe in browsers without a client secret. The verifier is held in sessionStorage (browser) or in-memory (other runtimes).

auth.signIn()

const authUrl = await client.auth.signIn()
// Redirect the user to authUrl. After consent they come back to redirectUri
// with ?code=… and ?state=… in the query string.

auth.handleCallback(callbackUrl?)

// On the callback route — defaults to window.location.href
const session = await client.auth.handleCallback()
// session: { user: { id, email, name? }, accessToken, expiresAt }

auth.getSession()

const session = await client.auth.getSession() // null if signed out / expired

auth.signOut()

await client.auth.signOut()

auth.onAuthStateChange(cb)

const unsubscribe = client.auth.onAuthStateChange((session) => {
  if (session) {
    console.log('signed in as', session.user.email)
  } else {
    console.log('signed out')
  }
})
// Later: unsubscribe()

client.workflows

trigger(options) — synchronous

const result = await client.workflows.trigger({
  workflowId: 'wf_abc',
  inputs: { message: 'Hello' },
})
// result: { executionId, status, outputs? }

POST /api/v1/workflows/{workflowId}/trigger. Best for short-running workflows that respond inline.

triggerAsync(options) — fire and poll

const { executionId } = await client.workflows.triggerAsync({
  workflowId: 'wf_abc',
  inputs: { message: 'Long task' },
})

// Poll for status when you're ready
const result = await client.workflows.getStatus(executionId)
// result: { executionId, status: 'running' | 'completed' | 'failed', outputs? }

POST /api/v1/workflows/{workflowId}/trigger?async=true. Use for long-running workflows.

getStatus(executionId)

const result = await client.workflows.getStatus(executionId)

GET /api/v1/workflows/executions/{executionId}.

client.chat

Streaming chat against a deployed chat surface.

for await (const chunk of client.chat.stream({
  deploymentId: 'chat_abc',
  message: 'Summarize Q3',
})) {
  process.stdout.write(chunk)
}

client.knowledge

const hits = await client.knowledge.query({
  question: 'What is our refund policy?',
  topK: 5,
})
// hits: Array<{ content, source, score }>

await client.knowledge.ingest({
  documents: [file1, file2],
  collection: 'policies',
})

client.ontology

Map-ready geo layers projected from your ontology — one layer per object type whose instances carry coordinates (latitude/lat, longitude/lng/lon). Omit all options to discover every mappable type across your readable ontologies; pass objectTypes to narrow.

const layers = await client.ontology.queryGeo({
  objectTypes: ['Aircraft', 'MilitaryBase'], // optional — omit to discover
  ontologySlug: 'my-domain', // optional — omit to search all
  limit: 300, // max instances per type (1–1000)
})
// layers: Array<{ objectTypeSlug, label, features }>
// features: Array<{ id, lat, lng, label, properties }>

Geo is a dedicated projection rather than a generic instance query: coordinate extraction, range validation, and binding traversal run server-side, so the client receives render-ready features.

client.storage

const { url } = await client.storage.upload(file, { path: 'reports/q3.pdf' })

const directLink = await client.storage.getUrl('reports/q3.pdf')

client.webhooks

A simple in-process event emitter for delivering webhook payloads to subscribers in your app.

const off = client.webhooks.on('workflow.completed', (data) => {
  console.log('workflow completed', data)
})
// Later: off()

Platform SDK — client.data, client.ai, client.action

The client exposes the platform's typed ScrydonSDK:

  • client.data.knowledge.search(...), client.data.memex.query(...), client.data.storage.read(...), client.data.memory.add(...) — read / write the workspace data surface
  • client.ai.llm.complete(...) — capability-resolved LLM calls
  • client.action.workflow.execute(...), client.action.email.send(...), client.action.sms.send(...), client.action.functionExecute(...) — server-side actions

See @scrydon/sdk/types for the full set of platform types.

Discovering what a capability offers

client.integrations.capabilities.list(...) returns every provider your organization has enabled for a capability, each with its selectable models:

const llm = await client.integrations.capabilities.list({ capability: "llm" });

llm.category; // "model" — selection descends to a model
llm.providers; // [{ productId, vendorId, name, models: [...], defaultModel? }]

Models come back already merged: a vendor that declares a fixed catalogue and one that discovers deployments at runtime are indistinguishable in the response, so you never branch on which kind you are talking to.

The category tells you how the capability is chosen:

CategoryMeaningmodels
modelpick a provider and a model (LLM, embeddings, speech, OCR, image, video, moderation)present
generalpick a provider; there is nothing below it (web search)absent

Pass applyAllowlist: false to see the full set before your organization's model allowlist is applied — useful when building a UI for editing that allowlist. Pass refresh: true to bypass the discovery cache.

This differs from client.integrations.capabilities.resolve(...), which returns the single target that would serve the capability right now. Use resolve to learn what will run; use list to offer a choice.

Errors

import {
  ScrydonAuthError,
  ScrydonForbiddenError,
  ScrydonRateLimitError,
  ScrydonValidationError,
  ScrydonServerError,
  ScrydonError,
} from '@scrydon/sdk'

try {
  await client.workflows.trigger({ workflowId: 'wf_abc' })
} catch (err) {
  if (err instanceof ScrydonAuthError) {
    // 401 — token expired or invalid; redirect to signIn()
  } else if (err instanceof ScrydonForbiddenError) {
    // 403 — user lacks permission for this workflow
  } else if (err instanceof ScrydonRateLimitError) {
    // 429 — err.retryAfter seconds
  } else if (err instanceof ScrydonValidationError) {
    // 400 — err.details has the Zod issues
  } else if (err instanceof ScrydonServerError) {
    // 5xx — retry with backoff
  } else if (err instanceof ScrydonError) {
    // Other 4xx — err.code, err.status
  }
}

React

@scrydon/sdk/react ships hooks for the common flows:

import { ScrydonProvider, useAuth, useWorkflow } from '@scrydon/sdk/react'

function App() {
  return (
    <ScrydonProvider config={{ baseUrl, clientId, redirectUri }}>
      <Page />
    </ScrydonProvider>
  )
}

function Page() {
  const { session, signIn, signOut } = useAuth()
  const { trigger, result, loading, error } = useWorkflow('wf_abc')

  if (!session) return <button onClick={signIn}>Sign in</button>

  return (
    <div>
      <button onClick={() => trigger({ message: 'Hello' })} disabled={loading}>
        Run workflow
      </button>
      {result && <pre>{JSON.stringify(result.outputs, null, 2)}</pre>}
    </div>
  )
}

TanStack Start / Next.js callback route

// /auth/callback page
import { ScrydonClient } from '@scrydon/sdk'

const client = new ScrydonClient({
  baseUrl: import.meta.env.VITE_SCRYDON_BASE_URL,
  clientId: import.meta.env.VITE_SCRYDON_CLIENT_ID,
  redirectUri: `${window.location.origin}/auth/callback`,
})

export function CallbackPage() {
  useEffect(() => {
    client.auth
      .handleCallback()
      .then(() => router.navigate({ to: '/' }))
      .catch((err) => console.error('OAuth callback failed', err))
  }, [])
  return <p>Signing you in…</p>
}

Backend / server-to-server

@scrydon/sdk is built around an end-user OAuth flow. For backend processes that need to execute workflows on behalf of the platform (cron, CI, internal services), call the API directly:

curl -X POST "https://scrydon.com/api/workflows/{workflowId}/execute" \
  -H "x-api-key: $SCRYDON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": "…"}'

API keys are issued during the deploy flow (see Execution). They are the right tool for server contexts where there is no end user.

Requirements

  • Node.js 18+ (for fetch + atob globals)
  • TypeScript 5.0+ for full types
  • A browser-like environment for the React module

License

Apache-2.0

On this page

On this page