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.
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
Browser → Workshop
Credentials are sent over HTTPS only during login. The password is not persisted.
Workshop → Bethesda
The server adds its application key and forwards the minimum request required.
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"
})
});Passwords, cookies, session tokens, app keys, and presigned URLs must be redacted from diagnostics and traffic captures.
API REFERENCE
Endpoints
/api/bethesda/catalogConfirmedSearch and paginate the public ESO addon catalog.
/api/bethesda/download?id={content_id}ConfirmedReconstruct the latest Windows release as a ZIP.
/api/bethesda/loginObservedExchange Bethesda credentials for a protected session cookie.
/api/bethesda/logoutObservedInvalidate the upstream session and clear the local cookie.
/api/bethesda/meConfirmedList addons owned by the authenticated author.
/api/bethesda/addonsConfirmedCreate an unpublished addon draft.
/api/bethesda/addons/{id}ObservedUpdate metadata for an owned addon.
/api/bethesda/uploadInferredInitiate, transfer, and complete a ZIP upload.
Machine-readable details, parameters, bodies, and response schemas are in the OpenAPI 3.1 document.
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"
}Invalid or missing input
401Missing or expired author session
413Archive exceeds 200 MB
502/503Upstream failure or server configuration missing
HOW TO
Search and filter addons
- Send
GET /api/bethesda/catalog. - Add
text,categories, orauthor_displayname. - Paginate with
pageandsize; keep page sizes modest. - Use
hardware_platformsto narrow the default cross-platform results.
HOW TO
Download an addon ZIP
- Read an addon’s
content_idfrom catalog results. - Navigate to
/api/bethesda/download?id=CONTENT_ID. - 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
- Authenticate in the same browser session.
- POST title, overview, description, and category to
/api/bethesda/addons. - Store the returned
content_id. - 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
The upstream initiate/PUT/complete sequence is inferred and may change. A failed package upload does not roll back an already-created metadata draft.
- Create
FormDatacontainingarchive,addonId,version, andnote. - POST it to
/api/bethesda/uploadfrom an authenticated session. - Inspect both HTTP status and any returned
phase. - 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 devProduction requires AWS credentials, a Bethesda application key, and Cloudflare DNS credentials. Review the repository README for the OIDC and SST deployment setup.