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/chatwhenagentSlugis setPOST /v1/routers/:slug/chatwhen onlyrouterSlugis setPOST /v1/chatwhenagentIdis set (with optionalrouterSlugin 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