@refactual/sdk
v0.1.2
Published
Official Refactual REST API client for Node.js and the browser.
Maintainers
Readme
@refactual/sdk
Typed Node.js + browser client for the Refactual platform. Send legacy code in, get modern code back; ship a Shopify store URL in, get a ranked action plan back. All from one client with auto-retry, bearer-token auth, and webhook signature verification.
Preview release (
0.x) — the API is stable but minor versions may add fields before1.0.0. Pin exact versions in production and watch the changelog.
What is Refactual?
Refactual is a developer platform that turns AI agents into a working stack for legacy modernization, document parsing, and ecommerce intelligence. The core surfaces are:
- Refactor — paste COBOL, Fortran, Perl, VB6, PHP, Java 7, jQuery (40+ source languages); receive idiomatic modern code in your target language with a reviewed diff.
- Repos — drop a GitHub URL or upload a directory; receive a modernization score, hotspots ranked by tech-debt severity, and Auto-Fix recipes with a dev-weeks effort estimate.
- Parser — upload PDFs or freeform text; receive structured JSON, field-level diffs, or a generated API schema.
- Stores — connect Shopify; receive a ranked AI action plan with dollar-impact estimates for revenue, AOV, and CAC moves.
Underneath those products is a single REST API, an MCP server (so Claude, Cursor, and Continue can call the same tools), and 15 Apps Marketplace integrations (GitHub, Slack, Linear, Jira, Sentry, Shopify, etc.) so output flows back into the tools your team already ships with.
This SDK wraps that REST API for TypeScript and JavaScript projects.
Why use this over fetch?
- Typed responses —
ChatCompletion,CreditBalance,UsageDay,Session,Invoice,Model. Inferred return types in your editor; noas anycasts. - Promise-based, isomorphic — works in Node 18+, Deno, Bun, Cloudflare
Workers, and modern browsers. Bring your own
fetchfor edge runtimes. - Auto retry with backoff — 429s and 5xx errors retry up to twice with
exponential backoff and
Retry-Afterhonoring. You don't write the loop. - Retries are billed once — every call carries an idempotency key that its retries reuse, so a timed-out request that is retried can't be charged twice. See Retries are billed once.
- Bearer + OAuth — long-lived API keys for server code, short-lived
client_credentialsaccess tokens for third-party apps and MCP clients. - Webhook verification —
verifyWebhook(rawBody, signature, secret)with timestamp tolerance. No copy-pasting HMAC code from Stack Overflow. - Typed errors —
RefactualErrorexposesstatus,type(e.g.insufficient_credits,rate_limited), and the raw response. - No external runtime deps — uses the platform's native
fetch. Tiny install footprint, easy to audit.
Install
npm install @refactual/sdkpnpm add @refactual/sdk and yarn add @refactual/sdk work too. Requires
Node 18+ for native fetch; older Node can pass a fetch polyfill via the
fetch option.
Quickstart
Get a key at app.refactual.com/developer.
Use a rk_test_* key in CI — it returns deterministic sandbox responses
and consumes zero credits.
import Refactual from '@refactual/sdk';
const client = new Refactual({ apiKey: process.env.REFACTUAL_API_KEY! });
// Refactor a snippet of legacy COBOL into TypeScript.
// Inferred type: Promise<ChatCompletion>
const reply = await client.chat.completions.create({
model: 'claude-sonnet-4-6',
systemPrompt:
'You are Refactor. Output only the modernized code in TypeScript. ' +
'Preserve behavior; no commentary.',
messages: [
{ role: 'user', content:
'IDENTIFICATION DIVISION.\n' +
'PROGRAM-ID. HELLO.\n' +
'PROCEDURE DIVISION.\n' +
' DISPLAY "Hello, world!".\n' +
' STOP RUN.\n'
},
],
maxTokens: 2048,
});
console.log(reply.content); // modernized TypeScript
console.log(reply.usage.input_tokens); // 47
console.log(reply.usage.cost_cents); // 0.42The response is fully typed — reply.content is string, reply.usage is
{ input_tokens; output_tokens; cost_cents }. No schema definitions to keep
in sync.
What you can build
CI bot that posts modernization scores on PRs. Run client.chat.completions.create
on the diff with a system prompt that asks for a hotspot summary, then post
the markdown back via octokit.issues.createComment. Block merges if the
score regresses.
Slack /refactor command. Wire a Slack slash command to a serverless
function that pipes the user's code snippet through Refactual and replies
with the modernized version in a code block. Add oauth.token so each
workspace authenticates against its own Refactual account.
PDF-to-JSON ingestion pipeline. Run incoming invoices through a chat
completion configured with a strict JSON schema in the system prompt; pipe
the structured output into Postgres via Prisma. Use the usage and
credits endpoints to surface cost-per-document in your admin dashboard.
Storefront action-plan widget. Connect Shopify in the Refactual app,
then surface the ranked action plan in your internal merchandising tool by
polling client.sessions.list. Webhooks push updates to your dashboard.
Features
- TypeScript-first; full
.d.tsshipped indist/. - Async/await throughout; no callback or event-emitter APIs.
- Automatic retry on 429 + 5xx with exponential backoff and jitter.
- One idempotency key per call, minted before the first attempt and reused by every retry — a retried request is billed once.
- Configurable per-request timeout (default 60s) and retry count.
defaultHeadersfor correlation IDs, tenant IDs, etc.RefactualErrorwith structuredstatus/type/rawfor programmatic recovery.- OAuth 2.0
client_credentialsgrant with scoped tokens. - Webhook signature verification (HMAC-SHA256, 5-minute tolerance).
- Generated OpenAPI types available at
@refactual/sdk/generatedfor advanced cases.
API surface
All methods return Promise<T> with fully typed responses.
| Resource | Description |
|---------------------------------|------------------------------------------|
| client.chat.completions.create| Submit messages, receive a model reply. |
| client.credits.retrieve | Current balance, plan, auto-reload. |
| client.usage.list | Daily token + cost rollup. |
| client.sessions.list | Recent conversations. |
| client.sessions.retrieve | Full transcript for one session. |
| client.invoices.list | Free-credit grants and monthly invoices. |
| client.models.list | Available models with context windows. |
| client.intelligence.create/list/retrieve/upload/delete | Run and poll a Repos modernization report. |
| client.autofix.list/retrieve | Turn a Repos hotspot into a PR/diff. |
| client.documents.create/list/retrieve/delete | Parser: files → structured JSON. |
| client.refactor.code | One-call legacy→modern code translation. |
| client.oauth.token | Exchange client_credentials for a token. |
| client.request | Low-level escape hatch for new endpoints.|
| client.requestRaw | Same, plus status, headers, replayed.|
| verifyWebhook (named export) | Validate a webhook signature. |
Full reference: https://app.refactual.com/developer/docs.
Configuration
const client = new Refactual({
apiKey: 'rk_live_...',
baseURL: 'https://app.refactual.com', // override for self-hosted
timeoutMs: 60_000,
maxRetries: 2,
defaultHeaders: { 'X-Correlation-Id': myId },
// For Workers/Deno where global fetch is non-standard:
// fetch: customFetch,
});OAuth 2.0 (client_credentials)
Use short-lived access tokens when authenticating third-party apps or MCP clients on behalf of your users:
const tok = await new Refactual({ apiKey: 'rk_placeholder' }).oauth.token({
clientId: process.env.REFACTUAL_CLIENT_ID!,
clientSecret: process.env.REFACTUAL_CLIENT_SECRET!,
scope: ['chat:write', 'chat:read'],
});
const client = new Refactual({ accessToken: tok.access_token });
// tok.expires_in is 3600 (1 hour). Re-call oauth.token to rotate.Webhook verification
import { verifyWebhook } from '@refactual/sdk';
import express from 'express';
const app = express();
app.post('/webhook',
express.raw({ type: 'application/json' }), // preserve raw body
(req, res) => {
try {
verifyWebhook(
req.body.toString('utf8'),
req.headers['refactual-signature'] as string,
process.env.WEBHOOK_SECRET!,
);
} catch (e) {
return res.status(400).send((e as Error).message);
}
const event = JSON.parse(req.body.toString('utf8'));
switch (event.type) {
case 'chat.completed': /* ... */ break;
case 'credit.low': /* ... */ break;
case 'invoice.paid': /* ... */ break;
case 'subscription.updated': /* ... */ break;
}
res.sendStatus(200);
});Error handling
import Refactual, { RefactualError } from '@refactual/sdk';
try {
await client.chat.completions.create({ messages: [/* … */] });
} catch (e) {
if (e instanceof RefactualError) {
// e.status: 402 | 429 | 401 | …
// e.type: 'insufficient_credits' | 'rate_limited' | 'authentication_error' | …
// e.raw: full server payload
// e.idempotencyKey: the key the call was sent with — resubmit with it for the same single answer
if (e.type === 'insufficient_credits') topUp();
else if (e.status === 429) backoffAndRetry();
else throw e;
}
}Retries are billed once
Every non-GET request the SDK sends carries an Idempotency-Key: a UUID
minted before the first attempt and reused by every retry of that call —
timeouts, 429s, 5xx. The platform runs a keyed chat.completions.create
once and remembers its answer for 24 hours, so a retry receives the stored
reply instead of starting a second, separately billed completion.
reply.replayed is true when that happened.
If a retry lands while the original attempt is still running, the platform
answers 409 idempotency_in_flight and the SDK waits for the original to
finish — polling with the same key for up to timeoutMs — then returns its
result. Only if that budget also runs out does the 409 surface as a
RefactualError, with e.idempotencyKey attached so you can resubmit the
same call later and still receive the one answer. In-flight polls don't
count against maxRetries.
Supply your own key when the same logical call may be issued again by a new process — a queue that can deliver a job twice, a cron that reruns after a crash:
const reply = await client.chat.completions.create(
{ messages: [{ role: 'user', content: code }] },
{ idempotencyKey: `job-${job.id}` }, // any string, up to 200 characters
);
if (reply.replayed) console.log('answered from the earlier attempt — not billed again');Keys are scoped to your API key. A call that ended in an error is remembered
under its key too, so a resubmission returns that same error; start a new
call (which mints a new key) to try again. client.requestRaw() returns the
same metadata — status, headers, replayed, idempotencyKey — for any
endpoint.
Streaming
Streaming token-by-token responses are on the roadmap. Today, chat.completions.create
returns the full reply in a single Promise. For long modernization jobs,
prefer the sessions endpoints to poll progress, or subscribe to webhook
events.
MCP integration
Refactual ships an MCP server that exposes the same surface as this SDK to Claude, Cursor, Continue, and any MCP-aware agent. The OAuth flow above is how third-party MCP clients authenticate against a user's Refactual account. See https://app.refactual.com/developer/docs/mcp for the manifest and tool definitions.
Versioning + support
- Semver. Breaking API changes bump the major version.
- Issues: https://github.com/refactual/refactual/issues
- Email: [email protected]
- Status: https://status.refactual.com
License
Apache-2.0. See LICENSE.
