Arma Reforger Mods API
An unofficial, cached JSON data source for Arma Reforger Workshop mod metadata. Built for bots, panels, dashboards, and server tooling.
https://api.reforgermods.netUse /v1 for all new integrations. Older unversioned routes remain available as deprecated aliases, but they will not receive new fields.
Data is normalized from publicly accessible Arma Reforger Workshop pages. Workshop fields, availability, and page structure can change without notice, so the API is not an authoritative source for ownership, entitlement, moderation, platform accounts, or real-time Workshop state.
Authentication & Identification
The public tier requires no API key. Send an identifying User-Agent or X-API-Client header so traffic can be supported without blocking legitimate tools.
Paid API keys can be sent with either X-API-Key or Authorization: Bearer. Paid limits are shared across all active keys on the same account, so creating additional keys does not multiply throughput.
User-Agent: my-discord-bot/2.1 (contact: [email protected])Authorization: Bearer rfm_live_...
X-API-Key: rfm_live_...Rate Limits
Limits apply per resolved client IP on the public tier and per billing account on paid API key tiers. Every response includes rate-limit headers, and paid requests also include X-API-Plan.
Call GET /v1/rate-limits with your API key to inspect the current plan, request limit, burst allowance, and whether the limit is shared by account or client IP.
On 429 Too Many Requests or 503 Service Unavailable, wait for Retry-After and retry later. Batch and cache on your side instead of polling, and do not request variations of the same query to bypass caching; there is no public cache-bypass or force-refresh parameter.
Caching & Refresh Jobs
Mod data is served from cache. If an ID or list has not been fetched recently, the API starts a refresh job and responds immediately instead of making your request wait on a live Workshop fetch.
HTTP/1.1 202 Accepted
Retry-After: 3
Location: /v1/refresh/jobs/job_8841
{ "status": "refreshing", "jobId": "job_8841" }If-None-Match.Retry-After.Cache lifetimes
| Resource | Fresh cache | Stale fallback |
|---|---|---|
| Mod detail | 1 hour | 24 hours |
| Mod list and search | 10 minutes | 1 hour |
| Not found response | 10 minutes | None |
Stale data may be served with X-Cache: STALE while a background refresh runs, so the API stays responsive when the upstream Workshop is slow or unavailable.
Response headers
| Header | Meaning |
|---|---|
X-Cache | HIT, STALE, or MISS |
Retry-After | How long to wait before retrying |
Location | Refresh-job URL on 202 Accepted |
ETag | Validator for conditional requests |
X-RateLimit-* | Current rate-limit state |
X-Request-Id | Request identifier for support and debugging |
Conditional requests
Responses include an ETag. Send it back in If-None-Match to get a free 304 Not Modified when nothing changed:
curl -H 'If-None-Match: W/"a1b2c3d4e5f6a7b8c9d0e1f2"' \
"https://api.reforgermods.net/v1/mods?search=radio"/v1/health
Returns process health only. It does not request Workshop data.
curl https://api.reforgermods.net/v1/health
const res = await fetch("https://api.reforgermods.net/v1/health", {
headers: { "X-API-Client": "my-tool/1.0" }
});
console.log(await res.json());
import requests
res = requests.get("https://api.reforgermods.net/v1/health", headers={
"User-Agent": "my-tool/1.0 ([email protected])"
}, timeout=10)
print(res.json())
{
"status": "success",
"data": { "code": 200, "alive": true }
}/v1/mods
Search and list cached Workshop mod previews. Supports search, sort, tags, and category.
curl -H "User-Agent: my-bot/1.0" \
"https://api.reforgermods.net/v1/mods?search=radio&sort=newest&limit=20"
const url = new URL("https://api.reforgermods.net/v1/mods");
url.searchParams.set("search", "radio");
url.searchParams.set("sort", "newest");
const res = await fetch(url, {
headers: { "X-API-Client": "my-panel/1.0" }
});
console.log(await res.json());
import requests
res = requests.get("https://api.reforgermods.net/v1/mods", params={
"search": "radio",
"sort": "newest",
}, headers={"User-Agent": "my-panel/1.0"}, timeout=15)
print(res.json())
Query parameters
| Name | Values |
|---|---|
search | Optional search text |
sort | popularity, newest, subscribers, or version_size |
tags | Optional Workshop tag filter. Repeat to include multiple tags. |
category | Alias for a single tags value, used by the browser UI. |
The response returns a page of mod preview objects with pagination metadata and links. See the full list response structure.
/v1/mods/{page}
Fetch a specific positive page number using the same response schema as /v1/mods.
curl -H "X-API-Client: my-app/1.0" \
"https://api.reforgermods.net/v1/mods/2?search=radio&sort=newest&tags=GAMEPLAY"
const res = await fetch("https://api.reforgermods.net/v1/mods/2?search=radio&sort=newest&tags=GAMEPLAY", {
headers: { "X-API-Client": "my-app/1.0" }
});
console.log(await res.json());
import requests
res = requests.get(
"https://api.reforgermods.net/v1/mods/2",
params={"search": "radio", "sort": "newest", "tags": "GAMEPLAY"},
headers={"User-Agent": "my-app/1.0"},
timeout=15,
)
print(res.json())
/v1/search
Convenience route for first-page search results. It returns the same shape as /v1/mods?search=....
curl "https://api.reforgermods.net/v1/search?search=radio"
const res = await fetch("https://api.reforgermods.net/v1/search?search=radio", {
headers: { "X-API-Client": "my-tool/1.0" }
});
console.log(await res.json());
import requests
res = requests.get("https://api.reforgermods.net/v1/search", params={"search": "radio"})
print(res.json())
/v1/mod/{id}
Fetch one mod by Workshop ID, including dependencies and scenario metadata when available.
curl -H "User-Agent: my-bot/1.0" \
"https://api.reforgermods.net/v1/mod/5965550F24A0C152"
const modId = "5965550F24A0C152";
const res = await fetch("https://api.reforgermods.net/v1/mod/" + modId, {
headers: { "X-API-Client": "my-panel/1.0" }
});
console.log(await res.json());
import requests
mod_id = "5965550F24A0C152"
res = requests.get(f"https://api.reforgermods.net/v1/mod/{mod_id}", headers={
"User-Agent": "my-panel/1.0 ([email protected])"
}, timeout=15)
print(res.json())
{
"status": "success",
"data": {
"name": "Example Mod",
"author": "Example Author",
"originalModURL": "https://reforger.armaplatform.com/workshop/5965550F24A0C152",
"apiModURL": "https://api.reforgermods.net/v1/mod/5965550F24A0C152",
"imageURL": "https://example.com/image.png",
"rating": "92%",
"version": "1.1.0",
"gameVersion": "1.1.0.34",
"size": "192.42 KB",
"subscribers": 66677,
"downloads": 791142,
"created": "19.05.2022",
"lastModified": "17.03.2024",
"id": "5965550F24A0C152",
"summary": "Short Workshop summary",
"description": "Workshop description",
"license": "Arma Public License (APL)",
"tags": ["GAMEPLAY"],
"dependencies": [
{
"name": "Example Dependency",
"originalModURL": "https://reforger.armaplatform.com/workshop/{dep_id}",
"apiModURL": "https://api.reforgermods.net/v1/mod/{dep_id}"
}
],
"scenarios": [
{
"name": "Example Scenario",
"description": "Scenario summary",
"scenarioID": "{EXAMPLE}Missions/Example.conf",
"gamemode": "Game Master",
"playerCount": 16,
"imageURL": "https://example.com/scenario-image.png"
}
]
}
}dependencies, scenarios, tags, and optional metadata may be empty when the Workshop does not provide them. Field-by-field types are documented in the mod object structure.
/v1/rate-limits
Returns the rate-limit tier resolved for the request. Send an API key to check the paid plan attached to that key; omit the key to see the public client-IP tier.
curl -H "Authorization: Bearer rfm_live_..." \
https://api.reforgermods.net/v1/rate-limits
const res = await fetch("https://api.reforgermods.net/v1/rate-limits", {
headers: { "Authorization": "Bearer rfm_live_..." }
});
console.log(await res.json());
import requests
res = requests.get("https://api.reforgermods.net/v1/rate-limits", headers={
"Authorization": "Bearer rfm_live_..."
}, timeout=10)
print(res.json())
{
"authenticated": true,
"rate_limit": {
"plan": "pro",
"limit_per_minute": 1200,
"burst": 120,
"window": "1 minute",
"shared_by": "account",
"active_key_limit": 10
}
}Invalid, revoked, or inactive paid keys return the same authentication errors as other API routes.
/v1/refresh/jobs/{id}
Inspect refresh progress after a 202 Accepted response. Most clients can simply wait for Retry-After and retry the original URL.
curl "https://api.reforgermods.net/v1/refresh/jobs/9f0b7d0f6fd4f88a8bb0e455f0b640247a93"
const jobId = "9f0b7d0f6fd4f88a8bb0e455f0b640247a93";
const res = await fetch("https://api.reforgermods.net/v1/refresh/jobs/" + jobId);
console.log(await res.json());
import requests
job_id = "9f0b7d0f6fd4f88a8bb0e455f0b640247a93"
res = requests.get(f"https://api.reforgermods.net/v1/refresh/jobs/{job_id}", timeout=10)
print(res.json())
{
"id": "9f0b7d0f6fd4f88a8bb0e455f0b640247a93",
"status": "queued",
"resource_url": "/v1/mods?search=radio",
"retry_after_seconds": 2
}Possible statuses are queued, running, succeeded, failed, and expired. After a job succeeds, request resource_url again; refresh-job responses do not contain the finished mod or list payload. Do not poll jobs faster than the advertised retry_after_seconds.
List Response Structure
List and search endpoints (/v1/mods, /v1/mods/{page}, /v1/search) share one response shape: pagination metadata in meta, an array of mod preview objects in data, and page URLs in links.
{
"status": "success",
"meta": {
"totalPages": 2593, // int
"currentPage": 2, // int
"totalMods": 41478, // int
"shownMods": 16, // int
"modsIndexStart": 17, // int
"modsIndexEnd": 32 // int
},
"data": [ { ...mod preview objects... } ],
"links": {
"next": "https://api.reforgermods.net/v1/mods/3?search=radio",
"prev": "https://api.reforgermods.net/v1/mods/1?search=radio"
}
}Mod Preview Object
List and search endpoints return mod previews. Previews do not include every field from the detail endpoint.
Fetch /v1/mod/{id} for the full mod object.
{
"name": "Super Awesome Mod", // string
"author": "Homer Simpson", // string
"imageURL": "https://example.com/image.png", // string
"originalModURL": "https://reforger.armaplatform.com/workshop/{mod_id}", // string
"apiModURL": "https://api.reforgermods.net/v1/mod/{mod_id}", // string
"size": "192.42 KB", // string
"rating": "92%", // string
"ID": "{mod_id}" // string
}Mod Object
Querying a single mod by ID returns the full mod object, including dependencies and scenarios when the Workshop provides them.
{
"name": "Super Awesome Mod", // string
"author": "Homer Simpson", // string
"originalModURL": "https://reforger.armaplatform.com/workshop/{mod_id}", // string
"apiModURL": "https://api.reforgermods.net/v1/mod/{mod_id}", // string
"imageURL": "https://example.com/image.png", // string
"rating": "92%", // string
"version": "1.1.0", // string
"gameVersion": "1.1.0.34", // string
"size": "192.42 KB", // string
"subscribers": 66677, // int
"downloads": 791142, // int
"created": "19.05.2022", // string
"lastModified": "17.03.2024", // string
"id": "{mod_id}", // string
"summary": "This is a super awesome mod", // string
"description": "Big awesome mod!", // string
"license": "Arma Public License (APL)", // string
"tags": ["SUPER", "AWESOME"], // string array
"dependencies": [ { ...dependency objects... } ], // object array
"scenarios": [ { ...scenario objects... } ] // object array
}Dependency Object
Mods with dependencies list them as an array of dependency objects. Each links to the dependency on the official Workshop and on this API.
{
"name": "Mod Dependency", // string
"originalModURL": "https://reforger.armaplatform.com/workshop/{dep_id}", // string
"apiModURL": "https://api.reforgermods.net/v1/mod/{dep_id}" // string
}Scenario Object
Mods that ship playable scenarios list them as an array of scenario objects. The scenarioID is the value a server config.json uses for game.scenarioId.
{
"name": "Some Scenario", // string
"description": "You will do awesome things in this scenario", // string
"scenarioID": "{mod_id}Missions/SomeScenario.conf", // string
"gamemode": "Campaign", // string
"playerCount": 64, // int
"imageURL": "https://example.com/image.png" // string
}Refresh Job Object
Returned by /v1/refresh/jobs/{id} while background refreshes run.
{
"id": "9f0b7d0f6fd4f88a8bb0e455f0b640247a93", // string
"status": "queued", // string: queued | running | succeeded | failed | expired
"resource_url": "/v1/mods?search=radio", // string
"retry_after_seconds": 2 // int
}Errors
Errors use a consistent JSON response and every response includes X-Request-Id for support. Include that value when reporting an issue.
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests.",
"requestId": "..."
}
}