Runtime API reference

Pulse SDK and Handlers

This is the practical reference for writing Pulse backend code: which handler shape to use, what the Pulse SDK gives you, and how env.fetch, env.secrets, env.state, env.request, env.log, and env.runtime fit together.

Pattern guide for selecting durable Pulse handler conventions.

Implementation focus

Use this before adding or refactoring backend endpoints so the code is readable, searchable, and explicit about runtime authority.

Expected outcomes

Pulse SDK entrypoints

The Pulse SDK gives backend code two explicit entrypoints. Use definePulse() when the handler needs Vibecodr runtime capabilities such as env.fetch, env.secrets, env.connections, env.state, env.request, env.log, or env.runtime. Use defineWebPulse() when the endpoint is plain HTTP and should work like a standard Request/Response handler.

env.runtime stays the safe runtime metadata lane. When a Pulse needs to inspect what Vibecodr exposed to it, use env.runtime.capabilities() instead of probing raw env fields or private runtime internals.

The important rule is explicitness: a production Pulse should not depend on function argument guessing to decide whether it receives input and env or a standard Request.

  • definePulse((input, env) => ...) receives parsed JSON input plus the curated Pulse runtime API.
  • defineWebPulse((request) => ...) receives the web-standard Request and returns a Response.
  • env.fetch keeps outbound network calls inside Vibecodr policy controls.
  • env.state coordinates operation lifecycle across Pulse calls with named resources and stable keys.
Enhanced Pulse SDK handler typescript
import { definePulse } from "@vibecodr/pulse";

export default definePulse(async (input, env) => {
  const city = typeof input?.city === "string" ? input.city : "Paris";
  const runtimeCaps = env.runtime.capabilities();

  const forecast = await env.fetch(
    `https://weather.example.com/forecast?city=${encodeURIComponent(city)}`
  );

  env.log.info("forecast.loaded", {
    city,
    status: forecast.status,
    declaredSecrets: runtimeCaps.secrets.declaredKeys,
  });

  return {
    city,
    ok: forecast.ok,
    requestId: env.runtime.requestId,
  };
});
Web-standard handler typescript
import { defineWebPulse } from "@vibecodr/pulse";

export default defineWebPulse(async (request) => {
  if (request.method !== "POST") {
    return Response.json(
      { error: "method_not_allowed" },
      { status: 405 }
    );
  }

  const body = await request.json().catch(() => null);
  return Response.json({ received: body });
});

What a Pulse State operation counts

A Pulse State operation is one admitted attempt to use a descriptor-declared env.state resource. It is counted when the Pulse asks Vibecodr to guard a key before protected work runs, so duplicate webhooks or retried HTTP mutations can be coordinated before creator code performs the protected operation.

Pulse State operations are separate from Pulse runs. A run can use zero Pulse State operations, one operation, or more than one when it intentionally protects multiple idempotency resources.

runOnce() is scoped to the resource plus key inside the Pulse owner/deployment scope. Multiple calls with the same resource and key meet at the same coordination record.

  • Use a Pulse State operation for awaited work inside runOnce() or an acquired manual lifecycle.
  • Think of Pulse State as an operation guard. App records, search/query data, and files belong in a data surface or connected service.
  • The monthly quota protects the shared coordination lane from unbounded retry and duplicate-key cost.
  • If duplicate protection is unnecessary, skip env.state and the run will not spend a Pulse State operation.
  • Creator code cannot list, delete, purge, or reset Pulse State records. Account and project cleanup are handled through separate audited product flows.

How long Pulse State remembers a key

A state resource may declare retentionTtlSeconds. That is how long Vibecodr keeps duplicate-memory for completed or terminal operations on that resource, capped by the owner's plan.

Free plans can retain Pulse State duplicate-memory for up to 24 hours, Creator plans for up to 7 days, and Pro plans for up to 30 days. If a resource asks for a longer window, Vibecodr caps it to the plan limit.

Retention keeps a minimized coordination record: protected operation identity, status, timing, and small replay/failure metadata. Raw keys, secrets, request bodies, and application records do not become creator-readable state.

Vibecodr persists that coordination record in durable platform storage. It is not held by keeping a live worker warm, so a cold start does not erase duplicate memory.

  • retentionTtlSeconds controls duplicate-memory retention for the state resource, not the lifetime of user data.
  • The retained record mostly costs tiny stored metadata; reads, writes, and runtime work are paid when calls or cleanup touch the record.
  • After the retention window, the record no longer counts as duplicate memory and becomes eligible for Vibecodr cleanup; do not use Pulse State as long-term app storage.
  • Cleanup runs in bounded batches. Exact byte-removal timing can lag the semantic retention window, so plan caps and cleanup retries keep the storage footprint bounded.
  • Owner/account and Pulse deletion use separate audited cleanup paths and are not exposed through the creator runtime SDK.

Choose among the Pulse State tools

runOnce() fits the common case, but Pulse State is a small toolkit for coordinating a protected operation. Pick the tool that matches how much lifecycle control the Pulse needs.

A manual lifecycle is useful when the Pulse needs to decide when to complete or fail a guarded operation instead of wrapping all protected work in one callback.

  • runOnce(key, handler): use this when one awaited callback contains the work. Vibecodr guards the key, runs the callback only for the acquired attempt, then completes or fails the operation.
  • Manual lifecycle: use claim(key) when the Pulse needs to call complete() or fail() itself. The handle is usable only inside the current invocation.
  • keyFromRequest(request): use this for HTTP mutations that carry one trusted idempotency key. Verify webhook signatures before deriving keys from provider events.
  • effect() and effectKey(): use these inside an acquired runOnce() or manual lifecycle to pass provider-ready idempotency metadata to guarded Stripe or HTTP side effects.
Manual lifecycle typescript
const claim = await env.state.orderMutations.claim(orderId);

if (claim.status === "completed") {
  return claim.replay;
}
if (claim.status === "pending") {
  return { accepted: true, status: "already-running" };
}

let protectedWorkSucceeded = false;
try {
  const effect = await claim.effect({
    provider: "stripe",
    operation: "payment_intents.capture",
    target: paymentIntentId,
  });

  await env.fetch(`https://api.stripe.com/v1/payment_intents/${paymentIntentId}/capture`, {
    method: "POST",
    headers: effect.headers,
    auth: env.secrets.bearer("PAYMENT_PROVIDER_API_KEY"),
  });
  protectedWorkSucceeded = true;
} catch (error) {
  if (!protectedWorkSucceeded) {
    await claim.fail(error);
  }
  throw error;
}

await claim.complete({ accepted: true });
return { accepted: true };

Using Pulse State operations safely

Why use it: Pulse State operations exist for work where a retry or duplicate delivery could charge a customer twice, send the same email twice, or mutate a provider twice. Pulse State lets Vibecodr coordinate the key before creator code performs the protected operation.

When to use it: use env.state for webhook events, POST mutations, and other retryable requests that carry one stable idempotency key. Skip it for ordinary reads, analytics-only logs, or work where running twice is harmless.

How to use it safely: declare the state resource, derive the key from a trusted source, and keep the protected work awaited inside runOnce(). Verify webhooks before deriving the key, because an unverified body is not trusted input. Duplicate protection makes repeated wakes safe; it is not an exactly-once guarantee for third-party side effects.

  • Verify webhooks before deriving the key.
  • Use keyFromRequest() for generic HTTP mutations that require an Idempotency-Key header.
  • Only awaited work inside runOnce() is protected; waitUntil(), timers, and fire-and-forget promises run outside the protected operation.
  • Duplicate pending requests should not run creator code again.
  • Completed duplicates return the stored small JSON-safe callback result when replay was retained. If replay was disabled or omitted because the result was unsafe, oversized, or not JSON-safe, runOnce() fails with PulseStateReplayUnavailable instead of running creator code again.
Verified webhook Pulse State operation typescript
import { definePulse } from "@vibecodr/pulse";

export default definePulse(async (_input, env) => {
  const event = await env.webhooks.verify("stripe", {
    secret: "STRIPE_WEBHOOK_SECRET",
  });

  return env.state.stripeWebhookEvents.runOnce(event.id, async () => {
    await env.fetch("https://api.example.com/fulfill", {
      method: "POST",
      auth: env.secrets.bearer("PROVIDER_API_KEY"),
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ eventId: event.id }),
    });

    return { accepted: true };
  });
});

Production handler conventions

A handler convention is a team contract. Once client code depends on response fields, status codes, or route names, changing them becomes a public API change.

Prefer small handlers that compose clear helpers over large route files. Method checks, validation, idempotency, external calls, and response shaping should be quick to find without scrolling through unrelated behavior.

  • Check request method before reading expensive bodies.
  • Parse and validate input before calling external services.
  • Keep route names stable.
  • Version payloads when callers need compatibility.
  • Log metadata, not raw secrets, tokens, or private user payloads.

Example and read next

Example: a Pulse receives POST /api/contact. Use a web-standard Request handler, parse JSON once, validate required fields, call trusted providers through env capabilities, and return a viewer-safe Response.

Use these related pages when you need the next layer of guidance. They point to the most likely follow-up tasks, not every page that happens to touch the same system.

  • Read next: /blueprints (/blueprints)
  • Read next: Vibes & Pulses (/docs/vibes-pulses)
  • Read next: Pulse Routing (/docs/pulse-routing)
  • Read next: Secrets & APIs (/docs/secrets)

Related documentation