SDK Reference
The @pxdiff/sdk package provides programmatic access to the pxdiff API for screenshot capture, diffing, and approval workflows.
Installation
Section titled “Installation”npm install @pxdiff/sdkPxdiffClient
Section titled “PxdiffClient”The main class for interacting with pxdiff.
Constructor
Section titled “Constructor”import { PxdiffClient } from "@pxdiff/sdk";
const client = new PxdiffClient({ apiKey: "pxd_your_key_here", apiUrl: "https://pxdiff.com", // optional, this is the default project: "my-app", // optional project slug maxRetries: 3, // optional, retry on 429/5xx retryBaseDelayMs: 1000, // optional, base delay for backoff});| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | (required) | API key (format: pxd_ prefix + base64url) |
apiUrl | string | "https://pxdiff.com" | API base URL |
project | string | Project slug for scoping operations | |
maxRetries | number | 3 | Max retry attempts on 429 and 5xx. Set to 0 to disable. |
retryBaseDelayMs | number | 1000 | Base delay in ms for exponential backoff |
Captures
Section titled “Captures”createCapture(options)
Section titled “createCapture(options)”Creates a new capture. Supports two modes:
- Fleet mode — provide URLs, pxdiff captures the screenshots for you
- Manual mode — provide PNG buffers directly
// Fleet mode: pxdiff captures the screenshotsconst capture = await client.createCapture({ suite: "storybook", branch: "feat/login", commit: "abc123", targets: [ { name: "button-primary", url: "https://storybook.example.com/iframe.html?id=button--primary" }, { name: "button-secondary", url: "https://storybook.example.com/iframe.html?id=button--secondary" }, ],});
// Manual mode: upload your own screenshotsconst capture = await client.createCapture({ suite: "e2e", branch: "feat/login", commit: "abc123", snapshots: [ { name: "login-page", png: screenshotBuffer }, { name: "signup-page", png: signupBuffer, viewport: { width: 1280, height: 900 } }, ],});Options:
| Field | Type | Default | Description |
|---|---|---|---|
suite | string | (required) | Suite identifier |
branch | string | Git branch name | |
commit | string | Git commit SHA | |
baselineRef | string | Baseline branch for comparison | |
autoApprove | boolean | Auto-approve diffs | |
ephemeral | boolean | Non-persistent capture (no branch/commit) | |
isLocal | boolean | Local development capture | |
snapshots | SnapshotInput[] | PNG buffers (manual mode) | |
targets | CaptureTarget[] | URLs to capture (fleet mode) |
Returns: Capture
getCapture(captureId)
Section titled “getCapture(captureId)”Fetch full details of a capture including all snapshots.
const capture = await client.getCapture("cap_abc123");console.log(capture.status); // "completed"console.log(capture.snapshots.length); // 12Returns: Capture
findCapture(options)
Section titled “findCapture(options)”Search for a capture by commit, branch, or suite.
const capture = await client.findCapture({ suite: "storybook", commits: ["abc123def456"],});| Field | Type | Description |
|---|---|---|
suite | string | Suite identifier |
commits | string[] | Commit SHAs to search |
branch | string | Git branch name |
Returns: Capture or null
uploadSnapshot(captureId, name, png, options?)
Section titled “uploadSnapshot(captureId, name, png, options?)”Upload a single screenshot to an existing capture.
const snapshot = await client.uploadSnapshot("cap_abc", "login-page", pngBuffer, { viewport: { width: 1280, height: 720 },});Returns: Snapshot
uploadSnapshotWithDiff(input)
Section titled “uploadSnapshotWithDiff(input)”Upload a screenshot and immediately get diff results. Used by the Playwright plugin for per-screenshot assertions.
const result = await client.uploadSnapshotWithDiff({ name: "homepage", png: screenshotBuffer, suite: "e2e", branch: "feat/redesign", commit: "abc123", baselineRef: "main", viewport: { width: 1280, height: 720 }, sessionId: "session_xyz",});
console.log(result.status); // "changed"console.log(result.diffPercentage); // 2.4| Field | Type | Default | Description |
|---|---|---|---|
name | string | (required) | Snapshot identifier |
png | Buffer | Uint8Array | (required) | Image buffer |
suite | string | (required) | Suite identifier |
branch | string | (required) | Git branch |
commit | string | (required) | Git commit SHA |
baselineRef | string | (required) | Baseline reference |
viewport | object | { width, height } | |
sessionId | string | Session ID for grouping | |
path | string[] | Display hierarchy path | |
metadata | Record | Custom metadata | |
isLocal | boolean | Local development | |
autoApprove | boolean | Auto-approve this snapshot |
Returns: SnapshotDiffResult
createDiff(options)
Section titled “createDiff(options)”Create a diff comparing a head capture against a baseline.
const diff = await client.createDiff({ suite: "storybook", headCaptureId: "cap_abc", baselineRef: "main", threshold: 0.063,});| Field | Type | Default | Description |
|---|---|---|---|
suite | string | (required) | Suite identifier |
headCaptureId | string | (required) | Capture to compare |
baselineRef | string | Baseline branch reference | |
baseCaptureId | string | Specific capture to compare against | |
threshold | number | 0.063 | Pixel difference threshold (0–1) |
antiAliasing | boolean | Ignore anti-aliasing differences | |
manifest | string[] | Only diff these snapshot names | |
targetRefs | string[] | Target branches for approval |
Returns: Diff
getDiff(diffId)
Section titled “getDiff(diffId)”Fetch full diff details including per-snapshot results.
const diff = await client.getDiff("diff_abc");
for (const result of diff.results) { if (result.status === "changed") { console.log(`${result.name}: ${result.diffPercentage}% different`); }}Returns: Diff
reDiff(diffId)
Section titled “reDiff(diffId)”Trigger a re-diff against current baselines. Useful after baselines have been updated.
Returns: Diff
Approvals
Section titled “Approvals”approve(diffId, options?)
Section titled “approve(diffId, options?)”Approve snapshots in a diff, promoting them to baselines.
// Approve specific snapshotsawait client.approve("diff_abc", { snapshots: ["button-primary", "header"],});
// Approve allawait client.approve("diff_abc");| Field | Type | Description |
|---|---|---|
snapshots | string[] | Specific snapshot names. Omit to approve all. |
Returns: { approved: number, total: number }
revoke(diffId, snapshots?)
Section titled “revoke(diffId, snapshots?)”Revoke approval for snapshots, resetting them to pending.
// Revoke specificawait client.revoke("diff_abc", ["button-primary"]);
// Revoke allawait client.revoke("diff_abc");Returns: { revoked: number }
getBaselineRefs(suite)
Section titled “getBaselineRefs(suite)”List all branch refs that have approved baselines for a suite.
const refs = await client.getBaselineRefs("storybook");// ["main", "develop"]Sites & Stories
Section titled “Sites & Stories”uploadSite(tarGz)
Section titled “uploadSite(tarGz)”Upload a tar.gz archive of a static site (e.g. Storybook build) for story discovery.
const site = await client.uploadSite(archiveBuffer);console.log(site.id); // "site_abc"console.log(site.status); // "pending"Returns: Site
discoverStories(siteId)
Section titled “discoverStories(siteId)”Discover stories from an uploaded Storybook site.
const stories = await client.discoverStories("site_abc");for (const story of stories) { console.log(story.title, story.name, story.url);}Returns: StoryEntry[]
Sessions
Section titled “Sessions”Sessions group inline screenshot uploads into a single capture and diff. Used by the Playwright plugin.
createSession(options)
Section titled “createSession(options)”const session = await client.createSession({ suite: "playwright", branch: "feat/login", commit: "abc123", baselineRef: "main",});| Field | Type | Default | Description |
|---|---|---|---|
suite | string | (required) | Suite identifier |
branch | string | (required) | Git branch |
commit | string | (required) | Git commit SHA |
baselineRef | string | Baseline reference | |
autoApprove | boolean | Auto-approve all uploads | |
isLocal | boolean | Development capture |
Returns: { sessionId, captureId, diffId }
completeSession(sessionId, options?)
Section titled “completeSession(sessionId, options?)”Complete a session, triggering carry-forward and GitHub check run posting. A session may contain diffs from multiple suites.
const result = await client.completeSession("session_abc", { exitCode: 0,});
console.log(result.imageToken); // signed token for image accessfor (const diff of result.diffs) { console.log(diff.suite); // "my-suite" console.log(diff.diffId); // "diff_xyz" console.log(diff.conclusion); // "success" | "failure" | null console.log(diff.reviewUrl); // URL to review UI console.log(diff.summary); // { changed, new, unchanged, removed, failed } console.log(diff.changedSnapshots); // ["button", "header"]}Returns: { imageToken, diffs } where each diff contains { diffId, suite, conclusion, checkRunUrl, reviewUrl, summary, changedSnapshots }
Error Handling
Section titled “Error Handling”All methods throw PxdiffError on failure.
import { PxdiffClient, PxdiffError } from "@pxdiff/sdk";
try { await client.getCapture("invalid-id");} catch (e) { if (e instanceof PxdiffError) { console.error(e.code); // "CAPTURE_NOT_FOUND" console.error(e.statusCode); // 404 console.error(e.message); // "Capture not found" }}| Property | Type | Description |
|---|---|---|
code | string | Error code (e.g. "VALIDATION_ERROR", "RATE_LIMITED") |
message | string | Human-readable message |
statusCode | number | HTTP status code |
retryAfter | number | undefined | Seconds to wait (from Retry-After header, present on 429 responses) |
Retryable errors (429, 500, 502, 503) are automatically retried with exponential backoff before being thrown. See Rate Limits for details.
Git Utilities
Section titled “Git Utilities”Exported from @pxdiff/sdk/git.
detectBranch(override?)
Section titled “detectBranch(override?)”Detect the current git branch. Checks CI environment variables first, then falls back to git.
import { detectBranch } from "@pxdiff/sdk/git";const branch = await detectBranch();Detection order: GITHUB_HEAD_REF → GITHUB_REF → CI_COMMIT_REF_NAME → git rev-parse --abbrev-ref HEAD
detectCommit(override?)
Section titled “detectCommit(override?)”Detect the current git commit SHA.
import { detectCommit } from "@pxdiff/sdk/git";const commit = await detectCommit();Detection order: GitHub PR event payload → GITHUB_SHA → CI_COMMIT_SHA → git rev-parse HEAD
detectBaselineRef()
Section titled “detectBaselineRef()”Detect the baseline branch reference (synchronous).
import { detectBaselineRef } from "@pxdiff/sdk/git";const ref = detectBaselineRef(); // "main" or nullDetection order: GITHUB_BASE_REF → PXDIFF_BASELINE_REF → null
getGitMergeBase(ref)
Section titled “getGitMergeBase(ref)”Find the merge-base commit between HEAD and a remote branch.
import { getGitMergeBase } from "@pxdiff/sdk/git";const mergeBase = await getGitMergeBase("main"); // commit SHA or nullStory Utilities
Section titled “Story Utilities”explodeStoryTargets(stories)
Section titled “explodeStoryTargets(stories)”Convert Storybook story entries into concrete capture targets. Handles mode explosion, viewport explosion, and parameter passthrough.
import { explodeStoryTargets } from "@pxdiff/sdk";
const stories = await client.discoverStories(siteId);const targets = explodeStoryTargets(stories);
const capture = await client.createCapture({ suite: "storybook", branch: "main", commit: "abc123", targets,});Behavior:
- Stories with
parameters.disable: trueare filtered out - Each mode in
parameters.modesproduces a separate target with[modeName]suffix - If no modes, each entry in
parameters.viewportsproduces a target with__widthsuffix - Copies
delay,selector,cropToViewport,ignoreSelectorsto all generated targets - Includes
framework: "storybook",storyId, andtagsin metadata
Capture
Section titled “Capture”interface Capture { id: string; suite: string; branch: string | null; commit: string | null; status: "pending" | "capturing" | "processing" | "completed" | "failed"; snapshots: Snapshot[]; totalSnapshots: number; completedSnapshots: number; failedSnapshots: number; partial: boolean; ephemeral: boolean; autoApprove: boolean; createdAt: string;}Snapshot
Section titled “Snapshot”interface Snapshot { name: string; viewport: { width: number; height: number } | null; imageUrl: string;}interface Diff { id: string; suite: string; status: "processing" | "completed" | "failed"; headCaptureId: string; baseCaptureId: string | null; baselineRef: string | null; isStale: boolean; targetRefs: string[] | null; isLocal: boolean; results: DiffResult[]; reviewUrl: string | null; createdAt: string;}DiffResult
Section titled “DiffResult”interface DiffResult { name: string; viewport: { width: number; height: number } | null; status: "unchanged" | "changed" | "new" | "removed" | "skipped"; diffPercentage: number | null; diffImageUrl: string | null; currentImageUrl: string; baselineImageUrl: string | null; headHash: string | null; approvalStatus: "pending" | "approved" | "rejected"; baselineSource: string | null;}SnapshotDiffResult
Section titled “SnapshotDiffResult”interface SnapshotDiffResult { name: string; suite: string; status: "new" | "changed" | "unchanged"; diffPercentage: number | null; diffId: string; captureId: string; currentS3Key: string | null; baselineS3Key: string | null; diffS3Key: string | null; path: string[] | null; approvalStatus: "pending" | "approved" | "rejected"; baselineSource: string | null;}CaptureTarget
Section titled “CaptureTarget”interface CaptureTarget { name: string; url: string; selector?: string; viewport?: { width: number; height: number }; framework?: "storybook"; delay?: number; cropToViewport?: boolean; ignoreSelectors?: string[]; path?: string[]; metadata?: Record<string, unknown>;}interface Site { id: string; project: string; status: "pending" | "uploading" | "ready" | "failed"; fileCount: number; totalBytes: number; cdnUrl: string; createdAt: string; error: string | null;}StoryEntry
Section titled “StoryEntry”interface StoryEntry { url: string; name: string; id?: string; title?: string; tags?: string[]; parameters?: { delay?: number; modes?: Record<string, Record<string, unknown>>; viewports?: number[]; disable?: boolean; selector?: string; cropToViewport?: boolean; ignoreSelectors?: string[]; diffThreshold?: number; pauseAnimationAtEnd?: boolean; forcedColors?: string; prefersReducedMotion?: string; media?: string; };}