640by480 API

Reference for the JSON/REST API exposed by 640by480.com.

All endpoints are mounted under /api/. Base URL in production:

https://640by480.com/api/

Authentication

The API accepts two authentication schemes on every endpoint:

There is no HTTP Basic, JWT, or OAuth support.

Permissions


Authentication Endpoints

POST /api/auth/login/

Obtain (or retrieve an existing) auth token. No authentication required.

Request body (JSON or form-encoded):

{ "username": "alice", "password": "hunter2" }

Response 200:

{
  "token": "<your token here>",
  "user_id": 7,
  "username": "alice"
}

If the user already has a token, the same key is returned (get_or_create semantics).

Response 400: validation errors from DRF.


POST /api/auth/logout/

Invalidate the calling user's token. Authentication required.

Request body: empty.

Response 200:

{ "message": "Successfully logged out." }

Response 500 (no token to delete, or other error):

{ "error": "<message>" }

Posts

Wired via DRF DefaultRouter against PostViewSet. Standard CRUD plus one custom action.

Pagination

GET /api/posts/ is paginated with PageNumberPagination and PAGE_SIZE = 20. Use ?page=N to navigate.

{
  "count": 137,
  "next": "https://640by480.com/api/posts/?page=2",
  "previous": null,
  "results": [ /* post objects */ ]
}

Ordering

List results are ordered by -created (newest first).

Post object

{
  "id": 42,
  "image":     "https://640by480.com/media/foo.jpg",
  "thumbnail": "https://640by480.com/media/foo_thumbnail.jpg",
  "detail":    "https://640by480.com/media/foo_detail.jpg",
  "description": "string or null",
  "author":  { "id": 7, "username": "alice" },
  "created":  "2025-04-01T12:34:56Z",
  "modified": "2025-04-01T12:34:56Z",
  "comment_count": 3,
  "comments": [ /* comment objects */ ]
}

Image sizes

Every post stores three versions of the uploaded image, generated server-side at upload time:

Field Max dimensions Typical use
image original (no resize) full-resolution download
detail 640 × 640 single-post / detail view
thumbnail 250 × 250 feed / grid view

The thumbnail and detail versions are produced with PIL's Image.thumbnail(), which preserves aspect ratio and constrains the longer side to the limit. A landscape photo therefore becomes something like 640×427 for detail, not a forced 640×640 square. EXIF orientation is normalized so portrait photos are not sideways.

Filename convention: if the original upload is foo.jpg, the resized files in the same media directory are:

How to fetch a given size

The post object exposes all three as absolute URLs. Pick the field that matches the size you want and GET it directly — image bytes are served as static media (no Authorization header required for the image fetch itself):

# Feed view: list posts and use the thumbnail URL
curl https://640by480.com/api/posts/ | jq '.results[].thumbnail'

# Detail view: fetch one post and use the detail URL
curl https://640by480.com/api/posts/42/ | jq -r '.detail'

# Original: same response, use the image URL
curl https://640by480.com/api/posts/42/ | jq -r '.image'

There is no API parameter to request a custom size — only these three fixed variants exist.

Routes

Method Path Auth Description
GET /api/posts/ none Paginated list, newest first.
POST /api/posts/ required Create a post.
GET /api/posts/{id}/ none Retrieve a single post.
PUT /api/posts/{id}/ author Full update.
PATCH /api/posts/{id}/ author Partial update.
DELETE /api/posts/{id}/ author Delete the post.
POST /api/posts/{id}/add_comment/ required Add a comment to this post.

POST /api/posts/ — Create a post

Response 201: the full post object.

Image uploads with content types other than image/jpeg, image/png, or image/gif will fail server-side during thumbnail generation. Validate client-side.

POST /api/posts/{id}/add_comment/

Convenience for posting a comment without specifying post_id.

Request body:

{ "text": "Nice shot!" }

Response 201: the new comment object.

Response 400: { /* validation errors */ }


Comments

Wired via the same router against CommentViewSet. Same CRUD shape as posts.

Comment object

{
  "id": 9,
  "text": "Nice shot!",
  "author":   { "id": 7, "username": "alice" },
  "created":  "2025-04-01T12:34:56Z",
  "modified": "2025-04-01T12:34:56Z"
}

The serialized comment does not include the parent post id. The post relationship is only visible by reading the parent post's comments array.

Pagination

GET /api/comments/ uses the global PAGE_SIZE = 20. Same response envelope as posts.

Ordering

No explicit ordering — falls back to the model's default (typically insertion order / primary key).

Routes

Method Path Auth Description
GET /api/comments/ none Paginated list.
POST /api/comments/ required Create a comment. Requires post_id in body.
GET /api/comments/{id}/ none Retrieve a single comment.
PUT /api/comments/{id}/ author Full update.
PATCH /api/comments/{id}/ author Partial update.
DELETE /api/comments/{id}/ author Delete the comment.

POST /api/comments/ — Create a comment

Request body:

{ "text": "Nice shot!", "post_id": 42 }

Response 201: the comment object.


API Root

GET /api/ returns DRF's auto-generated index:

{
  "posts":    "https://640by480.com/api/posts/",
  "comments": "https://640by480.com/api/comments/"
}

Examples

Login and post a photo (curl)

# 1. Get a token
TOKEN=$(curl -s -X POST https://640by480.com/api/auth/login/ \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"hunter2"}' \
  | python -c 'import sys,json; print(json.load(sys.stdin)["token"])')

# 2. Upload a photo
curl -X POST https://640by480.com/api/posts/ \
  -H "Authorization: Token $TOKEN" \
  -F "image=@cat.jpg" \
  -F "description=Mittens at sunset"

List recent posts (no auth)

curl https://640by480.com/api/posts/?page=1

Comment on a post

curl -X POST https://640by480.com/api/posts/42/add_comment/ \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text":"Beautiful framing."}'

Notes and Caveats

  1. Timestamps are UTC, ISO 8601 with Z suffix (USE_TZ = True).
  2. Image formats: JPEG, PNG, and GIF only.
  3. CSRF: clients using session auth must send X-CSRFToken on unsafe methods. Token-auth clients are exempt.
  4. URL pattern ordering: in digicam/urls.py, the re_path(r'^api/', include('api.urls')) line is registered before the catch-all profile route path('<str:username>/', ...), so every /api/* request resolves to the API. Do not reorder these — putting the catch-all first would cause GET /api/ (the API root) to render the Profile view for a user literally named "api" instead.