Vercel AI SDK
The @verdacert/ai-sdk-tools npm package wraps the REST API as a typed Vercel AI SDK tool catalog. Drop one import into your generateText, streamText, or agent loop and the model can quote, submit, poll, refund, and verify end-to-end.
Install
Two peer deps, both standard.
pnpm add @verdacert/ai-sdk-tools ai zod
# or: npm i @verdacert/ai-sdk-tools ai zodai (v5 or v6) and zod (v3 or v4) are peer dependencies — bring your own version.
Usage
One factory call, one import, all seven tools.
import { generateText, stepCountIs } from "ai";
import { createVerdacertTools } from "@verdacert/ai-sdk-tools";
const tools = createVerdacertTools({
apiKey: process.env.VERDACERT_API_KEY!,
});
const { text } = await generateText({
// Routes through Vercel AI Gateway when AI_GATEWAY_API_KEY is
// present (auto-injected on Vercel). Provider-string model.
model: "anthropic/claude-sonnet-4",
tools,
stopWhen: stepCountIs(20), // multi-step agent loop
prompt:
"I have a 3-page Farsi birth certificate at " +
"https://example.com/cert.pdf. Quote a USCIS-acceptance " +
"translation, submit it, poll until ready, and give me " +
"the certified URL.",
});
console.log(text);AI_GATEWAY_API_KEY is auto-injected — the AI SDK forwards the provider-string model id automatically. If you prefer direct providers, swap to model: anthropic("claude-sonnet-4-20250514") from @ai-sdk/anthropic.The seven tools
Each is a plain AI SDK Tool — destructure if you only want a subset.
| Tool | Description |
|---|---|
getCapabilities | Returns live enums — call once per session to ground the model. |
quote | Binding price + ETA. Free; no order created. quoteId valid 24h. |
submit | Place the order. Idempotent on (apiKey, idempotencyKey). Returns jobId. |
getStatus | Poll progress. Safe to back off — start at 30s. |
getResult | Fetch the certified PDF URL + JWS receipt once status === ready. |
refund | Full or partial refund within 30 days. Stripe-idempotent server-side. |
verifyCertificate | Independently verify a Verdacert certificate id. |
Subset / extend
// Only some tools? Destructure:
const { quote, submit, getStatus, getResult } =
createVerdacertTools({ apiKey: process.env.VERDACERT_API_KEY! });
const { text } = await generateText({
model: "anthropic/claude-sonnet-4",
tools: { quote, submit, getStatus, getResult },
prompt: "…",
});Each entry is a plain AI SDK Tool — wrap or instrument freely. Pre-validate inputs with the exported zod schemas if you want to short-circuit before a tool call:
import { quoteInputSchema } from "@verdacert/ai-sdk-tools";
const parsed = quoteInputSchema.safeParse(input);Client options
The factory accepts a few overrides for testing and observability.
createVerdacertTools({
apiKey: process.env.VERDACERT_API_KEY!,
// Optional:
baseUrl: "https://verdacert.com", // staging / self-host override
fetch: globalThis.fetch, // inject for tracing or retries
defaultHeaders: { "x-trace-id": "…" }, // forwarded on every call
});| Option | Type | Purpose |
|---|---|---|
apiKey | string | Required. Live or sandbox token. |
baseUrl | string | Override the host (default https://verdacert.com). |
fetch | typeof fetch | Inject your own fetch implementation — e.g. OpenTelemetry-wrapped. |
defaultHeaders | Record<string,string> | Headers added to every request (trace ids, custom User-Agent). |
Errors
Non-2xx responses throw VerdacertHttpError with the structured envelope intact.
import { VerdacertHttpError } from "@verdacert/ai-sdk-tools";
try {
await tools.submit.execute(input, /* AI SDK passes context */);
} catch (err) {
if (err instanceof VerdacertHttpError) {
console.error(err.status); // 402
console.error(err.body.code); // "PAYMENT_FAILED"
console.error(err.body.recoveryHint); // tuned for an LLM consumer
console.error(err.requestId); // hand this to support
}
throw err;
}When tools are invoked by the AI SDK itself (e.g. inside generateText), errors flow through the SDK's tool-result channel and the model can react on the next turn — the recoveryHint values are tuned for that path. See /docs/errors for the stable code list.
Streaming
Plug straight into streamText — tool calls work identically.
import { streamText, stepCountIs } from "ai";
import { createVerdacertTools } from "@verdacert/ai-sdk-tools";
export async function POST(req: Request) {
const { messages } = await req.json();
const tools = createVerdacertTools({
apiKey: process.env.VERDACERT_API_KEY!,
});
const result = streamText({
model: "anthropic/claude-sonnet-4",
tools,
stopWhen: stepCountIs(20),
messages,
});
return result.toDataStreamResponse();
}Verifying certificates client-side
verifyCertificate round-trips through the API and returns the parsed envelope plus a compact JWS. If you want to skip the round-trip and verify entirely against the cached JWKS:
import { jwtVerify, createRemoteJWKSet } from "jose";
const jwks = createRemoteJWKSet(
new URL("https://verdacert.com/.well-known/jwks.json"),
);
const result = await tools.verifyCertificate.execute(
{ certificateId: "vcrt_…" },
/* AI SDK context */,
);
const { payload, protectedHeader } = await jwtVerify(result.jws, jwks);
// payload.certificateId, payload.documentSha256, payload.isSandboxFull walkthrough at /docs/certificates (Python and Go examples too).
Reference agent
A complete working example — Claude using these tools to walk a paralegal through an I-130 immigration case — lives in examples/immigration-paralegal.
git clone https://github.com/mitrakmt/verdacert.git
cd verdacert/examples/immigration-paralegal
pnpm install
cp .env.example .env # add VERDACERT_API_KEY + ANTHROPIC_API_KEY
pnpm start # uses the included sample case
# … or supply your own case file:
pnpm start path/to/case.md