Developer documentation
GitHubOpenAPI
Unofficial, inspectable API adapter

Build with the addon commons.

Reference material for Wayrest Workshop’s same-origin API, the observed Bethesda protocol beneath it, and safe addon publishing workflows.

Before you integrate

This is not an official Bethesda SDK. The public adapter is pre-1.0, upstream behavior can change without notice, and the archive upload handshake remains inferred. Never embed a Bethesda application key in browser code.

QUICKSTART

Search the public catalog

The catalog endpoint is public and returns normalized Bethesda response data. Query parameters are forwarded through a conservative allowlist.

const params = new URLSearchParams({
  text: "crafting",
  page: "1",
  size: "20",
  sort: "utime",
  order: "desc"
});

const response = await fetch(
  `https://eso-addon-uploader.bryantjames.com/api/bethesda/catalog?${params}`
);
const { data } = await response.json();

TRUST & SECURITY

What crosses each boundary

01

Browser → Workshop

Credentials are sent over HTTPS only during login. The password is not persisted.

02

Workshop → Bethesda

The server adds its application key and forwards the minimum request required.

03

Session storage

The token stays in an HttpOnly, Secure, SameSite=Lax cookie unavailable to client JavaScript.

Do not send authentication requests from third-party origins or build a credential-collecting client around this deployment. Self-host when you need a different trust boundary.

EVIDENCE LABELS

Read confidence before code

Confirmed
Exercised successfully against the live API with expected results.
Observed
Request shape was captured or exercised, but edge cases remain undocumented.
Inferred
Constructed from adjacent traffic or response clues; treat as experimental.

AUTHENTICATION

Cookie-based author sessions

Login accepts JSON and sets the session cookie on success. Browsers include it automatically on later same-origin author operations.

await fetch("/api/bethesda/login", {
  method: "POST",
  headers: { "content-type": "application/json" },
  credentials: "same-origin",
  body: JSON.stringify({
    username: "your-bethesda-username",
    password: "your-password"
  })
});
Never log the request body.

Passwords, cookies, session tokens, app keys, and presigned URLs must be redacted from diagnostics and traffic captures.

API REFERENCE

Endpoints

GET/api/bethesda/catalogConfirmed

Search and paginate the public ESO addon catalog.

GET/api/bethesda/download?id={content_id}Confirmed

Reconstruct the latest Windows release as a ZIP.

POST/api/bethesda/loginObserved

Exchange Bethesda credentials for a protected session cookie.

POST/api/bethesda/logoutObserved

Invalidate the upstream session and clear the local cookie.

GET/api/bethesda/meConfirmed

List addons owned by the authenticated author.

POST/api/bethesda/addonsConfirmed

Create an unpublished addon draft.

PUT/api/bethesda/addons/{id}Observed

Update metadata for an owned addon.

POST/api/bethesda/uploadInferred

Initiate, transfer, and complete a ZIP upload.

ERRORS

Predictable failure envelopes

Adapter errors use an error string. Experimental upload errors also include a phase so clients can distinguish initiation, binary transfer, schema drift, and completion failures.

{
  "error": "Bethesda initiated the upload but returned an unfamiliar response.",
  "phase": "initiate-schema"
}
400

Invalid or missing input

401

Missing or expired author session

413

Archive exceeds 200 MB

502/503

Upstream failure or server configuration missing

HOW TO

Search and filter addons

  1. Send GET /api/bethesda/catalog.
  2. Add text, categories, or author_displayname.
  3. Paginate with page and size; keep page sizes modest.
  4. Use hardware_platforms to narrow the default cross-platform results.

HOW TO

Download an addon ZIP

  1. Read an addon’s content_id from catalog results.
  2. Navigate to /api/bethesda/download?id=CONTENT_ID.
  3. The adapter finds the latest Windows manifest, downloads its files, and returns a reconstructed ZIP.

The archive route is memory-bound. Do not use it as a bulk mirror or bypass upstream rate and licensing constraints.

HOW TO

Create and update a draft

  1. Authenticate in the same browser session.
  2. POST title, overview, description, and category to /api/bethesda/addons.
  3. Store the returned content_id.
  4. PUT later metadata changes to /api/bethesda/addons/CONTENT_ID.
const draft = await fetch("/api/bethesda/addons", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    title: "My Addon",
    overview: "A short public summary",
    description: "Full description",
    category: "User Interface"
  })
}).then(response => response.json());

HOW TO · EXPERIMENTAL

Upload a release archive

Keep a local copy.

The upstream initiate/PUT/complete sequence is inferred and may change. A failed package upload does not roll back an already-created metadata draft.

  1. Create FormData containing archive, addonId, version, and note.
  2. POST it to /api/bethesda/upload from an authenticated session.
  3. Inspect both HTTP status and any returned phase.
  4. Verify the release in the author dashboard before considering it complete.

HOW TO

Self-host the complete stack

git clone https://github.com/the-jolly-green-bryant/eso-addon-uploader.git
cd eso-addon-uploader
cp .env.example .env.local
npm ci
npm test
npm run dev

Production requires AWS credentials, a Bethesda application key, and Cloudflare DNS credentials. Review the repository README for the OIDC and SST deployment setup.