Reference for the JSON/REST API exposed by 640by480.com.
All endpoints are mounted under /api/. Base URL in production:
https://640by480.com/api/
The API accepts two authentication schemes on every endpoint:
Authorization: Token <key> header. Obtain a key via POST /api/auth/login/.There is no HTTP Basic, JWT, or OAuth support.
IsAuthenticatedOrReadOnly — anonymous clients can GET, authenticated clients can write.IsAuthorOrReadOnly — only the original author of a post or comment can PUT, PATCH, or DELETE it.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>" }
Wired via DRF DefaultRouter against PostViewSet. Standard CRUD plus one custom action.
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 */ ]
}
List results are ordered by -created (newest first).
{
"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 */ ]
}
thumbnail is generated at max 250×250.detail is generated at max 640×640.author, created, modified, thumbnail, detail.description may be null.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:
foo.jpg — originalfoo_detail.jpg — detailfoo_thumbnail.jpg — thumbnailThe 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.
| 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 postmultipart/form-data (image is a file upload).image (required, file) — JPEG, PNG, or GIF.description (optional, text).author is set automatically from the authenticated user; do not send it.Response 201: the full post object.
Image uploads with content types other than
image/jpeg,image/png, orimage/gifwill 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 */ }
Wired via the same router against CommentViewSet. Same CRUD shape as posts.
{
"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
commentsarray.
GET /api/comments/ uses the global PAGE_SIZE = 20. Same response envelope as posts.
No explicit ordering — falls back to the model's default (typically insertion order / primary key).
| 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 commentRequest body:
{ "text": "Nice shot!", "post_id": 42 }
post_id is consumed by the view but does not appear in the response.post_id does not match an existing post.Response 201: the comment object.
GET /api/ returns DRF's auto-generated index:
{
"posts": "https://640by480.com/api/posts/",
"comments": "https://640by480.com/api/comments/"
}
# 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"
curl https://640by480.com/api/posts/?page=1
curl -X POST https://640by480.com/api/posts/42/add_comment/ \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"text":"Beautiful framing."}'
Z suffix (USE_TZ = True).X-CSRFToken on unsafe methods. Token-auth clients are exempt.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.