---
title: "Public API"
description: "The three endpoints that answer without an account, one example each, and where the machine-readable description lives."
canonical: https://keepitlocal.app/docs/api
last-updated: 2026-09-13
---

# Public API

The three endpoints that answer without an account, one example each, and where the machine-readable description lives.

Three endpoints answer without an account. Everything else needs a person signed in.

The OpenAPI 3.1 description: https://keepitlocal.app/openapi.json. What needs a session: https://keepitlocal.app/auth.md.

## GET /api/certificates/{id}

Looks up a redaction certificate by its id: the SHA-256 of the file that went in and the file that came out, how many marks were burned in, the engine version, and whether the signature still verifies. The same certificate as the /verify/{id} page.

```
curl https://keepitlocal.app/api/certificates/CERT_ID

{
  "v": 1,
  "id": "CERT_ID",
  "issuer": "KeepItLocal Redact",
  "kind": "pdf",
  "input_sha256": "…64 hex characters…",
  "output_sha256": "…64 hex characters…",
  "marks": 12,
  "pages": 5,
  "engine_version": "0.5.14",
  "created_at": "2026-09-10T09:00:00.000Z",
  "signature": "…base64url Ed25519…",
  "verify_url": "https://keepitlocal.app/verify/CERT_ID",
  "valid": true
}
```

200 with the certificate; 200 with {"id", "valid": false} when a stored row no longer matches its signature; 404 not_found; 429 rate_limited with Retry-After. 60 lookups an hour per address.

## GET /api/health

One database round trip. This is what the uptime monitor polls.

```
curl https://keepitlocal.app/api/health

{ "status": "ok", "time": "2026-09-10T09:00:00.000Z" }
```

200 ok; 503 {"status": "degraded"} when the database does not answer; 429 after 600 polls an hour from one address.

## POST /api/early-access

Joins the early-access list. A duplicate address succeeds without saying so, so the list cannot be enumerated.

```
curl -X POST https://keepitlocal.app/api/early-access \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","organisation":"Example Ltd"}'

{ "ok": true }
```

200 {"ok": true}; 400 when the body is not JSON or the address is not valid; 415 when the Content-Type is not application/json; 429 after five requests an hour from one address.

## Which rate-limit headers are sent?

Every response from the three endpoints carries RateLimit-Limit (requests allowed per address in the window), RateLimit-Remaining (requests left) and RateLimit-Reset (seconds until the window resets), as integers, except the early-access 400 and 415, which are answered before the limiter runs. A 429 also carries Retry-After with the same number of seconds as RateLimit-Reset. Any path under /api with no endpoint answers 404 with a JSON body: {"error": "not_found", "docs": "https://keepitlocal.app/docs/api"}.

## How is the API versioned and deprecated?

This API has no version segment because nothing has changed shape since it was published. A breaking change gets a new path and the old one answers with Deprecation and Sunset headers for at least six months, announced in /changelog.

## What needs a human session?

A person signs in at /sign-in with an email and password, or with Google. There is no OAuth server, no agent registration and no API key today, and the developer package and the assistant connector are not released. Documents never reach this server: every endpoint above sees hashes and counts, never content. Questions: hello@keepitlocal.app.
