Integrations and API surface
Plans never reach the network directly. They use integrations: declared, typed operations the runtime executes.
Declaring an integration
const payments = a.integration("payments", {
provider: "stripe",
config: { secret: "STRIPE_SECRET_KEY" }, // names of secrets, never values
operations: {
charge: {
mode: "call", // returns data during the plan
input: { orderId: t.id(), amountCents: t.int().min(1) },
output: { chargeId: t.string().max(100) },
},
refund: { mode: "effect", input: { chargeId: t.string().max(100) } }, // sent after commit
},
});
const charge = payments.call("charge", { orderId, amountCents: 2500 }); // charge.chargeId
payments.send("refund", { chargeId });callruns during the plan, and its output is validated againstoutput. Calls are journaled: a re-run after a conflict reuses the result. A route can make at most 3 calls.effectis fire-and-forget, delivered through the outbox after commit, with retries.
Receiving webhooks
import { verify } from "@aleph-lang/sdk";
a.webhook("POST /webhooks/stripe", {
verify: verify.stripe("STRIPE_WEBHOOK_SECRET"),
dedupe: { bodyField: "id", ttl: "3d" },
})
.body({ id: t.string().max(64), type: t.string().max(64) })
.handle(($, { body }) => {
$.log("stripe event", body.type);
return { received: true };
});- Verification first. The signature is checked on the exact raw body, in constant time, before the plan runs. A bad, missing, or stale signature returns
401 invalid_signature. - Lenient bodies. Declare only the fields you use; senders add fields over time, and undeclared ones are ignored.
- Dedupe. With
dedupe, a repeated delivery of the same event id gets the first response replayed, so retries from the sender are harmless.
| Scheme | Use |
|---|---|
verify.stripe(secret) | Stripe-Signature, 5-minute tolerance |
verify.github(secret) | X-Hub-Signature-256 |
verify.hmacSha256({ secret, header, prefix?, encoding?, timestampHeader?, tolerance? }) | Any HMAC-SHA256 sender |
In development, pass secrets with aleph dev --secret STRIPE_WEBHOOK_SECRET=whsec_....
OpenAPI, MCP, and typed clients
Every route's inputs are declared, and its response type is inferred (or declared with .returns), so Aleph can describe your API precisely.
A typed fetch client
npx aleph client app.ts -o src/client.tsimport { AlephError, createClient, type Routes } from "./client";
const api = createClient({
baseUrl: "https://api.example.com",
headers: async () => ({ authorization: `Bearer ${await getToken()}` }),
});
const order = await api.postOrders({ items: [{ sku: "BK-1", qty: 2 }], shipTo: { city: "Paris", zip: "75001" } });
order.total; // Decimal: `${number}`
try {
await api.postOrdersOrderIdPay({ orderId: order.orderId }, { idempotencyKey: crypto.randomUUID() });
} catch (e) {
if (e instanceof AlephError && e.body.error === "missing") { /* typed declared errors */ }
}
type OrderList = Routes["myOrders"]["output"];- Functions: one per route, named after the route id. Params, query, and body are merged into one input object.
- Results: precise types, including nullability, decimals as strings, enums as unions, and union types for routes with several
$.returns. - Errors: an
AlephErrorwhose body is typed by the route's declared errors, including theirdetails.
Commit the generated file and regenerate it in CI. Changing a route then breaks the code that uses it at compile time. The example apps include a test that fails if src/client.ts is stale.
OpenAPI
npx aleph openapi app.ts > openapi.jsonAn OpenAPI 3.1 document with:
- Inputs: parameter and body schemas.
- Responses: response schemas, success statuses, and typed error bodies (with
details). - Metadata: deprecation, and
x-aleph-*extensions for idempotency, webhooks, and examples.
MCP tools for agents
a.route("GET /orgs/:orgId/members/by-email/:email")
.params({ orgId: t.string().max(64), email: t.string().min(3).max(200) })
.member("orgId", { apiKeyScopes: ["members:read"] })
.describe("Find a member by email")
.mcp("find_member")
.errors({ notFound: err.notFound() })
.handle(($, { params, errors }) => {
const member = Member.findBy("byEmail", [params.orgId, params.email.lower()]);
$.ensure(member.exists(), errors.notFound);
return member;
});npx aleph mcp app.tsThis lists the routes exposed as MCP tools, with JSON Schema inputs. A tool call goes through the route, with the same auth, validation, and limits. That's what makes it safe to hand an agent: whatever it asks for, its usage is bounded.