MarketDB API: Difference between revisions

No edit summary
Muhbot (talk | contribs)
Document LIVE private API keys, complete datasets, filters and replica-only refresh; clarify public caching and authentication.
Line 1: Line 1:
== MuhRO Web API ==
== MuhRO Web API ==


The MuhRO Web API serves pre-generated JSON snapshot files from the control panel. There are two parts:
The MuhRO Web API serves JSON snapshots from the control panel. There are three parts:


* '''MarketDB API''': vending, buying and sales trend data. Snapshots are refreshed about every 5 minutes.
* '''MarketDB API''': vending, buying and sales trend data. Snapshots are refreshed about every 5 minutes.
* '''Game DB API''': item and monster data as shown on the item and monster pages, including drops, drop chances, NPC shops, spawns and item groups. Snapshots are rebuilt when the game database is updated, which is about once a week.
* '''Game DB API''': item and monster data as shown on the item and monster pages, including drops, drop chances, NPC shops, spawns and item groups. Snapshots are rebuilt when the game database is updated, which is about once a week.


Both parts are snapshot-based:
* '''Private Account API''': your own characters, inventories, carts, account/master storages and vending/buying shops. Requires a scoped private key. Background snapshots refresh every minute.
* API requests do ''not'' execute live database queries.
 
* Responses are served from JSON files and carry cache headers, so clients should honour <code>ETag</code> and <code>Cache-Control</code>.
All three parts serve snapshots without querying the game database on API requests. Public MarketDB and Game DB responses support cache validation with <code>ETag</code> and <code>Cache-Control</code>. Private responses require authorization on every request and use <code>Cache-Control: private, no-store</code>.


== Base URL ==
== Base URL ==
Line 16: Line 16:
</pre>
</pre>


All endpoints use the query form. The <code>key</code> parameter is accepted everywhere but currently not required.
All endpoints use the query form. Public MarketDB and Game DB endpoints currently do not require a key. The Private Account API requires an <code>Authorization: Bearer</code> or <code>X-API-Key</code> header and rejects keys in URLs. Use the canonical <code>https://flux.muhro.eu/</code> address directly to avoid redirects when sending credentials.


== MarketDB API ==
== MarketDB API ==
Line 256: Line 256:
  "rate_raw":7000,"rate":93.37,"steal":true,"option":null,"index":0}
  "rate_raw":7000,"rate":93.37,"steal":true,"option":null,"index":0}
</pre>
</pre>
== Private Account API ==
The Private Account API is available on LIVE. It gives a tool read access to the data owned by the master account that created its key. It cannot change game data or access another owner's data.
=== Create a private key ===
# Open [https://flux.muhro.eu/?module=master&action=apikeys Private API keys] or follow the Private API keys link on your master account page.
# Sign in with your master account email and password. This uses a separate 15-minute session.
# Enter a label, select the required read scopes and choose an expiry of 7, 30, 90 or 365 days.
# Copy the key immediately. The full key is shown only once.
# Allow up to one minute for the first background refresh before requesting data.
You can have up to ten active keys. Revoke a key in the same manager when a tool no longer needs access. Revocation takes effect for subsequent requests immediately. Keep keys out of URLs, logs, public websites and distributed application binaries. An existing management session can revoke keys during a replica outage.
=== Endpoint and authentication ===
<pre>
GET https://flux.muhro.eu/?module=api&action=private&name=<dataset>
Authorization: Bearer YOUR_PRIVATE_KEY
</pre>
Alternatively send <code>X-API-Key: YOUR_PRIVATE_KEY</code>. Send keys only in headers over HTTPS. A conflicting pair of authentication headers is rejected. The <code>key</code> and <code>token</code> query parameters are rejected.
Example with the key already set in an environment variable:
<pre>
curl --header "Authorization: Bearer $MUHRO_PRIVATE_KEY" \
  'https://flux.muhro.eu/?module=api&action=private&name=inventories'
</pre>
=== Datasets and scopes ===
The <code>name</code> parameter also names the read scope required on the key.
{| class="wikitable"
! name / scope !! Complete dataset
|-
| <code>characters</code> || Current owned characters with account IDs, levels, saved zeny and online status.
|-
| <code>inventories</code> || All inventory item instances on owned characters.
|-
| <code>carts</code> || All cart item instances on owned characters.
|-
| <code>storages</code> || All owned account and master storage items. Guild storage is excluded.
|-
| <code>shops</code> || All owned vending and buying shops with nested item listings.
|}
Deleted characters are excluded. Item rows use <code>nameid</code> for the item type; nested shop listings use <code>item_id</code>. Item-instance details include amount, equipment flags, identification, refine, cards, random options, enchant grade, binding, expiry and unique ID where applicable. Use the public Game DB API to resolve item names and descriptions.
=== Complete responses ===
Every successful response contains the entire matching dataset. There is no pagination, continuation token or silent truncation. Do not send <code>page</code> or <code>page_size</code>; they are rejected. A resource limit causes an unavailable response rather than a partial dataset.
{| class="wikitable"
! Field !! Meaning
|-
| <code>dataset</code> || Dataset name.
|-
| <code>generated_at</code> || Snapshot generation time in UTC.
|-
| <code>expires_at</code> || Snapshot expiry time in UTC.
|-
| <code>filters</code> || Applied filters, or an empty object when none were supplied.
|-
| <code>count</code> || Number of returned rows after filtering.
|-
| <code>data</code> || Array containing all matching rows. Each shop row contains an <code>items</code> array.
|}
Example response with fictional character data:
<pre>
{
  "dataset": "characters",
  "generated_at": "2026-10-21T08:00:00Z",
  "expires_at": "2026-10-21T08:02:00Z",
  "filters": {},
  "count": 1,
  "data": [
    {
      "char_id": "12345", "account_id": "2000001", "char_num": "0",
      "name": "Example Merchant", "class": "5", "base_level": "100",
      "job_level": "50", "zeny": "100000", "online": "0"
    }
  ]
}
</pre>
Numeric database fields are returned as strings. In particular preserve zeny, prices and item unique IDs as strings or use an integer type that can represent their full range. JavaScript <code>Number</code> cannot represent every possible value exactly.
=== Optional filters ===
Omit filters to receive the full dataset. Filters combine with AND and only narrow the owner's cached data. Unknown or inapplicable filters return <code>400</code>. Filters do not run database queries or trigger a refresh.
{| class="wikitable"
! Flag !! Applies to !! Meaning
|-
| <code>account_id</code> || All datasets || Exact game account ID. Excludes master storage rows because those belong to the master account.
|-
| <code>char_id</code> || characters, inventories, carts, shops || Exact character ID.
|-
| <code>item_id</code> || inventories, carts, storages, shops || Exact item type.
|-
| <code>equipped=0</code> or <code>equipped=1</code> || inventories, carts, storages || No equipment flags or any equipment flag.
|-
| <code>bound=0</code> or <code>bound=1</code> || inventories, carts, storages || Unbound or any binding type.
|-
| <code>identified=0</code> or <code>identified=1</code> || inventories, carts, storages || Unidentified or identified.
|-
| <code>min_refine</code>, <code>max_refine</code> || inventories, carts, storages || Inclusive refine range.
|-
| <code>storage_type=account</code> or <code>storage_type=master</code> || storages || Account or master storage.
|-
| <code>storage_name</code> || storages || Exact storage name returned in the data. URL-encode spaces and other special characters.
|-
| <code>shop_type=vending</code> or <code>shop_type=buying</code> || shops || Vending or buying shops.
|}
For shops, <code>item_id</code> filters the nested listings and removes shops with no matching item. Other shop filters preserve every listing in each matching shop.
Examples below still require an authentication header:
<pre>
https://flux.muhro.eu/?module=api&action=private&name=inventories&char_id=12345&equipped=1
https://flux.muhro.eu/?module=api&action=private&name=inventories&min_refine=10&bound=0
https://flux.muhro.eu/?module=api&action=private&name=storages&storage_type=master&item_id=501
https://flux.muhro.eu/?module=api&action=private&name=storages&storage_name=Master%20I
https://flux.muhro.eu/?module=api&action=private&name=shops&shop_type=vending&item_id=501
</pre>
=== Refreshes, privacy and main database load ===
* Private API requests read local key state and private snapshot files only. Key creation and revocation also use local files.
* Sign-in and background refreshes query only a verified read-only replica connection. The private API system issues no queries to the main game database.
* Background refresh runs every minute for owners with active keys. Requests and cache misses never trigger a refresh or fall back to the main database.
* Snapshots and authorization expire 120 seconds after refresh starts. The replica must have running replication threads and no more than 15 seconds of lag. Unavailable or expired data returns <code>503</code>; expired snapshots are not served as a fallback.
* Password, account restrictions and ownership changes become visible through the replica. Their visibility can be delayed by up to about 135 seconds. Local key revocation applies immediately to subsequent requests.
* Data reflects saved game state that has reached the replica. Shop data uses the same database approximation of active shops as MarketDB.
All private success and error responses use <code>Cache-Control: private, no-store, max-age=0</code>. Private responses do not use <code>ETag</code>/<code>304</code> cache validation and do not enable cross-origin browser access. Do not put them in shared caches.
Zero main database queries does not mean zero server resource usage: the private API still uses web-server CPU and disk. Ordinary website pages retain their existing database connections.
=== Private API limits and errors ===
Data requests are limited to 60 per minute per owner, shared across that owner's keys. Key creation is limited to five attempts per 15 minutes per owner. Sign-in is limited to ten attempts per 15-minute source-IP rate bucket. A key can only use its selected scopes.
Errors return JSON such as <code>{"error":"snapshot_unavailable"}</code>.
{| class="wikitable"
! Status !! Meaning / action
|-
| <code>200</code> || Complete matching dataset returned. An empty match has <code>count: 0</code> and <code>data: []</code>.
|-
| <code>400</code> || Invalid dataset/filter or a key supplied in the URL. Fix the request.
|-
| <code>401</code> || Missing, invalid, expired or revoked key. Check the header or create a new key if needed.
|-
| <code>403</code> || Required scope is missing or the owner is unavailable.
|-
| <code>405</code> || The data endpoint only accepts GET.
|-
| <code>429</code> || Rate limit exceeded. Honor <code>Retry-After: 60</code>.
|-
| <code>503</code> || No fresh snapshot/authorization, a busy cache or another unavailable dependency. Honor <code>Retry-After: 60</code>. Repeated requests do not force a refresh.
|}


== Response Headers ==
== Response Headers ==
This table applies to the public MarketDB and Game DB APIs. Private response headers are described above.


{| class="wikitable"
{| class="wikitable"
Line 293: Line 463:
| Game DB
| Game DB
| 240 requests per minute per IP.
| 240 requests per minute per IP.
|-
| Private Account API
| 60 requests per minute per owner across all keys.
|}
|}


Fetch the list endpoints once and cache them; the game data only changes about weekly and <code>gamedbmeta</code> tells you when.
For the public APIs fetch the list endpoints once and cache them; the game data only changes about weekly and <code>gamedbmeta</code> tells you when.


== Status Codes ==
== Status Codes ==
This table applies to the public APIs. See the Private API limits and errors section above for private responses.


{| class="wikitable"
{| class="wikitable"