Skip to content
DocsAPI ReferenceJavaScript Client

JavaScript Client

Typed JS/TS client for the Struere HTTP API

The struere npm package ships a typed JavaScript client under the struere/client subpath. It wraps the Chat API and Data API with end-to-end TypeScript types, throws structured errors, and runs in browsers, Node 18+, Bun, and Deno with zero runtime dependencies.

Installation

bun add struere
import { StruereClient, StruereApiError } from 'struere/client'

The struere/client subpath has no dependency on the Node-only definition primitives (defineAgent, defineData, etc.), so it is safe to import from browser bundles.

Constructor

const struere = new StruereClient({
  apiKey: process.env.STRUERE_API_KEY!,
})
Option Type Default Description
apiKey string API key for the target environment. Required
baseUrl string 'https://api.struere.dev' Override the API base URL
fetch typeof globalThis.fetch globalThis.fetch Custom fetch implementation for tests or runtimes without a global fetch

The client throws synchronously on construction if apiKey is missing or if no fetch is available.

Works In

  • Browsers — CORS-enabled (Access-Control-Allow-Origin: *) on Chat and Data endpoints, no proxy required
  • Node 18+ — uses the global fetch
  • Bun — uses the global fetch
  • Deno — uses the global fetch

Chat

const reply = await struere.chat({
  agentSlug: 'support',
  message: 'Hi!',
})

console.log(reply.threadId, reply.message, reply.usage)

client.chat(request)

interface ChatRequest {
  agentSlug?: string
  agentId?: string
  routerSlug?: string
  message: string
  threadId?: string
  externalThreadId?: string
  threadContext?: { params?: Record<string, unknown> }
}

Exactly one of agentSlug, agentId, or routerSlug is required. The client routes to:

  • POST /v1/agents/:slug/chat when agentSlug is set
  • POST /v1/routers/:slug/chat when only routerSlug is set
  • POST /v1/chat when agentId is set (with optional routerSlug in the body)
interface ChatResponse {
  threadId: string
  message: string
  assistantMessageId?: string
  usage: {
    inputTokens: number
    outputTokens: number
    totalTokens: number
    reasoningTokens?: number
  }
  _executionMeta?: {
    iterationCount: number
    model: string
    durationMs: number
    toolCallSummary: Array<{ name: string; durationMs: number; status: string; errorType?: string; errorMessage?: string }>
    errorCount: number
    permissionDenialCount: number
  }
  _transferred?: { targetAgentSlug: string; targetAgent: string }
}

Pass threadId to continue an existing conversation, or externalThreadId to deduplicate by an upstream identifier.

Data

The client.data namespace exposes typed CRUD and query operations against the Data API. All methods return promises and throw StruereApiError on non-2xx responses.

client.data.entityTypes()

Lists all entity types in the current environment.

const { data } = await struere.data.entityTypes()
data.forEach((t) => console.log(t.slug, t.name))

client.data.list(type, options?)

Paginated list of entities for a given type.

const players = await struere.data.list<{ name: string }>('player', { limit: 50 })
console.log(players.data, players.cursor, players.hasMore)
Option Type Description
limit number Page size
cursor string Pagination cursor returned by a previous call
status string Filter by entity status

client.data.get(type, id)

Fetch a single entity by ID.

const player = await struere.data.get<{ name: string }>('player', 'e57abc123')

client.data.create(type, data, options?)

Create a new entity.

const created = await struere.data.create('player', { name: 'Mia', team: 'red' })

client.data.update(type, id, patch, options?)

Patch an existing entity. Only the fields you pass are updated.

const updated = await struere.data.update('player', 'e57abc123', { team: 'blue' })

client.data.delete(type, id)

Delete an entity by ID.

await struere.data.delete('player', 'e57abc123')

client.data.query(type, options?)

Filtered query with pagination.

const matches = await struere.data.query('player', {
  filters: { team: { $eq: 'red' }, score: { $gt: 100 } },
  limit: 25,
})
Option Type Description
filters Record<string, FilterValue> Field filters (see operators below)
status string Filter by entity status
limit number Page size
cursor string Pagination cursor

Filter Operators

A FilterValue is either a literal value (implicit $eq) or one of:

Operator Type Description
$eq unknown Field equals value
$neq unknown Field does not equal value
$in unknown[] Field value is one of the array members
$contains unknown Field contains substring or array element
$gt unknown Greater than
$gte unknown Greater than or equal
$lt unknown Less than
$lte unknown Less than or equal
$exists boolean Field is present (true) or absent (false)
const active = await struere.data.query('player', {
  filters: {
    team: { $in: ['red', 'blue'] },
    nickname: { $contains: 'fox' },
    retiredAt: { $exists: false },
  },
})

client.data.search(type, options)

Full-text search across the entity type's searchFields.

const results = await struere.data.search<{ name: string }>('player', {
  query: 'mia',
  limit: 10,
})

Errors

All client methods throw StruereApiError on non-2xx HTTP responses.

import { StruereApiError } from 'struere/client'

try {
  await struere.data.get('player', 'missing')
} catch (err) {
  if (err instanceof StruereApiError) {
    console.error(err.status, err.message, err.code, err.requestId)
  } else {
    throw err
  }
}
Field Type Description
status number HTTP status code
message string Error message from the server, or HTTP <status> if none
code string? Machine-readable error code, when provided
requestId string? Request identifier for correlating with server logs
details string? Additional human-readable detail
body unknown? Parsed response body

Browser Example: Vite + React

A minimal Vite component that lists entities and renders them. Requires a development API key created via bunx struere keys create --env development and stored in VITE_STRUERE_API_KEY.

import { useEffect, useState } from 'react'
import { StruereClient, StruereApiError } from 'struere/client'

const struere = new StruereClient({
  apiKey: import.meta.env.VITE_STRUERE_API_KEY,
})

interface Player {
  name: string
  team: string
}

export function PlayerList() {
  const [players, setPlayers] = useState<Player[]>([])
  const [error, setError] = useState<string | null>(null)

  useEffect(() => {
    struere.data
      .list<Player>('player', { limit: 25 })
      .then((res) => setPlayers(res.data.map((e) => e.data)))
      .catch((err) => {
        if (err instanceof StruereApiError) {
          setError(`${err.status}: ${err.message}`)
        } else {
          setError(String(err))
        }
      })
  }, [])

  if (error) return <p>Error: {error}</p>

  return (
    <ul>
      {players.map((p, i) => (
        <li key={i}>{p.name} ({p.team})</li>
      ))}
    </ul>
  )
}

For chat from a browser, swap data.list for chat():

const reply = await struere.chat({
  agentSlug: 'support',
  message: 'Hi!',
})

See Also

  • API Overview — Authentication, base URL, CORS
  • Chat API — Raw HTTP request/response for chat
  • Data API — Raw HTTP request/response for entity CRUD
  • struere keys — Create the API key the client uses