semesh-sdk
v0.4.0
Published
Add Semesh login, database and payments to any app — any framework, hosted anywhere.
Maintainers
Readme
semesh-sdk
Add Semesh login, database, and payments to any app — any framework, hosted anywhere.
A thin, dependency-free client for the Semesh platform: OAuth/PKCE login, a delegated-spend runtime, hosted Postgres access, and checkout/payments — all against one Semesh API key.
Install
npm install semesh-sdkRequires Node ≥ 18.
Entry points
| Import | What it gives you |
|---|---|
| semesh-sdk | createClient(...) — the top-level client |
| semesh-sdk/auth | SettleAuth — OAuth/PKCE login + cookie helpers |
| semesh-sdk/db | hosted Postgres (db.query, db.migrate, …) |
| semesh-sdk/payments | SettlePayments — checkout + webhook verification |
| semesh-sdk/runtime | SettleRuntime — canonical Unit Action runtime + verifyInvocation |
Quick start
import { createClient } from "semesh-sdk";
const settle = createClient({ baseUrl: "https://www.semesh.io", clientId: process.env.SEMESH_CLIENT_ID });Invoke a canonical Unit Action
Use the runtime key only on your backend. The SDK resolves the current server detail, pins its exact
UnitActionRef and catalog runtime generation, quotes the exact input snapshot, sends one invoke POST,
and then reads that same attempt with GET:
import { createClient } from "semesh-sdk";
const settle = createClient({
baseUrl: "https://api.semesh.net",
apiKey: process.env.SEMESH_APP_API_KEY,
});
// Natural-language discovery is one bounded GET against the current public
// server Catalog. Groups are navigation-only; expand one when search does not
// directly return the concrete Unit you need.
const catalog = await settle.runtime.searchServiceCatalog("search the current service catalog", {
limit: 10,
});
let unit = catalog.data.find((result) => result.kind === "unit");
const group = catalog.data.find((result) => result.kind === "group");
if (!unit && group) {
const expanded = await settle.runtime.describeServiceGroup(group.id);
unit = expanded.data.units.find((member) => member.available);
}
if (!unit) throw new Error("No matching Service Unit");
// Search and Group summaries only identify the Unit. Read its bounded safe
// Action index, choose one exact Action ID, then refresh that Action through the
// existing canonical detail rail before quote/invoke.
const unitIndex = await settle.runtime.describeServiceUnit(unit.id);
const action = unitIndex.actions.find(
(item) =>
item.disclosure.availability.state === "available" &&
item.disclosure.availability.callable
);
if (!action) throw new Error("No available Service Unit Action");
const actionId = action.unitActionRef.action_id;
const description = await settle.runtime.describeUnitAction(unit.id, {
actionId,
});
// Quote resolves the same canonical detail and sends one zero-effect quote
// POST. It never invokes the Action or retries that POST.
const quoted = await settle.runtime.quoteUnitAction(
unit.id,
{ query: "current catalog" },
{ actionId }
);
console.log(
quoted.unitActionRef,
quoted.catalogPin,
quoted.effectClass,
quoted.confirmationRequired,
quoted.price
);
const attemptId = order.id; // stable identity for this logical operation
const invocation = await settle.runtime.invokeUnitAction(
unit.id,
{ query: "current catalog" },
{
actionId,
idempotencyKey: attemptId,
payer: userAccessToken,
}
);
if (invocation.state !== "terminal") {
const latest = await settle.runtime.observeUnitAction(
invocation.unitActionRef,
invocation.invokeAttemptId,
{ payer: userAccessToken }
);
console.log(latest.state, latest.result);
}If the invoke response is lost or malformed, the SDK never sends that POST again. It performs only the
caller-scoped observation GET for the same idempotencyKey; keep that key and use
observeUnitAction(...) later. A stale or mismatched detail/quote, ambiguous Action selection, missing
confirmation, or oversized/malformed input fails before the effect-bearing invoke POST.
searchServiceCatalog(...) defaults to the anonymous public server-authored Catalog and returns its
detached data, notices, and meta projection. In default or explicit public scope it omits
ambient credentials and never sends the runtime key; every scope remains discovery-only and never
quotes, invokes, or follows redirects. A Group is never callable, and Action summaries in a search
result are discovery hints rather than execution
authority; pass a selected Unit ID to describeServiceUnit(...), choose one exact Action ID, and
then call describeUnitAction(...) to refresh canonical Action detail.
To discover a Service Unit dynamically shared with the runtime-key owner, pass
{ scope: "accessible" } consistently to searchServiceCatalog(...),
describeServiceGroup(...), describeServiceUnit(...), and the selected Unit's describeUnitAction(...),
quoteUnitAction(...), or invokeUnitAction(...). Accessible Search, Group, and Unit detail GETs use
only the runtime bearer actor and set credentials: "omit"; payer and chain are not discovery
audience and remain confined to quote/invoke/observation. Scope is never copied onto those Action
paths, and an inaccessible or revoked detail fails without falling back to public. The default and
explicit public scope retain the existing anonymous Search/Group behavior. owned and unknown
scopes are rejected by this runtime API.
describeServiceGroup(...) defaults to anonymously expanding one exact public Group; explicit
accessible instead uses only the runtime bearer actor. Both modes use bounded GET-only retries,
omit ambient browser credentials, and return detached data and meta while preserving the server's
units[] order. The method rejects redirects and responses over 1 MiB and never falls back from a
missing Group to a legacy service route. Group/member identity drift, duplicate members,
contradictory availability, incomplete pins, or any callable/Action/execution authority field fails
closed. The returned Unit summaries remain navigation hints: select an available Unit ID, then call
describeServiceUnit(...) with the same scope before selecting an Action.
describeServiceUnit(...) performs one bounded GET against the dedicated /v1/units/{id} route and
returns a detached exact whitelist containing the Unit identity/current catalog pin plus every
server-ordered Action's exact ref, effect policy, safe disclosure, and current availability. It
includes valid unavailable or unknown Actions so an application can explain why they cannot be
selected, but it never returns pricing, routes, providers, bindings, secrets, rate cards, raw
execution methods/paths, diagnostics, or response metadata. Public scope stays anonymous;
accessible sends only the runtime bearer actor; both omit browser credentials, reject redirects
and responses over 1 MiB, and never fall back to /v1/services/{id}. Duplicate, foreign,
mixed-revision, or partially malformed Action indexes fail atomically without a partial result. The
index remains navigation-only: choose unitActionRef.action_id, then call describeUnitAction(...)
with the same scope so availability, pin, route, and effect admission are refreshed before quote or
invoke.
describeUnitAction(...) returns a detached snapshot of the exact UnitActionRef, catalog pin,
effectClass, destructive, confirmation policy, and a detached disclosure capsule advertised by
the server. The capsule contains the presentation/schema fields plus detached effect,
authorization, provider-neutral requirement booleans, execution mode/result protocol, retry,
wait, and current availability/callable state. secretRequired is only a boolean requirement
class; it does not disclose a secret name, identifier, or value. Execution methods and paths remain
internal to the SDK, and the capsule never copies pricing, routes, providers, bindings, secret
material, or rate-card data from canonical detail. quoteUnitAction(...) inherits the same capsule
and adds the detached input snapshot and digest, exact validated price, and quote reference/receipt.
Exact pricing remains authoritative only in the quote result. Retry disclosure does not authorize the
SDK to replay a POST: quote and invoke POSTs remain single-attempt, and uncertain invocation is handled
through observeUnitAction(...). Both methods fail closed on a stale or mismatched pin or hostile
route, follow no redirects, and have no invoke or provider effect. Use describeUnitAction(...) to
inspect confirmationRequired; when it is true, obtain the caller's authorization and pass
{ confirmed: true } to quoteUnitAction(...). The server rejects an unconfirmed quote with HTTP 428
before any invoke or provider effect.
runtime.invokeCapability(...) remains available as the explicit compatibility API for legacy
capability identifiers. New Service Unit integrations should use invokeUnitAction(...).
The self-describing agent contract
The agent-facing contract (agent.md) is served by the platform at GET /agent.md (and via the
semesh agent-md CLI command) — it is the single source of truth and is not shipped inside this
npm package, so it never drifts from the running API.
Security notes
- POST requests are never auto-retried by the SDK. Canonical Unit Action invoke requires a stable
idempotencyKey; uncertainty is reconciled withobserveUnitAction(...), not a blind POST replay. verifyInvocation/ webhook verification use constant-time comparison and timestamp-freshness checks.- Never put a server secret in client-side code; only
NEXT_PUBLIC_-style public values belong in a bundle.
License
MIT — see LICENSE.
