Error Handling
Production patterns for handling DeepCitation errors gracefully.
Which operations can fail?
| Operation | Can fail? | Common causes | Safe to retry? |
|---|---|---|---|
new DeepCitation({ apiKey }) |
Yes | Missing or empty API key | No – fix the key |
uploadFile() / prepareAttachments() |
Yes | Network timeout, file too large, invalid format | Yes |
prepareUrl() |
Yes | Network timeout, URL unreachable, blocked by site | Yes (with backoff) |
verify() |
Yes | Network timeout, invalid citations, API error | Yes |
verifyAttachment() |
Yes | Network timeout, invalid attachment ID | Yes |
getAllCitationsFromLlmOutput() |
No | Never throws – returns {} on failure |
N/A |
wrapCitationPrompt() |
No | Never throws – returns enhanced prompts | N/A |
getCitationStatus() |
No | Never throws – returns status object | N/A |
Error types
DeepCitation provides structured error classes for programmatic error handling:
verify({ llmOutput }) is a convenience wrapper — it parses citations from the raw LLM output (via getAllCitationsFromLlmOutput()), groups them by attachment, then verifies each group. Use verifyAttachment(attachmentId, citations) when you extract and manage citations yourself. See SDK Reference for full method signatures.
import {
AuthenticationError,
NetworkError,
PaymentRequiredError,
RateLimitError,
ValidationError,
ServerError,
DeepCitationError,
} from "deepcitation";
try {
const { verifications } = await dc.verify({ llmOutput });
} catch (err) {
if (err instanceof AuthenticationError) {
// API key is missing, invalid, or expired
// Status code: 401 or 403
// NOT retryable - fix the API key
console.error("Check your DEEPCITATION_API_KEY:", err.message);
} else if (err instanceof PaymentRequiredError) {
// Free tier exhausted, spend cap hit, or payment failed
// Status code: 402
// NOT retryable - add or update a payment method
console.error("Payment required:", err.message, "billing code:", err.billingCode);
} else if (err instanceof RateLimitError) {
// Hit rate limit (429)
// Retryable after delay
console.error("Rate limited, retry after delay:", err.message);
} else if (err instanceof ValidationError) {
// Bad request: invalid format, file too large, etc.
// Status codes: 400, 404, 413, etc.
// NOT retryable - fix the input
console.error("Validation error:", err.message);
} else if (err instanceof ServerError) {
// API returned 5xx error
// Retryable with backoff
console.error("Server error, safe to retry:", err.message);
} else if (err instanceof NetworkError) {
// Network failure: timeout, DNS, connection refused
// Retryable with backoff
console.error("Network error, safe to retry:", err.message);
}
}
All errors extend DeepCitationError and include:
code- Machine-readable error code (see table below)isRetryable- Boolean flag indicating whether the operation can be safely retriedstatusCode- HTTP status code if applicabledocUrl- Link to documentation for this error code (e.g.,https://docs.deepcitation.com/errors#DC_AUTH_INVALID)
Error Code Reference
| Code | Error Class | HTTP Status | Retryable | Recovery Action |
|---|---|---|---|---|
DC_AUTH_INVALID |
AuthenticationError |
401, 403 | No | Check API key — rotate at deepcitation.com/keys |
DC_PAYMENT_REQUIRED |
PaymentRequiredError |
402 | No | Free tier exhausted, spend cap hit, or payment failed — add or update a payment method at deepcitation.com/pricing. Error includes a billingCode field with the server-side reason. |
DC_NETWORK_ERROR |
NetworkError |
— | Yes | Retry with exponential backoff — check network connectivity |
DC_RATE_LIMITED |
RateLimitError |
429 | Yes | Retry with exponential backoff — API rate limit hit |
DC_VALIDATION_ERROR |
ValidationError |
400, 404, 413 | No | Fix the input — check file size (max 100 MB), format, or attachment ID |
DC_SERVER_ERROR |
ServerError |
5xx | Yes | Retry with exponential backoff — if persistent, check status.deepcitation.com |
Retry pattern
Use the isRetryable flag to determine which errors are safe to retry:
import { DeepCitationError } from "deepcitation";
async function withRetry<T>(
fn: () => Promise<T>,
{ maxRetries = 3, baseDelay = 1000 } = {}
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
const isLastAttempt = attempt === maxRetries;
// Only retry errors that are marked as retryable
if (err instanceof DeepCitationError && !err.isRetryable) {
throw err; // Don't retry auth or validation errors
}
if (isLastAttempt) throw err;
// Exponential backoff with jitter
const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 500;
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error("Unreachable");
}
// Usage
const { verifications } = await withRetry(() =>
dc.verify({ llmOutput: response.content })
);
// Production guidance:
// - 3 retries is sufficient for transient errors (network blips, 503s)
// - Never retry auth errors (fix the key) or validation errors (fix the input)
// - For 402 billing errors (PaymentRequiredError), retrying won't help — add a payment method
// - If you see persistent 5xx errors after 3 retries, check status.deepcitation.com
Rate limits
DeepCitation has two distinct limit types:
- Billing limits (
402 Payment Required→PaymentRequiredError): when your free tier is exhausted, a spend cap is reached, or payment fails. Not retryable — add or update a payment method at deepcitation.com/pricing. - API rate limits (
429 Too Many Requests→RateLimitError): when request throughput exceeds your plan’s concurrency limits. Retryable with exponential backoff.
If you’re processing many documents in parallel, use the built-in concurrency limiter:
// The client limits concurrent uploads to 5 by default
const results = await dc.prepareAttachments(manyFiles);
File size limits
- Maximum file size: Check current limits at deepcitation.com/pricing
- Supported formats: PDF, DOCX, XLSX, PPTX, HTML, JPG, PNG, TIFF, WebP, HEIC
- Files that exceed limits will return a
413error
Empty citations
If getAllCitationsFromLlmOutput() returns an empty object {}, check:
- Did you wrap the prompt? Use
wrapCitationPrompt()to add citation instructions to your LLM call - Is the LLM following the format? Check the raw LLM output for
<cite ... />tags or<<<CITATION_DATA>>>blocks - Did you pass the
deepTextPages? The LLM needs the source text thatwrapCitationPrompt()renders into citation-ready prompt text
const citations = getAllCitationsFromLlmOutput(llmOutput);
if (Object.keys(citations).length === 0) {
// No citations found -- check the raw output
console.log("Raw LLM output:", llmOutput);
console.log("Contains cite tags:", llmOutput.includes("<cite"));
console.log("Contains deferred block:", llmOutput.includes("<<<CITATION_DATA>>>"));
}
Verification returns empty
If verify() returns { verifications: {} }, the client found no citations to verify. This is not an error – it means getAllCitationsFromLlmOutput() found nothing in the LLM output. See “Empty citations” above.
Common Mistakes
“My citations aren’t styled”
You forgot to import the stylesheet. Add @import "deepcitation/tailwind.css" to your CSS (Tailwind v4) or import "deepcitation/styles.css" in JS. See Styling.
“Verification says not_found but the text is there”
The LLM likely paraphrased the source. Check if you got a partial_text_found or found_source_match_only status instead. See Verification Statuses for the full list of partial match statuses.
“I exposed my API key in the browser”
Rotate it immediately at deepcitation.com/keys. Never prefix your key with NEXT_PUBLIC_ or expose it in client-side code — all DeepCitation API calls should happen server-side.
“API key format”
Keys always start with sk-dc- and must be at least 20 characters. If you’re getting authentication errors, check for trailing whitespace or newlines in your environment variable.