# Integrating GENBN

API base: https://genbn.org/api/v1
OpenAPI: https://genbn.org/openapi.json
Registration is open. lazybook.ai is the first planned official publisher, currently under development.

## Register once per publishing service

POST /services with JSON `{ "name": "Your Publisher", "email": "operator@your-publisher.com" }`.
Optional fields: `code` (1–15 letters/digits, normalized uppercase), `domain` (self-declared), and `url_template` (HTTPS URL containing `{id}` or `{genbn}`). One service per email is supported. Email verification does not verify ownership of the supplied publisher name or domain.

The response is 201 with `code` and `status: "pending"`. A human opens the verification email and explicitly confirms activation; the page shows the API key and an email copy is attempted. Alternatively POST /verify-email with `{ "token": "TOKEN_FROM_EMAIL" }` returns `api_key`. Tokens expire after 24 hours and are single-use.

If registration returns 409 `registration_pending`, POST /services/resend-verification with `{ "email": "operator@your-publisher.com" }`. For an active service with a lost key, POST /services/recover-key with the same email payload. The human confirms the emailed recovery link, rotating the key and immediately invalidating the old one. GET /verify and GET /recover display confirmation forms; GET alone does not activate or rotate credentials.

## Authentication

Store the key in backend secret configuration. Send `Authorization: Bearer YOUR_API_KEY` for minting and updates. Never place it in client-side code, logs, prompts, or source control. Browser cross-origin integration is not provided; call from your backend. Admin credentials are not publisher credentials.

## Mint once per publication

Fetch GET /categories for valid category/subcategory pairs. Example: FIC / SCFI.

```sh
curl https://genbn.org/api/v1/books \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: publication:YOUR_PUBLICATION_ID" \
  -d '{"category":"FIC","subcategory":"SCFI"}'
```

A 201 response includes `genbn` (display form with the `GENBN ` prefix), `id` (URL form without the prefix), `service_code`, UTC mint `year`, `category`, `subcategory`, `serial`, `url`, and `redirect`. Persist both `genbn` and `id` against your publication before displaying them.

The optional Idempotency-Key is 1–128 ASCII letters, digits, periods, underscores, colons or hyphens. Persist it before your first request. It is scoped to your service and retained indefinitely. Repeating a key with the same normalized category/subcategory/URL returns the original 201 response; a changed payload returns 409 `idempotency_conflict`. Requests without a key mint again on every successful request. A replay returns the original snapshot; GET the book for current data.

## Set or update the destination

```sh
curl -X PATCH https://genbn.org/api/v1/books/YOUR_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-publisher.com/books/your-book"}'
```

You can include `url` in the initial mint request instead. Per-book URLs must be public HTTP(S), at most 2000 characters, without credentials or literal IPv6 addresses. PATCH with `url: null` clears the explicit destination and falls back to the service template. Service templates are HTTPS, at most 500 characters; `{id}` substitutes the URL identifier and `{genbn}` substitutes the URL-encoded display identifier.

PATCH /services/YOUR_CODE updates `name`, `domain`, or `url_template`; a publisher can update only its own service and books. Service codes and minted identifier fields are immutable.

## Read and share

GET /books/YOUR_ID returns public metadata and the current resolved `redirect`.
GET /services/YOUR_CODE returns public service information, excluding email and secrets.
Share https://genbn.org/YOUR_ID. It returns a 302 redirect when a destination exists, a 200 pending page when registered without a destination, or 404 when absent. Keep identifiers when destinations change. Use a new identifier for a separately published edition; do not mint for each generation attempt.

## Errors and retries

Errors use `{ "error": "machine_readable_code" }`, sometimes with `hint` or `retry_after_sec`.
- 400: correct invalid input (including invalid_category_pair, invalid_url, invalid_token, token_expired).
- 401: missing/invalid publisher key. Recover credentials if necessary.
- 403: publisher does not own the resource.
- 404: unknown resource.
- 409: registration conflict or idempotency_conflict; resolve before retrying.
- 413: body too large; default JSON limit is 16 KiB.
- 415: send Content-Type: application/json.
- 429: honor Retry-After. Limits apply per IP, email, and publisher as appropriate.
- 502 email_send_failed: retry sending later; the previous resend/recovery token is preserved when delivery fails.

Use bounded exponential backoff for network failures and 5xx responses. Always reuse the mint idempotency key and payload after an uncertain response. Do not automatically retry non-idempotent minting. Test with a separate development registry and never expose test secrets.
