Documentation
REST API: Getting Started
Introduction

The Mordel.Net REST API gives your own programs and apps read-only access to the units designed on Mordel.Net: BattleMechs, Combat Vehicles, IndustrialMechs and ProtoMechs. You can list units, read every detail of a design, and download the same files the website offers, such as record sheets, Alpha Strike cards, MegaMek files and the Technical Readout.

At a Glance
Base URLhttps://sandbox.mordel.net/api/v1
MethodsGET only. The API does not create, change or delete anything.
FormatJSON, encoded as UTF-8 (Content-Type: application/json; charset=utf-8). File downloads have their own content type.
AuthenticationAn API key in the X-API-Key header. See Authentication.
Dates and timesUnix timestamps: whole seconds since January 1, 1970 UTC.
Missing valuesA field that has no value is null; fields are never left out.
CachingEvery response is sent with Cache-Control: no-store.
Using the API from a web page:

The API can be called from JavaScript on any website. Every response, including errors, carries Access-Control-Allow-Origin: *, and browsers may read the Content-Disposition and Retry-After headers. The browser's preflight check (OPTIONS) succeeds without a key and allows the X-API-Key header. Requests are not sent with cookies, so do not set credentials: 'include'.

Unit images (image_url) are also served with Access-Control-Allow-Origin: *, so an app can draw them on a canvas by loading them with crossOrigin = "anonymous".

Quick start

Four steps take you from no key to your first list of units:

  1. Create a key on the API Key page (log in, click your username next to Logged in as to open your profile, then click API Key). Copy it straight away: for your security it is only shown once.
  2. Check that the API is reachable. This needs no key:
    curl "https://sandbox.mordel.net/api/v1/ping"
  3. Check your key:
    curl -H "X-API-Key: YOUR_API_KEY" "https://sandbox.mordel.net/api/v1/key"
  4. List the first five BattleMechs:
    curl -H "X-API-Key: YOUR_API_KEY" "https://sandbox.mordel.net/api/v1/units?type=bm&limit=5"

The same request from JavaScript:

const response = await fetch('https://sandbox.mordel.net/api/v1/units?type=bm&limit=5', {
	headers: { 'X-API-Key': apiKey }
});
const data = await response.json();
if (data.status !== 'OK') throw new Error(data.message);
console.log(data.units);
Authentication

Every endpoint except /ping needs an API key, sent in the X-API-Key header:

X-API-Key: mn_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
  • Keys are made on the API Key page (log in, click your username next to Logged in as to open your profile, then click API Key). A key is mn_ followed by 64 lowercase hexadecimal characters.
  • Each member has one key at a time. Replacing it or revoking it stops the old key working immediately.
  • The site only stores a fingerprint of your key, so a lost key can't be shown again. Replace it to get a new one.
  • A missing, malformed, replaced or revoked key returns 401 Unauthorized with a WWW-Authenticate: X-API-Key header.
  • Never put a key in the URL. A request with a key in its address, or with a parameter named key, apikey, api_key, x-api-key or x_api_key, is refused with 400 Bad Request and is not logged with the key. Replace a key that has been exposed this way.
Keep your key private. Your key also gives access to your own semi-private and private designs. In an app that runs in the browser, each person should enter their own key; never publish yours in a page or in source code.
Rate limits

Each key may spend 120 points in any 60 seconds. Most requests cost 1 point, and files that take the server longer to build cost more:

Request Costs
RequestCost
GET /pingFree, and not counted (no key is sent)
GET /key, GET /units, GET /units/{type}/{id}1
GET /units/ids2
File: record_sheet4
File: alpha_strike_card10
File: megamek and each of the five texts1

For example, in one minute a key could read 120 units, download 12 Alpha Strike cards, or download 30 record sheets. The cost is the same for every unit type, and the quantity of cards or ProtoMechs on a page doesn't change it.

When a request would go over the limit, the API returns 429 Too Many Requests with a Retry-After header giving the number of seconds to wait. That is how long it takes for enough of your earlier requests to leave the 60-second window. A request turned away like this costs nothing, so an app that waits as told always gets back in.

HTTP/1.1 429 Too Many Requests
Retry-After: 14
Content-Type: application/json; charset=utf-8

{"status":"Error","message":"Rate limit exceeded. Try again in 14 seconds."}

Record sheets and Alpha Strike cards are also built only a few at a time: at most 2 at once for each key, and 4 at once across all keys. A request beyond that returns 429 with Retry-After: 2 and the message Too many files are being generated at once. Try again in a moment. Downloading files one after another, rather than all at once, avoids this.

async function apiGet(url, apiKey) {
	for (;;) {
		const response = await fetch(url, { headers: { 'X-API-Key': apiKey } });
		if (response.status !== 429) return response;
		const seconds = Number(response.headers.get('Retry-After')) || 2;
		await new Promise(resolve => setTimeout(resolve, seconds * 1000));
	}
}
Errors

Errors use standard HTTP status codes, and the body is always JSON with status set to Error and a message that explains what to change:

{"status":"Error","message":"Invalid limit. Use a number from 1 to 1000."}

Successful responses have status set to OK.

Status Codes
StatusMeaningExamples
200 OKThe request worked.
400 Bad RequestA parameter is unknown or has an invalid value, or the key was put in the URL.Unknown parameter: colour. Allowed parameters are owner, type, created_since, updated_since, limit, cursor.
Invalid type. Use one or more of bm, cv, im, pm, separated by commas.
401 UnauthorizedThe key is missing, malformed, replaced or revoked.An API key is required. Send it in the X-API-Key header.
Invalid API key
404 Not FoundThe endpoint, unit or file format doesn't exist, the unit isn't visible to your key, or a text hasn't been stored for the unit yet.Endpoint not found
Unit not found
This text is not available for this unit yet. It is added the next time the unit is saved.
405 Method Not AllowedA method other than GET was used. The Allow header lists GET, OPTIONS.Method not allowed
422 Unprocessable ContentThe unit can't be produced in the requested format.MegaMek does not support FrankenMech units.
429 Too Many RequestsOver the rate limit, or too many files are being built at once. Wait for the number of seconds in Retry-After.Rate limit exceeded. Try again in 14 seconds.
500 Internal Server ErrorSomething went wrong on the server. Errors are logged and checked, so trying again later is the best course.Internal server error
503 Service UnavailableThe site is being updated and unit data is briefly unavailable. Try again later.Unit data is unavailable while the site is being updated. Try again later.

Parameter names are checked, so a misspelled filter is reported rather than silently ignored.

Which units you can see

The API shows the same designs you would see on the website when logged in:

  • Lists (/units and /units/ids) contain every public approved unit, your own public units included.
  • With owner=me, the lists contain only your own approved units, at every privacy level: public, semi-private and private.
  • A single unit and its files (/units/{type}/{id}) are available for any public approved unit, and for any of your own approved units whatever their privacy.
  • Other members' semi-private and private units, and units that are pending, rejected or deleted, are never returned. Asking for one returns 404 Unit not found, exactly as for a unit that doesn't exist.

Each unit's privacy field is public, semi_private or private.

Keeping a copy up to date

An app that keeps its own copy of the units can stay current with a few requests a day:

  1. First time: list every unit with GET /units (see Pagination), and save the server_time from the first page.
  2. Each later sync: list again with updated_since= the saved time. This returns units created or changed since then. Update your copy, then save the server_time from the first page of this sync for next time.
  3. Removals: call GET /units/ids and remove from your copy any unit whose ID is not in the list. Units that are deleted or made private simply stop appearing in /units, so this is the only way to find them.
  4. Details: fetch GET /units/{type}/{id} only for units that are new or whose updated time has changed.

server_time is taken before the units are read, so a unit saved during your sync is picked up next time. A unit's ID is unique only within its type, so store units by type and ID together, for example bm-43. Once a day is plenty for most apps.

The five texts (Technical Readout and the breakdowns) can change without the unit's updated time changing. If your app keeps copies of them, fetch them again now and then. See Known limitations.