Skip to content

Media & Photos API

Community photo submissions, CDN-served place gallery images, and curator management for official place photos.

Status

Live — POST /api/v1/places/:id/photos, POST /api/v1/admin/places/:placeId/photos, PATCH /api/v1/admin/photos/:photoId, and DELETE /api/v1/admin/photos/:photoId are implemented. Tracked under KUB-146, KUB-149, KUB-150, KUB-151.


Photo Source Types

Each photo has a source_type field indicating its origin:

ValueDescription
admin_uploadUploaded by a curator via the admin console (KUB-150)
scrapedImported from an upstream source and retained for provenance
communitySubmitted by an authenticated user via the app (KUB-146)

Community photos start with status: pending and must be approved before appearing in gallery responses. Related: KUB-151.


Submit Community Photo

http
POST /api/v1/places/:id/photos
Authorization: Bearer <token>
Content-Type: multipart/form-data

Authenticated users may submit photos of a place. Photos are held for moderation (status: pending) and do not appear publicly until approved.

Request

FieldTypeRequiredDescription
photofileYesJPEG, PNG, or WebP, max 10 MB
attributionstringNoOptional photographer credit

Response

http
HTTP/1.1 201 Created
json
{
	"data": {
		"id": "42",
		"status": "pending",
		"moderation_eta": null
	}
}

Related: KUB-146.

Error Responses

  • 400 invalid_body when the multipart body cannot be parsed.
  • 401 unauthorized when the caller is not authenticated.
  • 403 forbidden when the caller is a guest session.
  • 404 not_found when the place does not exist or is unpublished.
  • 413 file_too_large when the uploaded image exceeds 10 MB.
  • 415 unsupported_media_type when the file is not JPEG, PNG, or WebP.
  • 422 validation_error when the photo field is missing or not a file upload.

CDN Media URLs

Gallery responses return url and thumbnail_url fields that are already CDN-backed. Clients should treat both fields as opaque URLs rather than constructing media paths locally. Related: KUB-149.


Curator: Upload Official Photo

http
POST /api/v1/admin/places/:placeId/photos
Authorization: Bearer <admin-token>
Content-Type: multipart/form-data

Admin/curator endpoint to attach an official photo to a place. Photos are immediately published (status: published, source_type: admin_upload).

Request

FieldTypeRequiredDescription
photofileYesJPEG, PNG, or WebP, max 10 MB
attributionstringNoOptional photographer credit
alt_textstringNoOptional accessibility text

Response

http
HTTP/1.1 201 Created
json
{
	"data": {
		"id": "99",
		"source_type": "admin_upload",
		"status": "published"
	}
}

Related: KUB-150.

Error Responses

  • 400 invalid_body when the multipart body cannot be parsed.
  • 401 unauthorized when the caller is not authenticated.
  • 403 forbidden when the caller lacks curator/admin access.
  • 404 not_found when the place does not exist.
  • 413 file_too_large when the uploaded image exceeds 10 MB.
  • 415 unsupported_media_type when the file is not JPEG, PNG, or WebP.
  • 422 validation_error when the photo field is missing or not a file upload.

Curator: Update Photo Metadata

http
PATCH /api/v1/admin/photos/:photoId
Authorization: Bearer <admin-token>
Content-Type: application/json

Curators and admins can update attribution, alt text, or the moderation status for an existing place photo.

Request

json
{
	"attribution": "Roseau Photo Collective",
	"alt_text": "View toward Morne Bruce",
	"status": "approved"
}

status accepts only approved or rejected. Invalid field types and unsupported status values return 422 validation_error.

Malformed JSON returns 400 invalid_body before field validation runs.

Response

json
{
	"data": {
		"id": "99",
		"updated": true
	}
}

Curator: Delete Photo

http
DELETE /api/v1/admin/photos/:photoId
Authorization: Bearer <admin-token>

Permanently removes a photo. Works for admin_upload, scraped, and community photos.

Response

json
{
	"data": {
		"id": "99",
		"deleted": true
	}
}

Related: KUB-150.

Built with VitePress