SDK first query
Install the typed SDK, create a client for your pod and read your first entities.
@synap-core/sdk is a typed tRPC client for a Synap pod. Every procedure the pod exposes is available with
autocomplete and compile-time checks, because the client is typed with the pod's own router type.
The SDK calls the pod's /trpc endpoint, which accepts only a sign-in session: a person's session cookie in the
browser, or their session token anywhere else. It is for apps that act as a signed-in person. It does not take API
keys: for a script, an integration or an agent that holds an API key, use the
Hub REST API instead.
What you need
- A pod: hosted or self-hosted.
- An account on it you can sign in with.
- The ID of the workspace you want to read.
1. Install
npm install @synap-core/sdkFor a React app, install the React bindings instead; they include the SDK:
npm install @synap-core/react2. Sign in and create a client
In the browser, on a subdomain of the pod's domain (for example app.example.com next to pod.example.com) where
the person is already signed in, pass no credential: every request is sent with credentials: "include", so the
browser's session cookie authenticates it.
import { createSynapClient } from "@synap-core/sdk";
const synap = createSynapClient({
podUrl: "https://pod.example.com", // no trailing slash, no /trpc
workspaceId,
});Anywhere else (a server, a script, a desktop or mobile app), sign in once to get a session token, and pass it as
sessionToken. A pod with password sign-in issues one through the native login flow of its sign-in service:
FLOW=$(curl -fsS https://pod.example.com/.ory/kratos/public/self-service/login/api | jq -r .id)
curl -fsS -X POST "https://pod.example.com/.ory/kratos/public/self-service/login?flow=$FLOW" \
-H "Content-Type: application/json" \
-d '{"method": "password", "identifier": "you@example.com", "password": "<your password>"}' \
| jq -r .session_tokenconst synap = createSynapClient({
podUrl: "https://pod.example.com",
sessionToken: process.env.SYNAP_SESSION_TOKEN,
workspaceId: process.env.SYNAP_WORKSPACE_ID!,
onAuthError: () => {
// 401: the session expired or was revoked. Sign in again.
},
});A session lasts 30 days and is not extended by use; treat the token like a password.
| Option | Meaning |
|---|---|
podUrl | Base URL of the pod. The client calls <podUrl>/trpc. |
sessionToken | A sign-in session token, sent as x-session-token. Leave it out to use the browser's session cookie. |
workspaceId | Sent as x-workspace-id to scope requests. Pass "" for procedures that need no workspace. |
extraHeaders | Added to every request. |
onAuthError | Called on 401. The client then rejects with a clean tRPC error instead of a parse error. |
The type also has an apiKey option. It is only for the desktop app's local pod; a pod refuses API keys
(synap_user_…, synap_hub_…) on /trpc with 401.
Calls made in the same tick are batched into one HTTP request, and values are encoded with superjson, which is why
plain HTTP clients should use the Hub REST API instead of /trpc.
3. Read entities
const { items, pagination } = await synap.entities.list.query({ limit: 20 });
for (const entity of items) {
console.log(entity.id, entity.title);
}entities.list takes limit (1 to 1000, default 50) and offset, and filters such as profileSlug (one kind, for
example "task") and facetSlug (one role). With a workspace set, it returns that workspace's entities.
4. Write, and expect proposals
const result = await synap.entities.create.mutate({
profileSlug: "note",
title: "My first note",
});Writes go through the pod's governance. Depending on who the key belongs to and the rules you set, a write is applied
directly or comes back as a proposal (status: "proposed" with a proposalId) that a person approves. Handle
both. See Validation policy.
React
import { SynapProvider, useSynap } from "@synap-core/react";
export function App() {
return (
<SynapProvider podUrl="https://pod.example.com" workspaceId={workspaceId}>
<Entities />
</SynapProvider>
);
}
function Entities() {
const { data } = useSynap.entities.list.useQuery({ limit: 20 });
return <ul>{data?.items.map((e) => <li key={e.id}>{e.title}</li>)}</ul>;
}useSynap is the tRPC React client itself, not a function: write useSynap.entities.list.useQuery(…), not
useSynap(). SynapProvider takes the same options as createSynapClient (here, the signed-in person's session
cookie; pass sessionToken outside the browser), plus an optional queryClient to share an existing TanStack Query
client.
@synap-core/react also exports ready-made RouterInputs and RouterOutputs types, useSynapContext (the provider's
podUrl and workspaceId), and the hooks useEntity, useEntities, useLinks and useView.
Package reference
| Package | Exports |
|---|---|
@synap-core/sdk | createSynapClient, and the types SynapClient, SynapClientOptions and AppRouter |
@synap-core/react | SynapProvider, useSynap, useSynapContext, useEntity, useEntities, useLinks, useView, and the types RouterInputs, RouterOutputs, SynapProviderProps, AppRouter |
Both packages ship ES modules and CommonJS with TypeScript declarations, under the MIT license. The router types
come from @synap-core/api-types, a dependency, so the client's types follow the version of that package you install.
Use only what the package root exports: other subpaths are not part of the published API.
Scripts and agents with an API key
An API key works on the Hub REST API under /api/hub, not on /trpc. From TypeScript,
@synap-core/hub-rest-client is a zero-dependency
client for it:
npm install @synap-core/hub-rest-clientimport { HubRestClient } from "@synap-core/hub-rest-client";
const hub = new HubRestClient({
podUrl: "https://pod.example.com",
apiKey: process.env.SYNAP_API_KEY!,
workspaceId: process.env.SYNAP_WORKSPACE_ID,
});
const entities = await hub.getRecentEntities({ profileSlug: "task", limit: 20 });From any other language, call the same routes over HTTP with Authorization: Bearer <key>:
curl "https://pod.example.com/api/hub/entities?profileSlug=task&limit=20" \
-H "Authorization: Bearer $SYNAP_API_KEY"Every route is in the Hub REST API reference.
