Private Audiobook Beta — public API guide

Plugin release 2026.10.12.001; stable API namespace tq-audio-beta/v1. Developed by Matthew X Murphy, distributed by Built Not Begged. Release owner: Taylor Quinn. GPL-2.0-or-later.

Use your own WordPress site's HTTPS origin. The base is:

https://your-author-site.example/wp-json/tq-audio-beta/v1

The example domain and bracketed values in this guide are placeholders. Every installation has its own catalog, credentials, invitations, recordings and progress. A site appearance choice never changes access or selects another site's private state. The optional Powered by BuiltNotBegged.com player-footer credit is enabled by default and can be hidden in the administrator settings. Its setting does not change API rights; turning display off uses a native hidden ancestor and scoped CSS while retaining the complete link in page HTML and source.

Authentication

In Audiobook Beta → Agent API keys, create a key for the approved integration with only its required scopes. The 64-character lowercase hexadecimal key appears once; its hash is stored. Send it only in a header:

Authorization: Bearer <your scoped service key>
ScopePurpose
manage_catalogBooks, protected uploads, chapters, verified replacements and eligible cleanup.
manage_grantsIssue, inspect and revoke tester invitations.
reviewReview reports and update review status.
notificationsRead queued issue events and acknowledge delivery receipts.
listenRead the allowed library and stream recordings.
feedbackSubmit recording-bound issue reports.

These keys are neither WordPress Application Passwords nor tester invitations. A WordPress administrator cookie does not authorize these REST endpoints; the administrator screen uses its own capability and nonce checks. Revoke an exposed service key in the same screen.

The player uses a private HttpOnly, SameSite Strict cookie with Secure on HTTPS and per-book rights. Browser writes require its current X-CSRF-Token; enrollment and return also require the exact site origin. Never put service keys, cookies or CSRF values in URLs.

A tester enrolls with an eligible private invitation and email. No authentication code or email is sent. The remembered approved browser can use that email to return to a 24-hour session, subject to the original grant's expiry and revocation. Email alone is not proof of ownership and never authorizes a new browser or device. CRM membership also does not authorize listening.

Private responses use Cache-Control: private, no-store and Referrer-Policy: no-referrer. The PWA does not cache private API or audio responses.

Routes

Paths below are relative to the API base. Identifiers are site-local IDs, not filesystem paths. JSON write bodies have an 8,192-byte limit. Ordinary write routes use JSON; the protected upload route uses multipart form data.

Method and pathAccessContract
GET /sessionAnonymous or sessionSafe authentication state; authorized session also returns permissions, CSRF and own progress.
POST /identity/enrollEligible invitation, exact originemail, invite_token; private invitation registration.
POST /identity/requestExact origin, remembered browser credentialemail; return only while the original grant remains eligible.
DELETE /sessionSession, CSRFSign out and revoke this browser's return credential; preserve saved progress.
GET /booksListen rights or explicitly enabled public viewGranted/private tracks plus public tracks; unauthorized private tracks are minimal locked teasers.
GET, HEAD /tracks/{id}/streamListen rights or enabled explicit public trackCurrent protected MP3; single byte Range supported. Optional version pins a recording.
GET, HEAD /books/{id}/coverListen rightsA protected registered cover when available.
GET /progressBound tester identityOwn latest authorized recording position or null.
PUT /progressBound tester identity, CSRFRecording identity, position, speed and optional ordered revision.
GET /feedbackOwn reports or review rightsImmutable recording/timestamp reports and current review status.
POST /feedbackFeedback rightsRecording identity, category, timestamp, note and request ID.
PATCH /feedback/{id}Review rightsstatus: open, acknowledged, resolved or dismissed.
GET, POST /grantsmanage_grantsInspect grant metadata or issue an expiring scoped invitation.
DELETE /grants/{id}manage_grantsRevoke the invitation and its sessions.
GET, POST /catalog/booksmanage_catalogRead books or register id, title, author, optional shelf.
GET, PATCH /catalog/books/{id}manage_catalogRead or edit title, author, shelf.
GET, POST /catalog/sourcesmanage_catalogList uploaded source summaries or upload multipart audio MP3.
DELETE /catalog/sources/{id}manage_catalogRetire an unused owned encrypted source; current recordings are protected.
GET, POST /catalog/tracksmanage_catalogRead chapters or guarded publication of a chapter source.
GET, PATCH /catalog/tracks/{id}manage_catalogRead identity or edit title/order with expected_version.
POST /catalog/tracks/{id}/replacemanage_catalogPublish verified corrected audio using an exact source/hash/version/operation.
POST /catalog/tracks/{id}/replacement/abortmanage_catalogAbort a pending replacement using its operation_id and current expected_version.
GET /catalog/cleanupmanage_catalogCleanup verification/retry status.
POST /catalog/cleanup/{id}/retrymanage_catalogRetry a named persisted cleanup job.
POST /catalog/cleanup/retrymanage_catalogRetry up to ten due persisted jobs.
GET /notificationsnotificationsQueued immutable issue events.
POST /notifications/{id}/acknotificationsAcknowledge an event with the integration's genuine verified receipt.

Historical POST /session returns 428 and requires email enrollment; new integrations should use /identity/enroll instead.

Upload and publish a WordPress chapter

  1. Register the book with POST /catalog/books if it does not exist.
  2. POST multipart form data to /catalog/sources with file field audio and optional expected_sha256 (the actual full-file SHA-256). The route accepts a real uploaded MP3, not a caller-supplied filesystem path or public media URL.
  3. Save the returned source.source_id, source.sha256, source.bytes, source.duration_seconds and source.provider. The provider is wordpress. Validation reports wordpress_getid3 and full_decode_verified: false; it verifies MP3 structure and positive duration, not a full decode or narration quality.
  4. Publish with POST /catalog/tracks, using the source ID and exact hash from that response.

The following illustrates the JSON shape; replace every angle-bracketed placeholder with the actual value:

{
  "id": "chapter-1",
  "book_id": "my-book",
  "title": "Chapter One",
  "number": 1,
  "provider": "wordpress",
  "source_id": "<source_id returned by upload>",
  "expected_sha256": "<full SHA-256 returned by upload>",
  "expected_version": 0,
  "operation_id": "<unique stable operation ID>"
}

Use expected_version: 0 for a new chapter. Use its current exact version when replacing it. For POST /catalog/tracks/{id}/replace, supply the new provider, source_id, expected_sha256, expected_version and operation_id; the existing book/chapter identity remains.

Uploads use bounded AES-256-GCM encrypted chunks on this WordPress host, bypassing the public Media Library. The default upload cap is 256 MiB and can be configured up to 512 MiB; PHP and hosting limits may be lower. Publication rechecks the full source before changing the current recording. No external server is contacted for this provider.

Reuse the same operation ID and identical inputs when retrying an uncertain publication. Changed details conflict. A stale version is rejected. Eligible old managed audio is retired only after the new current recording is committed and the old source is no longer used by a current chapter. Recording metadata and reports remain. GET /catalog/cleanup states whether removal actually completed; a queued cleanup is not proof of reclaimed storage.

An uploaded source that never becomes current consumes storage until explicitly retired. DELETE /catalog/sources/{id} can retire an unused owned source. Do not broadly remove storage directories; protect the current recording and any source shared by another chapter.

Invitations and recording identity

POST /grants takes display_name, books mapping book IDs to explicit rights, integer expires_days from 1–30, and optional approved email. Rights are listen, feedback and review. The response includes grant metadata and a once-returned private invitation_url. Share that URL only with the approved tester. Each intentional issuance creates a new invitation; reconcile an uncertain response before issuing another.

Library tracks include id, title, number, version, sha256, actual duration_seconds, available, a protected stream_url, and feedback permission. A preparing chapter is not a confirmed playable recording. Retired version requests return 410 instead of serving new bytes under an old identity.

Progress PUT fields are track_id, track_version, audio_sha256, position_seconds, playback_speed (0.5–3) and optional positive integer client_revision. The server validates the current recording and rights and rounds position to milliseconds. The player restores paused. After a recording changes, saved restore time resets to zero while earlier feedback retains its old recording identity. Progress is bound to enrolled tester identities, not arbitrary service keys.

Reports and notifications

POST /feedback requires track_id, track_version, audio_sha256, position_seconds, category, note and an optional request_id. Categories are audio_issue, wrong_voice, unwanted_narration and missing_effect.

Optional fields are end_seconds, expected_voice, heard_voice, narration_text and suggested_effect. End time must follow the starting position and fit the actual recording. The native API accepts notes up to 3,000 characters; the player uses a 2,000-character limit. Expected/heard voice fields allow 120 characters each, unwanted narration 1,000, suggested effect 200 and request ID 64.

An unchanged retry under the same principal and request ID reuses its result; changed details conflict. Different testers and deliberate new request IDs remain separate. Repeated categories or timestamps are not deduplicated across reports. Suggestions for whistles, train horns, air brakes or other effects do not modify the audio automatically.

Success returns a feedback ID/status and a queued notification status. Review happens in WordPress or through an approved review integration. The free plugin does not install an email, messaging or push service. A separately configured worker can read /notifications, deliver through its chosen private channel, verify its actual delivery and then acknowledge using a genuine receipt. Reusing the same receipt is safe; conflicting acknowledgments return 409. Queueing or acknowledgment alone is not external delivery proof.

Limits and error handling

Persisted limits are 600 requests per IP/minute, 240 streams per principal/minute, 12 reports per principal/minute and 90 progress writes per identity/minute. Enrollment limits are 12 per IP/minute, 30 per email/hour and 20 per invitation/hour; return limits are 20 per IP/minute and 60 per email/hour. Guarded catalog/cleanup operations have actor limits of 20 per minute. Protected MP3 uploads are limited to 10 per administrator or service actor per hour. A 429 response includes Retry-After: 60.

Errors have this shape:

{"error":{"code":"<stable code>","message":"<friendly explanation>"}}

Common statuses: 400 invalid input, 401 required/invalid private access, 403 rights/CSRF/origin denied, 404 unavailable with current access, 409 stale version or conflicting retry, 410 retired recording, 413 oversized JSON, 416 invalid byte range, 428 enrollment required, 429 throttled, 503 source unavailable/preparing.

Protect the WordPress database, encryption-key option and encrypted recordings together in backups. Keys, invitations, tester metadata, private recordings and delivery receipts do not belong in public documentation, issue reports or logs. This guide intentionally omits first-party server addresses, private deployment paths and private messaging configuration.

Public/private tracks and email access leads

The master switch defaults off, approval defaults manual and missing track access_mode defaults private. Use GET/PATCH /access/settings with manage_grants and { "public_enabled": true, "approval_mode": "manual", "expires_days": 30 }. This opts the site into public track listening and locked private teasers. It does not mark a track public. PATCH /catalog/tracks/{id} with manage_catalog, its current expected_version and access_mode (public or private) to change only that recording's visibility. Anonymous current streaming requires both explicit public mode and the enabled master; changing either blocks subsequent anonymous requests. No private recording/provider paths or contacts appear in public responses.

An exact-origin POST /access/requests accepts email, explicit track_ids (1–200 IDs) and optional strict boolean marketing_opt_in (false by default). The browser receives a separate unguessable HttpOnly request cookie and calls GET /access/request for its own status. An approved request activates only its selected-track grant, never all grants registered to the same email. The UI refetches /session. Public listeners do not gain private feedback or personal progress rights.

GET /access/requests requires manage_grants and returns private email/consent details. PATCH /access/requests/{id} with action (approve, deny, revoke) and optional expires_days (1–30) manages the request. Automatic mode approves new requests immediately and does not verify mailbox ownership; manual mode remains the default. Existing named invitation enrollment and cookie-bound email return are still supported.

Consent wording/version/timestamp stays with the request. Checking coupons/updates is independent of access. Unchecked is not marketing permission; no campaign, approval email, code or forced CRM resubscription is sent. Your marketing integration must preserve unsubscribe/bounce/complaint suppression. Appearance colors are configured only in the administrator UI and never change API access. All API/audio responses stay no-store; the PWA caches no recordings.

Explicit page selections and unlisted books

The new explicit view and books parameters preserve the unparameterized API contract. Public reads ignore private credentials and project only selected public tracks; progress is local, and public write requests are forbidden. Private selections require actual grants even with public listening enabled. Catalog book creation/editing accepts optional strict boolean unlisted; true hides unauthorized book/cover listings without changing actual grants. See the separate Music guide for examples.