Staff Moderation¶
Staff moderation endpoints allow admin clients to review and update libraries, reports, and community photo submissions through the API.
All endpoints require a JWT access token for a user with is_staff=true. Authenticated non-staff users receive:
with status 403 Forbidden.
Summary¶
GET /api/v1/libraries/moderation/summary
Return moderation dashboard counts for staff clients.
Auth required: Yes (Bearer token, staff account)
{
"pending_libraries_count": 4,
"open_reports_count": 2,
"pending_photos_count": 5,
"total_pending": 11,
"total_libraries": 350,
"total_users": 128
}
List libraries¶
GET /api/v1/libraries/moderation
Return a paginated staff list of libraries across all moderation statuses. Approved libraries with staged owner edits appear as pending items using their proposed values, while their persisted public values remain approved.
Auth required: Yes (Bearer token, staff account)
Query parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
status |
string | all |
all, pending, approved, or rejected |
q |
string | — | Search name, address, or city |
country |
string | — | Filter by ISO 3166-1 alpha-2 country code |
source |
string | — | Filter by source text |
page |
int | 1 |
Page number (1–1000) |
page_size |
int | 20 |
Items per page (1–50) |
GET /api/v1/libraries/moderation/pending is also available as a convenience shortcut for new pending submissions and staged edits to approved libraries. It accepts the same q, country, source, page, and page_size parameters.
Response (200 OK)¶
Each item uses the standard library response fields plus status, rejection_reason, and created_by.
{
"items": [
{
"id": 42,
"slug": "florence-via-rosina-15-corner-books",
"name": "Corner Books",
"description": "A cozy community bookcase near the park entrance.",
"photo_url": "/media/libraries/photos/corner-books.jpg",
"thumbnail_url": "/media/libraries/thumbnails/corner-books.jpg",
"lat": 43.7696,
"lng": 11.2558,
"address": "Via Rosina 15",
"city": "Florence",
"country": "IT",
"postal_code": "50123",
"wheelchair_accessible": "",
"capacity": null,
"is_indoor": null,
"is_lit": null,
"website": "",
"contact": "",
"source": "",
"operator": "",
"brand": "",
"created_at": "2026-06-15T14:30:00Z",
"is_favourited": false,
"status": "pending",
"rejection_reason": "",
"created_by": {
"id": 1,
"username": "janedoe"
}
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total": 1,
"total_pages": 1,
"has_next": false,
"has_previous": false
}
}
Get a library¶
GET /api/v1/libraries/moderation/{slug}
Return any library by slug for staff users, including pending and rejected libraries hidden from the public detail endpoint. When an approved library has staged edits, this endpoint returns the proposed values with status: "pending" for review.
Update library status¶
PATCH /api/v1/libraries/moderation/{slug}
Set a library moderation status by slug. For a new pending submission, this updates the library status normally. For staged edits to an approved library, approved applies the proposed fields and photo to the existing library, while rejected discards only the proposed update and leaves the existing library approved. Sending pending for an already staged edit is idempotent: it returns the pending preview with 200 OK without changing the approved row, staged payload, or update timestamp.
Auth required: Yes (Bearer token, staff account)
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | New status: pending, approved, or rejected. pending is a no-op when an edit is already staged. |
rejection_reason |
string | No | Optional reason stored when rejecting the library |
Returns the updated library using the same response shape as the library moderation list.
List reports¶
GET /api/v1/libraries/moderation/reports
Return a paginated staff list of user-submitted reports.
Auth required: Yes (Bearer token, staff account)
| Parameter | Type | Default | Description |
|---|---|---|---|
status |
string | all |
all, open, resolved, or dismissed |
reason |
string | all |
all, damaged, missing, incorrect_info, inappropriate, or other |
page |
int | 1 |
Page number (1–1000) |
page_size |
int | 20 |
Items per page (1–50) |
Each report includes the target library summary, reporter summary, reason, details, optional photo_url, status, and created_at.
Update report status¶
PATCH /api/v1/libraries/moderation/reports/{report_id}
Set a report status.
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | New status: open, resolved, or dismissed |
curl -X PATCH https://bookcorners.org/api/v1/libraries/moderation/reports/7 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{"status": "resolved"}'
List community photos¶
GET /api/v1/libraries/moderation/photos
Return a paginated staff list of community photo submissions.
Auth required: Yes (Bearer token, staff account)
| Parameter | Type | Default | Description |
|---|---|---|---|
status |
string | all |
all, pending, approved, or rejected |
page |
int | 1 |
Page number (1–1000) |
page_size |
int | 20 |
Items per page (1–50) |
Each photo includes the parent library summary, submitter summary, caption, photo_url, thumbnail_url, status, and created_at.
Update community photo status¶
PATCH /api/v1/libraries/moderation/photos/{photo_id}
Set a community photo status. Approved photos are promoted to the parent library's primary image by the model.
| Field | Type | Required | Description |
|---|---|---|---|
status |
string | Yes | New status: pending, approved, or rejected |
curl -X PATCH https://bookcorners.org/api/v1/libraries/moderation/photos/12 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{"status": "approved"}'
Errors¶
| Status | Cause |
|---|---|
401 |
Missing or invalid authentication token |
403 |
Authenticated user is not staff |
404 |
Library, report, or photo does not exist |
422 |
Invalid payload or unsupported status/filter values |
429 |
Rate limit exceeded (see Rate Limiting) |