Koldera BETA

Developers / SDK

Build document checks into your workflow.

Server integrations and scoped browser sessions.

Packages in preview

Six surfaces, one API

  • .NET

    Preview

    Server · .NET 8 or .NET 10 (net8.0; net10.0)

    Koldera.Sdk

  • Node.js

    Preview

    Server · Node.js 22 or newer (ESM)

    @koldera/sdk (entry @koldera/sdk/node)

  • Browser JS

    Preview

    Browser · Modern browsers (ESM); same package as Node.js, separate entry

    @koldera/sdk (entry @koldera/sdk/browser)

  • React

    Preview

    Browser · React 19 with the browser entry of @koldera/sdk

    @koldera/sdk-react

  • Python

    Preview

    Server · Python 3.11 or newer (sync API over httpx)

    accessibility-platform (import accessibility_platform)

  • Go

    Preview

    Server · Go 1.24 or newer

    github.com/makulatura78/koldera-accessibility/sdk/go

Server integration

A predictable request lifecycle

  1. Create a projectSign in, create or join an organization, then a project. A project has a publishable identifier (ap_pk_…) and any number of secret keys.
  2. Create a secret keyMade once on the project page and shown once. It lives on your server or in CI secrets — never in a browser, a repository or a NEXT_PUBLIC_ variable.
  3. InstallPick your language below. An install command appears only once the package is verifiably available from the named registry.
  4. Submit a checkCreate a PDF job with the existing API and wait for its terminal state; a failed job is a normal return value, not an exception.
  5. Read the resultProcessing status, the PDF/UA machine check, findings and the manual-review flag are four separate things — see below.

Pick a language

ServerNot published.NET 8 or .NET 10 (net8.0; net10.0)
Package
Koldera.Sdk 0.1.0 — NuGet
Credential
Project secret key (ap_sk_…) from a server environment variable or secrets manager.
Reference
sdk/dotnet/README.md in the source repository (access-restricted; not public documentation).

Install

No public install command: this package is not on NuGet yet. Developers with access to the repository can build it from source as described in its README; everyone else can read the example below and wait for the beta announcement on the pricing page.

First check: one PDF, the terminal outcome, findings and manual review

Preview — requires beta package access

using Koldera.Sdk;

using var client = new AccessibilityClient(new AccessibilityClientOptions
{
    SecretKey = Environment.GetEnvironmentVariable("KOLDERA_SECRET_KEY"), // server-side only
    BaseUrl = new Uri(Environment.GetEnvironmentVariable("KOLDERA_API_URL")!),
});

await using var pdf = File.OpenRead("sample.pdf");
var job = await client.Pdf.CreateJobAsync(pdf, PdfJobModes.Analyze, fileName: "sample.pdf", cancellationToken: ct);
var done = await client.Jobs.WaitForCompletionAsync(job.Id, new WaitOptions { Timeout = TimeSpan.FromMinutes(5) }, ct);

if (done.State == JobStates.Failed)               // a failed job is returned, not thrown
    throw new InvalidOperationException($"{done.Error!.Code}: {done.Error.Message}");

// "completed" means processing finished; the machine gate is a separate value.
Console.WriteLine($"PDF/UA machine check: {done.Validation?.Status}"); // passed | failed | error | not_run
Console.WriteLine($"Manual review required: {done.ManualReviewRequired}"); // always true

foreach (var finding in await client.Jobs.ListFindingsAsync(job.Id, ct))
    Console.WriteLine($"{finding.RuleId}: {finding.Message}");

foreach (var artifact in await client.Jobs.ListArtifactsAsync(job.Id, ct))
{
    await using var file = File.Create(artifact.Kind + ".bin");
    await client.Artifacts.DownloadAsync(artifact, file, ct); // SHA-256 verified
}

completed only says processing finished. Read validation.status for the PDF/UA machine check (passed, failed, error or not_run), the findings for what was verified, and remember that manualReviewRequired stays true: no result is a PDF/UA or WCAG conformance statement.

Also available on this surface

  • HtmlToPdf.CreateJobAsync
  • Web.CreateScanAsync
  • Jobs.GetReadingOrderAsync
  • Jobs.RemediateReadingOrderAsync
  • ClientSessions.CreateAsync

Server versus browser credentials

Your backend holds a project secret key (ap_sk_…) and can create jobs directly. A user’s browser never holds that key — it only ever gets a short-lived session your backend mints for it. This website’s own free tools are a separate, keyless anonymous surface, not a keyless SDK tier.

Full credentials tableExactly what each surface holds and can do
Which credential each surface holds
Runs inHoldsCan create jobs?
Your backend (.NET, Node.js, Python, Go)Project secret key ap_sk_…Yes, on its project, within the organization's plan.
A user's browser (Browser JS, React)Publishable identifier ap_pk_… plus a session tokenOnly with a session your backend minted through POST /v1/client-sessions after authenticating its own user: origin-bound, scoped, job-budgeted, expiring within ten minutes. The identifier alone is refused (401).
This website's free toolsA Turnstile-protected anonymous sessionFive checks per day per network location on this site only. This is a different surface, not a keyless SDK tier.

Common answers from the API

Checking whether this deployment queues web page checks…

Status codes and what to do401, 402, 403, 409, 429, 503 — the full reference table
HTTP status codes and what to do
StatusMeaningWhat to do
401 UnauthorizedInvalid, revoked or missing secret key; a browser session that expired, was used from another origin or is only a publishable key.Rotate the key on the project page; mint a fresh session from your backend.
402 Payment requiredThe organization has no usable entitlement: no credits left, or no active subscription.The response carries upgradeUrl; plans are on the pricing page.
403 ForbiddenThe key or session lacks the scope (for example a session without order:remediate).Create a key or session with the scope the call needs; never widen a browser session beyond what the page does.
409 ConflictIDEMPOTENCY_CONFLICT (same key, different body), STALE_READING_ORDER_SNAPSHOT or ARTIFACT_EXPIRED.Use a fresh idempotency key per distinct request; reload the snapshot and re-approve; download links and files expire after 24 hours.
429 Too many requestsConcurrency, rate or the plan's fair-use daily cap.Honour Retry-After / resetAt (UTC); the SDKs already retry idempotent reads twice.
503 Service unavailableA capability is switched off on the deployment: WEB_CHECKS_UNAVAILABLE, HOSTED_CHECKS_UNAVAILABLE.Nothing was reserved or charged; PDF and HTML jobs are unaffected by a web-check outage.

Limits and billing

SDK jobs draw on the organization's plan: job credits, or a subscription with a fair-use daily cap per plan. Page, browser-time and concurrency limits come from the plan; the current configured prices and caps are on the pricing page, not in this documentation, so they never go stale here. During the beta an organization may run on an internal allowance instead. A job that fails on our side releases its reservation; a rejected request costs nothing.

What machine validation does not prove

manualReviewRequired is always true: a completed job or a passing machine check is never a PDF/UA or WCAG conformance statement.

What each field actually meansstate, validation.status, manualReviewRequired and the fix boundary
  • state: completed means the job finished processing. It says nothing about the document.
  • validation.status is the PDF/UA-1 machine check by veraPDF: passed, failed, error or not_run. A completed job with failed is the normal case for many real documents — and for every Chromium-generated PDF today, which lacks the metadata the standard requires.
  • A tagged structure tree and a passing machine check do not show that image descriptions are meaningful, that the reading order matches the meaning, or that the document conforms to PDF/UA or WCAG. manualReviewRequired is therefore always true.
  • Safe fixes are limited to two metadata rules applied to a copy; reading-order corrections happen only for an explicitly approved, hash-bound snapshot (acknowledged: true). No SDK derives an order or an alt text on its own.

Questions developers ask

Frequently asked questionsAPI-only access, browser sessions, URL conversion, CI gating, polling, docs, MCP
Can I call the API without an SDK?
Yes — the SDKs are thin clients over the versioned HTTP API described in contracts/openapi.yaml (access-restricted source repository). Send Authorization: Bearer ap_sk_…, a fresh Idempotency-Key on every creation call, and poll the job.
Can the browser SDK work with just the publishable key?
No. It identifies the project only; jobs need a session that your backend mints for an authenticated user.
Can I convert a URL to PDF?
No. HTML to PDF takes one self-contained static HTML file (scripts are refused, remote assets are not fetched); use contentSelector to print only part of the page.
Can I gate a template in CI?
Yes, with the server SDK. The repository's examples/ci-gate renders your template, runs one HTML → PDF job, compares the machine-verifiable findings with a committed baseline and exits 0 (no new machine finding), 1 (regression) or 2 (usage or API error); manual-review findings are listed and never fail the build. It can also write a versioned JSON report, and source file/line locations appear only when your build supplies a verified source map — never guessed from the rendered page. Notifications are polling-based; there are no webhooks.
Does a job keep running if my process dies while waiting?
Yes. Waiting is client-side polling; the job finishes server-side. Its record and findings can be read back later; download links and files expire after 24 hours.
Where is the reference documentation?
In each SDK's README in the source repository, alongside golden contract fixtures. There is no public documentation site yet; this page is the public entry point.
Can an AI assistant read my jobs directly?
Yes, read-only, through the hosted MCP connection (beta) — a separate, OAuth-bound surface from the SDKs above, scoped to one project and unable to start or pay for a check.

Server keys stay on your backend. Browser sessions are scoped and expire after 10 minutes.