The commercial boundary
Latchshot renders. Your product stays the product.
The integration belongs behind your trusted server. A customer calls your API with your credential. Your application authorizes that customer, checks its own cache and plan, asks Latchshot for uncached public-page bytes, stores the artifact, records usage, and returns its own response contract.
Good backend fit
Direct bytes are already the seam.
- Input
- One public HTTP or HTTPS URL on port 80 or 443
- Output
- Synchronous PNG or JPEG bytes your code already stores or returns
- Controls
- Bounded viewport, full page, format, quality, wait, delay, and cleanup
Keep another backend
The provider owns more than rendering.
- Delivery
- Hosted or signed image URLs, async jobs, webhooks, or provider storage are required
- Page state
- Authentication, cookies, arbitrary headers, scripts, clicks, custom or multi-step scrolling, or selectors are required
- Reach
- Private networks, non-standard ports, proxies, regions, or anti-bot behavior are required
This page documents a technical architecture. It does not claim that any screenshot product uses Latchshot, that the plans guarantee margin, or that Latchshot replaces your obligations to customers. Review your own terms, support load, tax, refunds, storage, transfer, and availability commitments.
Return a validated buffer from one server-only function.
Keep the Latchshot key in a server-only environment variable. Bound the caller deadline, require the expected content type, reject oversized artifacts, and preserve provider diagnostics without logging the credential or target URL.
const maxArtifactBytes = 15 * 1024 * 1024;
export async function renderPublicPage(url: string) {
const response = await fetch("https://latchshot.fly.dev/v1/render", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.LATCHSHOT_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
url,
kind: "screenshot",
format: "png",
width: 1440,
height: 900,
fullPage: false,
waitUntil: "domcontentloaded",
timeout: 20_000,
}),
signal: AbortSignal.timeout(25_000),
});
if (!response.ok) {
const detail = (await response.text()).slice(0, 500);
throw new Error(`render failed (${response.status}): ${detail}`);
}
const contentType = response.headers.get("content-type") ?? "";
if (!contentType.startsWith("image/png")) {
throw new Error(`unexpected artifact type: ${contentType || "missing"}`);
}
const bytes = Buffer.from(await response.arrayBuffer());
if (bytes.length === 0 || bytes.length > maxArtifactBytes) {
throw new Error(`artifact size outside caller limit: ${bytes.length}`);
}
return {
bytes,
renderMs: response.headers.get("x-latchshot-render-ms"),
remaining: response.headers.get("x-quota-remaining"),
};
}
The response body is the artifact, not a hosted URL. Keep your existing object-storage upload, CDN URL, base64 conversion, or binary API response after this function.
Do not expose this function as an unauthenticated proxy. Validate your customer credential, enforce your plan and rate limit, normalize the target contract, and check your cache before calling the upstream renderer.
Keep six product decisions on your side.
- Customer authentication: accept only your own API key or session. Never forward a customer-supplied Latchshot key.
- Request policy: define formats, dimensions, deadlines, and target rules that your product supports. A smaller public contract is easier to price and support.
- Cache identity: hash every output-affecting option, not just the URL. Decide freshness and invalidation explicitly.
- Artifact ownership: upload validated bytes to your storage before returning a hosted URL. Set retention, content type, and access policy there.
- Usage accounting: count customer usage according to your contract. Record a successful uncached render only after the artifact is safely committed.
- Billing and support: your plan, checkout, tax, refunds, service promises, and customer communication remain separate from Latchshot.
| Responsibility | Your product | Latchshot |
|---|---|---|
| Customer identity and entitlement | Owns and enforces | Not aware of your customer |
| Render target | Submits a permitted public URL | Revalidates public-network policy |
| Artifact | Stores, names, retains, and delivers | Returns transient image bytes |
| Customer quota | Defines and records | Enforces your upstream key allowance |
| Payment | Owns checkout and settlement | No automatic customer or overage charge |
| Support contract | Owns customer-facing promises | Exposes bounded API behavior and diagnostics |
On a narrow screen, scroll the responsibility table horizontally.
Model cost per successful uncached render.
Latchshot counts successful direct renders. Failed direct renders do not consume monthly quota, and automatic overages are disabled. Cache hits in your application do not reach Latchshot. At full included usage, the current list-price input is:
| Plan | Successful renders / UTC month | Monthly price | Price ÷ full allowance |
|---|---|---|---|
| Free | 100 | $0 | $0 |
| Launch | 2,500 | $19 | $0.0076 |
| Build | 12,000 | $49 | about $0.0041 |
| Scale | 50,000 | $149 | about $0.0030 |
On a narrow screen, scroll the plan table horizontally.
eligible_uncached_successes
= customer_successes × (1 - measured_cache_hit_rate)
gross_margin
= customer_revenue
- upstream_plan_cost
- storage_and_transfer
- other_variable_compute
- refunds_and_support_cost
The full-allowance division is not a metered rate, a guaranteed saving, or an overage price. Underused allowance raises the effective cost. Jobs outside Latchshot's boundary still need another provider and belong in the same model.
Do not collapse upstream failure into a customer 500.
Map failures deliberately. Preserve a short upstream code and request ID in internal diagnostics, but return your own stable error schema without exposing a credential, raw authorization header, customer URL, or unrestricted provider body.
- Permanent until input changes:
400,401,403, unsupported target, or option-validation failures. - Bounded retry candidates:
429,502,503, and504, only while the caller deadline still has room. - Quota exhausted: stop upstream work and return your product's explicit capacity state. Do not silently bill an overage.
- Artifact rejected: do not store or count a zero-byte, wrong-type, or caller-oversize response.
A fallback provider is useful only if it preserves the same product contract. Exercise it under a known upstream failure before depending on it.
Keep the current provider when it owns the workflow.
Latchshot is not a browser-session platform or hosted-artifact service. A split-provider backend is the correct design when only part of your traffic fits.
- Direct browser embedding, query-string credentials, or customer-side capture.
- Provider-hosted or signed image URLs, async jobs, callbacks, webhooks, or provider storage.
- Cookies, login state, arbitrary headers, authenticated pages, private previews, or private networks.
- Inline HTML, scripts, actions, clicks, typing, custom or multi-step scrolling, selectors, element clips, or multi-step navigation. Bounded full-page lazy-content activation is available separately through
scrollPage. - CAPTCHA solving, stealth behavior, proxy rotation, browser regions, or anti-bot bypass.
- Video, animated output, WebP, extraction, Lighthouse, or other non-render browser APIs.
A migration-fit issue or implementation-pilot request must use a safe public sample and non-secret contract details. Never include an API key, authorization header, private or signed URL, customer data, payment details, or sensitive artifact.
Prove one seam before moving traffic.
- Inventory the exact current provider request and mark every job that hits the stop list.
- Create a public acceptance set covering ordinary, font-heavy, animated, tall, consent-overlay, slow, and known-failure pages.
- Compare image dimensions, visible content, fonts, cleanup, bytes, and end-to-end time at identical settings.
- Unit-test authorization-before-render, cache hits, quota exhaustion, content-type rejection, size rejection, permanent errors, bounded retries, storage failure, and usage commit.
- Route a bounded share of eligible uncached jobs. Keep the former provider path until rollback is exercised.
The $99 implementation pilot covers one eligible JavaScript, TypeScript, or Python backend call site in a public GitHub repository you control, with a focused patch, tests, approved public acceptance sample, and rollback note. API usage is separate. The request takes no payment and authorizes no work; the owner separately confirms scope, payment, and start.
Test the render seam, not the whole business.
Use the recurring Free allowance for representative public pages. Keep customer identity, artifacts, quotas, billing, and support in your product.