mobieusKnow mobieusCore API — Marketplace, files, and moderation History #295
Author
system
Submitted
Jun 14, 2026 6:59am
Summary
KB drift-audit reconciliation 2026-06-14: corrected to match dev (report deliverables).

Marketplace, files, and moderation endpoints

The 2026-05-29 expansion added programmatic access to the marketplace listings, file uploads, and the moderation queue. Every endpoint here is scoped, paginated, and tenant-isolated like the rest of the v1 surface.

Marketplace listings

GET /api/v1/listings

Lists every listing on the tenant, newest first.

Query parameters:

Param Type Example
status string active, pending, sold, withdrawn, expired
seller_id integer 312
category_id integer 7
cursor string opaque
limit integer 25 (max 100)

Response shape:

{
  "data": [
    {
      "id": 42, "slug": "atari-2600-light-sixer",
      "title": "Atari 2600 Light Sixer",
      "description": "Working, original box…",
      "price_cents": 12500,
      "quantity": 1, "quantity_remaining": 1,
      "condition_rating": "good",
      "location": { "country": "US", "region": "NY", "city": "Brooklyn" },
      "status": "active",
      "category": { "id": 7, "name": "Atari 2600" },
      "seller":   { "id": 312, "username": "retroseller" },
      "created_at": "2026-05-29T13:00:00Z",
      "expires_at": "2026-06-28T13:00:00Z"
    }
  ],
  "next_cursor": "NDI=",
  "request_id": "req_…"
}

GET /api/v1/listings/{id}

Returns one listing by id with the same shape.

Scope required: listings:read.

File uploads

GET /api/v1/files

Lists every approved file on the tenant. Files still in quarantine are not exposed.

Query parameters:

Param Type Example
area_slug string atari-2600-disks
uploader_id integer 312
cursor string opaque
limit integer 25 (max 100)

Response shape:

{ "data": [{
    "id": 18, "title": "Centipede (1981)",
    "description": "Atari Centipede dump.",
    "original_name": "centipede.bin",
    "mime_type": "application/octet-stream",
    "size_bytes": 16384,
    "download_count": 4,
    "area":     { "slug": "atari-2600-disks", "name": "Atari 2600 disks" },
    "uploader": { "id": 312, "username": "retroseller" },
    "created_at": "2026-05-29T13:05:00Z"
  }], "next_cursor": null, "request_id": "req_…" }

GET /api/v1/files/{id}

Returns one approved file by id.

Scope required: files:read.

Moderation

GET /api/v1/reports

Lists moderation reports. Filter by status or reported_user_id.

Description is omitted from the list response for size; fetch a single report to get the full body.

GET /api/v1/reports/{id}

Returns a single report including the full description field.

POST /api/v1/reports/{id}/dismiss

Marks the report status='resolved' with resolution_notes prefixed Dismissed via API:. Body:

{ "reason": "No violation found." }

Returns 400 already_resolved if the report is already resolved.

POST /api/v1/reports/{id}/resolve

Same shape as dismiss but the resolution notes are prefixed Resolved via API:. Use this when the report is valid and you took some action externally that you want recorded.

GET /api/v1/moderation/actions

Returns every moderator action recorded on the tenant. Filter by action (e.g. forum.ban, moderation.warn), actor_id, or affected_user_id.

Scopes:

  • reports:readGET endpoints + the moderation-actions log
  • reports:manage — the two POST endpoints (dismiss, resolve)

Best practices

  • Page in batches: cursor pagination is forward-only; persist the last next_cursor you saw so you can resume without re-walking.
  • Read with the smallest scope: a key with only reports:read cannot accidentally close a report.
  • Subscribe to webhooks for listing.*, file.uploaded, report.created, and moderation.action_taken rather than polling — webhooks are delivered within ~60 seconds of the underlying write.
# Marketplace, files, and moderation endpoints

The 2026-05-29 expansion added programmatic access to the marketplace listings, file uploads, and the moderation queue. Every endpoint here is scoped, paginated, and tenant-isolated like the rest of the v1 surface.

## Marketplace listings

### `GET /api/v1/listings`

Lists every listing on the tenant, newest first.

Query parameters:

| Param          | Type     | Example              |
|----------------|----------|----------------------|
| `status`       | string   | `active`, `pending`, `sold`, `withdrawn`, `expired` |
| `seller_id`    | integer  | `312`                |
| `category_id`  | integer  | `7`                  |
| `cursor`       | string   | opaque               |
| `limit`        | integer  | 25 (max 100)         |

Response shape:

```json
{
  "data": [
    {
      "id": 42, "slug": "atari-2600-light-sixer",
      "title": "Atari 2600 Light Sixer",
      "description": "Working, original box…",
      "price_cents": 12500,
      "quantity": 1, "quantity_remaining": 1,
      "condition_rating": "good",
      "location": { "country": "US", "region": "NY", "city": "Brooklyn" },
      "status": "active",
      "category": { "id": 7, "name": "Atari 2600" },
      "seller":   { "id": 312, "username": "retroseller" },
      "created_at": "2026-05-29T13:00:00Z",
      "expires_at": "2026-06-28T13:00:00Z"
    }
  ],
  "next_cursor": "NDI=",
  "request_id": "req_…"
}
```

### `GET /api/v1/listings/{id}`

Returns one listing by id with the same shape.

Scope required: **`listings:read`**.

## File uploads

### `GET /api/v1/files`

Lists every **approved** file on the tenant. Files still in quarantine are not exposed.

Query parameters:

| Param         | Type    | Example               |
|---------------|---------|-----------------------|
| `area_slug`   | string  | `atari-2600-disks`    |
| `uploader_id` | integer | `312`                 |
| `cursor`      | string  | opaque                |
| `limit`       | integer | 25 (max 100)          |

Response shape:

```json
{ "data": [{
    "id": 18, "title": "Centipede (1981)",
    "description": "Atari Centipede dump.",
    "original_name": "centipede.bin",
    "mime_type": "application/octet-stream",
    "size_bytes": 16384,
    "download_count": 4,
    "area":     { "slug": "atari-2600-disks", "name": "Atari 2600 disks" },
    "uploader": { "id": 312, "username": "retroseller" },
    "created_at": "2026-05-29T13:05:00Z"
  }], "next_cursor": null, "request_id": "req_…" }
```

### `GET /api/v1/files/{id}`

Returns one approved file by id.

Scope required: **`files:read`**.

## Moderation

### `GET /api/v1/reports`

Lists moderation reports. Filter by `status` or `reported_user_id`.

Description is omitted from the list response for size; fetch a single report to get the full body.

### `GET /api/v1/reports/{id}`

Returns a single report **including the full `description`** field.

### `POST /api/v1/reports/{id}/dismiss`

Marks the report `status='resolved'` with `resolution_notes` prefixed `Dismissed via API:`. Body:

```json
{ "reason": "No violation found." }
```

Returns 400 `already_resolved` if the report is already resolved.

### `POST /api/v1/reports/{id}/resolve`

Same shape as dismiss but the resolution notes are prefixed `Resolved via API:`. Use this when the report is valid and you took some action externally that you want recorded.

### `GET /api/v1/moderation/actions`

Returns every moderator action recorded on the tenant. Filter by `action` (e.g. `forum.ban`, `moderation.warn`), `actor_id`, or `affected_user_id`.

Scopes:

- `reports:read`   — `GET` endpoints + the moderation-actions log
- `reports:manage` — the two `POST` endpoints (dismiss, resolve)

## Best practices

- **Page in batches**: cursor pagination is forward-only; persist the last `next_cursor` you saw so you can resume without re-walking.
- **Read with the smallest scope**: a key with only `reports:read` cannot accidentally close a report.
- **Subscribe to webhooks** for `listing.*`, `file.uploaded`, `report.created`, and `moderation.action_taken` rather than polling — webhooks are delivered within ~60 seconds of the underlying write.