OdyDocs

TypeScript SDKpreview

Use @ody/sdk from Node.js, Deno, Bun or the browser: setup, pagination, errors, idempotency and webhook verification.

Preview

@ody/sdk isn't on npm yet; see availability. The API it wraps is stable and live.

#Setup

Shell
npm install @ody/sdk   # once published
TypeScript
import { Ody } from "@ody/sdk";

const ody = new Ody(process.env.ODY_API_KEY!);
// Options: new Ody(key, { baseUrl: "https://api.ody.co" })

The client is ESM, has no dependencies, and runs anywhere fetch exists (Node.js 18+, Deno, Bun, Cloudflare Workers, browsers). Never ship a live key to a browser. Keys act as a workspace member, so call Ody from your server.

#Usage

TypeScript
// Contacts
const contact = await ody.contacts.create({ firstName: "Ada", phone: "+15125550123", tags: ["vip"] });
for await (const c of ody.contacts.listAll({ q: "ada" })) console.log(c.id);

// Conversations
const { conversations } = await ody.conversations.list({ status: "open", limit: 25 });
const { conversation, activities } = await ody.conversations.get(conversations[0].id);
await ody.conversations.setStatus(conversation.id, "done");

// Messages
const sent = await ody.messages.send({ to: "+15125550123", body: "Hi!", idempotencyKey: "welcome-ada" });
const hits = await ody.messages.search("refund", { limit: 10 });

// Calls
const calls = await ody.calls.list({ limit: 20 });
await ody.calls.place({ to: "+15125550123" });

// Numbers, 10DLC, agent
const available = await ody.numbers.searchAvailable({ startsWith: "512" });
await ody.numbers.buy({ phoneNumber: available[0].phone_number, idempotencyKey: "main-line" });
const status = await ody.messaging.status();
await ody.agent.update({ greeting: "Thanks for calling!" });
await ody.agent.publish();

#Errors

Every non-2xx response throws OdyError:

TypeScript
import { OdyError } from "@ody/sdk";

try {
  await ody.messages.send({ to: "+15125550123", body: "Hi" });
} catch (err) {
  if (err instanceof OdyError) {
    switch (err.code) {
      case "failed_precondition": /* e.g. 10DLC not approved yet: show err.message */ break;
      case "resource_exhausted":  await sleep((err.retryAfter ?? 1) * 1000); break;
      case "unauthenticated":     /* key revoked */ break;
    }
    console.error(err.status, err.code, err.message, err.requestId);
  } else {
    throw err; // network error
  }
}

The codes are listed in Errors.

#Webhook verification

TypeScript
import { verifyWebhookSignature } from "@ody/sdk";

const ok = verifyWebhookSignature(secret, headers, rawBody); // rawBody: string | Uint8Array

It checks the v1 HMAC-SHA256 signature in constant time and rejects timestamps more than 5 minutes off. It uses node:crypto, so it's server-side only. Full examples are in Verifying signatures.

#Webhooks

parseWebhookEvent verifies and parses in one step, and returns a WebhookEvent whose data is typed by event.type. It throws WebhookVerificationError on a bad or stale signature, so a handler is a try around one call:

TypeScript
import express from "express";
import { parseWebhookEvent, WebhookVerificationError } from "@ody/sdk";

app.post("/ody-webhook", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = parseWebhookEvent(process.env.ODY_WEBHOOK_SECRET!, req.headers, req.body);
  } catch (err) {
    if (err instanceof WebhookVerificationError) return res.status(401).end();
    throw err;
  }

  switch (event.type) {
    case "call.ringing":
      screenPop(event.data.from, event.data.callerName);       // typed as CallRingingData
      break;
    case "call.answered":
      if (event.data.answeredBy === "member") assign(event.data.memberUid);
      break;
    case "contact.updated":
    case "contact.deleted":
      void syncContact(event.data.contactId);                  // ContactEventData
      break;
  }
  res.status(200).end();
});

WEBHOOK_EVENT_TYPES is the catalog as a readonly tuple (WebhookEventType is its union), and isKnownWebhookEventType(type) tells you whether a delivery's type is one this SDK version knows, so a subscription to ["*"] can log new types instead of crashing. Each event's fields are described in Event types.

#Types

Everything is exported: Contact, ContactSummary, ContactInput, ConversationSummary, Activity, PhoneNumber, MessagingStatus, Agent, AgentInput, Webhook, WebhookDelivery, OdyErrorCode, and for webhooks WebhookEvent, WebhookEnvelope, WebhookEventDataMap, CallRingingData, CallAnsweredData, CallForwardedData, ContactEventData and more. They mirror the OpenAPI spec and the event catalog.

Last updated Wed Oct 14 2026 00:00:00 GMT+0000 (Coordinated Universal Time)Something wrong on this page? Tell us