| name | client |
| description | Use when instantiating the `Medal` class from `@medalsocial/sdk`, configuring auth (API key vs OAuth), setting `baseUrl`, debugging an HTTP response or `MedalApiError`, reasoning about retry behavior, or running the SDK in a browser / edge runtime. Required reading before any code that calls into the SDK at the client level. |
Medal Social SDK — Client
When to load this skill
- Instantiating
new Medal(...).
- Choosing between an API key and an OAuth access token.
- Configuring
baseUrl, timeout, or workspaceId.
- Debugging a 4xx / 5xx response.
- Reasoning about whether a failed request will be retried.
- Running the SDK in a browser, Cloudflare Worker, Deno, or Bun.
Base URL — the most common pitfall
The Medal Social API base URL is https://io.medalsocial.com.
It is NOT https://api.medalsocial.com. Setting baseUrl: "https://api.medalsocial.com" returns DNS or 404 errors. The SDK defaults to the correct value when baseUrl is omitted — only set it explicitly when targeting a non-prod environment.
Instantiation — token is positional
The token is the first positional argument, not a key on an options object:
import { Medal } from "@medalsocial/sdk";
const medal = new Medal("medal_xxx");
const medal = new Medal("oauth_access_token", {
workspaceId: "workspace_id_here",
});
new Medal({ apiKey: ... }) is wrong — there is no apiKey option. The constructor throws if the token is empty.
Options
interface MedalOptions {
baseUrl?: string;
timeout?: number;
workspaceId?: string;
}
API keys start with medal_ and are scoped to a single workspace — the SDK reads the workspace from the key. OAuth tokens are workspace-agnostic, so you must pass workspaceId.
Auth headers
Every request gets:
Authorization: Bearer <token>
x-workspace-id: <workspaceId> (only if workspaceId was set on the constructor)
User-Agent: medalsocial-sdk/<version> (best-effort — browsers reject custom User-Agent; the SDK swallows that error silently)
Retry behavior
BaseClient.request retries on 429 and 5xx for up to 3 attempts total:
- If the response has a
retry-after header (in seconds), the SDK waits that long.
- Otherwise it waits
250 * attempt ms (so 250, 500 between the first three attempts).
- Other 4xx errors are NOT retried — they throw
MedalApiError immediately.
- Network errors (fetch throws) are NOT retried — they bubble up.
- The request is aborted via
AbortController after timeout ms.
The SDK does not drain the response body between retries — if you observe a connection leak in long-running processes, that's worth investigating.
Errors
Non-2xx responses throw MedalApiError:
import { Medal, MedalApiError } from "@medalsocial/sdk";
try {
await medal.posts.create({ content: "hi", channel_ids: ["ch_1"] });
} catch (err) {
if (err instanceof MedalApiError) {
err.status;
err.code;
err.message;
err.details;
}
}
The Medal instance does not expose the underlying BaseClient — it's created as a local const in the constructor and only passed into the resource classes. There is no public way to read the resolved config from a Medal instance. If you need the same config later (for logging, custom requests), store your MedalOptions separately when you construct the client.
OIDC publishing — consumers do not need a token
The package is published with provenance attestation via npm OIDC trusted publishing. Consumers do not need an NPM_TOKEN to install. There is no static publish token; releases run from the locked prod branch only via GitHub Actions.
Runtimes
The SDK uses standard Web Fetch + AbortController and has no Node-only APIs, so it runs in Deno, Bun, Cloudflare Workers, and modern browsers.
Browser usage caveat: the Medal API does not currently support CORS for arbitrary origins — calls from browser code typically need a server-side proxy that holds the API key. Don't embed a medal_* key in client-side JavaScript regardless; it grants full workspace access.
Node: package.json enforces engines.node >=24. Older Node is blocked at install time. If you need Node 18–22 support, relax engines in your fork and verify against the SDK's test suite first.
Cloudflare Workers / edge: works out of the box; the User-Agent set is silently rejected (workers also disallow it) and the SDK swallows the error.