Items

Creating, reading, updating and deleting content items — the endpoints, the fields each type accepts, filters, and bulk semantics.

Endpoints

GET    /api/content/{type}          list          ?page=1&limit=1000&mine=1&…
POST   /api/content/{type}          create        one object, or an array (≤ 25)
PUT    /api/content/{type}          bulk update   array of { id, … } (≤ 25)
DELETE /api/content/{type}          bulk delete   [1,2,3] or { "ids": [1,2,3] } (≤ 100)

GET    /api/content/{type}/{id}     read one
PUT    /api/content/{type}/{id}     update one (partial)
DELETE /api/content/{type}/{id}     delete one

Listing

GET/api/content/{type}API keycanRead

List items of a type.

page defaults to 1. limit defaults to api.list.limitDefault (25) and is clamped — up to 1, and down to api.list.limitMax (100) rather than refused. Both are operator settings, and the ceiling is per role, so an operator can give one role a larger page without a code change. Ask for what you want and read what comes back; pagination.total is the full match count either way.

?mine=1 restricts a list to rows you own — and is the only way to list your own hidden items. Otherwise lists exclude hidden rows for non-staff.

Filters

Content-item lists accept these optional query parameters, and only the ones the type supports are applied:

Param Example Meaning
search ?search=rust Name or title contains, case-insensitively
tags ?tags=pvp,vanilla Has any of these tag names
categoryIds ?categoryIds=3,7 In any of these categories
communityId ?communityId=12 Belongs to this community
nsfw ?nsfw=0 NSFW flag

List responses are cached for api.list.cacheSec (30 seconds by default).

Creating

POST/api/content/{type}API keycanWrite

One object, or an array of up to 25.

curl -X POST https://api.moddingcommunity.com/api/content/asset \
  -H "Authorization: Bearer $TMC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "My Tool",
        "description": "Does a thing",
        "hidden": true,
        "tags": ["tooling", "gamedev"],
        "media": [{ "externalUrl": "https://example.com/shot.png", "title": "Screenshot" }]
      }'

Fields

id and ownerId / userId are rejected — the owner column is injected from the key’s user, and the id comes from the URL. Schemas are strict, so a misspelled field is a 400 rather than a silent no-op.

A few fields are accepted by the schema and then resolved against your role: isOfficial, the backdating pair createdAt / lastEdit, primary on groups and a community’s flagged state. A key that is not staff cannot set them.

Everywhere

Every content item takes this set, so the per-type tables below only name what is particular to that type:

Field
url The slug. Changing it leaves a redirect behind
description, content The card blurb and the body
hidden A draft. Behaves as if it does not exist to everyone else
delist Unlisted, but not hidden — out of the browsers, carousels, search and the sitemap, while keeping its page and everything attached to it
nsfw
usesAi Self-declared “contains or was made with AI”. Disclosure only — it filters nothing and gates nothing
archived Not on articles or collections
apiPublic Anonymous read access through this API. On by default — see Reading without a key
allowRatings, allowReviews, allowComments, allowMedia The engagement toggles
bannerId, iconId, cardId File ids. Servers spell these banner / icon / card
categoryIds The taxonomy set. Replaces the whole set when present
tags Tag names, up to 100. undefined leaves them; [] clears them

Per type

Type Required on create Also accepts
asset name appId, communityId, categoryId, environment, license, virusScanLink, subDisabled, media, releases, sourceItems
mod name, content, appId install, communityId, categoryId, environment, license, virusScanLink, subDisabled, media, releases, links, sourceItems, redirect
server appId See servers below
community name ageRequirement, appIds, categoryId, media, links
article title, content appId, communityId, categoryId, modId, serverId, assetId, userTargetId, media
collection name items, includeItemMedia, subDisabled
group name inviteOnly, appIds, appId, articleId, collectionId, communityId, modId, serverId, assetId, serverMapId

A group’s appId, modId, serverId and the rest say what the group is about; appIds is the separate many-to-many of games it covers. They are not the same field and setting one does not set the other.

Servers are an allow-list in three tiers

A server is the one type the API picks fields from rather than omitting them. Its input is shared with the query scanner and carries all of that scanner’s telemetry, so a new scanner field can never leak onto the public surface by being added upstream.

Ordinary owner fields

name, description, content, rules, srcUrl, url, the address (ip4, ip6, port, portQuery, hostName, useHostName), countryId, appId, communityId, showNetInfo, showUsers, showVars, showSlideshow, icon, banner, card, tags, media, links, releases.

Descriptive facts the owner may state

gameMode, version, password, secure, os, dedicated. The edit form has always rendered these, and none of them moves a server up a ranking: correcting a mis-detected operating system is legitimate.

Gated telemetry

online, curUsers, maxUsers, bots, avgUsers, vars, map and users. Accepted by the schema and then checked, at request time, against this server’s own unlocks — because the answer depends on the row, not on the payload.

Never writable at all, by anybody: lastScanned, retryCnt, locLat / locLon, isOfficial and the scanner’s detection cursors. They are internal bookkeeping with no owner-facing meaning, and no unlock makes them writable.

A server’s read is the opposite of its write, deliberately: everything we know about a server is public on its page, so GET /api/content/server/{id} resolves the map, the country, the categories, the rule set and the roster (up to 512 rows) rather than making you scrape the page for the current map. The owner’s showUsers and showVars switches still apply to the last two; the owner and staff always see both.

Updating

PUT/api/content/{type}/{id}API keycanWrite

A partial update — omitted scalars keep their stored value.

Relation arrays are the exception. An inline relation array is always the complete set: a PUT with no media key leaves media alone, but one with "media": [ … ] replaces the gallery wholesale. There is no way to say “add one” inline — use the relation sub-resource.

Bulk semantics

Bulk create and update validate and authorize every element before writing any of them, so a typo in the last item cannot leave the first nine half-created.

The writes themselves run sequentially and are not wrapped in a transaction: the canonical writers fan out into relation sync and cache invalidation of their own. If a write fails partway through, the response reports the failure and returns what did land:

{ "error": "…", "index": 3, "data": [ /* the items created before the failure */ ] }

Bulk delete authorizes the whole batch first and deletes nothing if any id is off-limits.