Files
2026-07-30 13:25:36 +10:00

1610 lines
50 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
openapi: 3.0.3
info:
title: GoLinks API
description: |
# GoLinks — Link & Content Management API
GoLinks is a self-hosted link management system. It lets you create short links,
bookmark pages with auto-extracted metadata, manage image collections, upload files,
write blog posts, and serve random images from a personal library.
## Key concepts
| Resource | What it is |
|---|---|
| **Page** | A bookmarked URL. When created, the server asynchronously fetches its title, description, and a screenshot. |
| **Bookmark** | An archived social-media post (e.g. an x.com / Twitter tweet) saved with full preview data — text, author, images, video poster thumbnails, link cards, quotes — so the archive page renders offline. Bulk-import via `/api/bookmarks/import`. |
| **Post** | A Markdown blog post, optionally tagged. |
| **Collection** | A named group of uploaded images (e.g. a photo album). |
| **Image** | An image file belonging to a Collection. |
| **File** | A generic uploaded file. Files can be made public via a shareable token URL. |
| **Random image** | Serves a random image from the server's local image library, resized on the fly. Great for placeholder images. |
## Common patterns
### Pagination
All list endpoints accept `?page=` (1-based) and `?page_size=` query parameters.
Responses follow the envelope `{ count, next, previous, results[] }`.
### Image embedding
Use the random image endpoint as a live placeholder:
```
![placeholder](https://your-domain/api/images/random/400/300/?fit=crop&v=1234567890)
```
Or import and self-host an external image:
```
![photo](https://your-domain/import/images/example.com/path/photo.jpg)
```
### File sharing
Upload a file via `POST /api/files`, call `POST /api/files/{id}/toggle-public` to make
it public, then share the returned `public_url` (`/public/files/{id}-{filename}`) —
no authentication is required for the recipient. Set an expiry with
`POST /api/files/{id}/set-expiry` to time-limit the link.
### Bookmarks bulk import
Import (or upsert) many archived posts in one request. Each item accepts the
full normalised export object from `x-bookmarks-exporter` (with nested
`author`, `media`, `card`, `quoted`, `retweet`, counters); matching is by
`tweet_id`:
```
POST /api/bookmarks/import
{ "bookmarks": [ { "id": "123", "author": {"screen_name":"u"}, "text": "...",
"media": [...], ... }, ... ] }
→ { "created": 10, "updated": 0, "skipped": 0, "total": 769 }
```
Use `PUT /api/bookmarks/bulk` with a bare JSON array of flat
`Bookmark`-shaped objects for the same upsert behaviour.
## Authentication
No authentication is currently required on any endpoint.
version: 1.0.0
x-logo:
url: /static/images/logo.png
servers:
- url: http://localhost:8000
description: Local development server
tags:
- name: Links
description: Short links with click tracking. Supports listing all links and retrieving the most visited ones sorted by click count.
- name: Pages
description: |
Bookmarked web pages. Creating a page triggers an async background job that fetches
the page title, description, and screenshot automatically.
- name: Posts
description: Markdown blog posts with optional tag categorisation.
- name: Bookmarks
description: |
Archived social-media posts (originally x.com / Twitter bookmarks), each saved with
full preview data — text, author, media poster thumbnails (no embedded video streams),
link cards, quotes — so the archive viewer page renders offline without further API calls.
Use `POST /api/bookmarks/import` for bulk upsert by `tweet_id`.
- name: Collections
description: Named groups of images. Each collection can hold many uploaded image files.
- name: Images
description: |
Image management and random image serving.
The random image endpoint is useful as a live placeholder — pass `?v={timestamp}`
to bust the browser cache on each render.
- name: Files
description: |
Generic file uploads with optional public sharing via tokenised URLs.
Files can be made public/private and given an expiry date.
paths:
/api/links:
get:
operationId: listLinks
tags: [Links]
summary: List all links
description: Retrieve a paginated list of all short links ordered by creation date (newest first).
parameters:
- in: query
name: page
schema:
type: integer
default: 1
description: Page number (1-based).
- in: query
name: page_size
schema:
type: integer
default: 10
maximum: 100
description: Number of results per page.
responses:
'200':
description: Paginated list of links.
content:
application/json:
schema:
$ref: '#/components/schemas/LinkList'
/api/links/most-visited:
get:
operationId: listMostVisitedLinks
tags: [Links]
summary: Most visited links
description: Return links that have at least one click, sorted by click count descending.
parameters:
- in: query
name: page
schema:
type: integer
default: 1
description: Page number (1-based).
- in: query
name: page_size
schema:
type: integer
default: 10
maximum: 100
description: Number of results per page.
responses:
'200':
description: Paginated list of links sorted by click_count descending.
content:
application/json:
schema:
$ref: '#/components/schemas/LinkList'
/api/pages:
get:
operationId: listPages
tags: [Pages]
summary: List all pages
description: Retrieve a paginated list of all bookmarked pages.
parameters:
- in: query
name: page
schema:
type: integer
description: Page number for pagination
- in: query
name: page_size
schema:
type: integer
description: Number of items per page
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/PageList'
post:
operationId: createPage
tags: [Pages]
summary: Create a new page
description: |
Bookmark a URL. After creation the server starts an async background job that fetches
the page title, description, content, and a screenshot.
Poll `GET /api/pages` or check `processing_status` to see when it completes.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PageCreate'
responses:
'201':
description: Page created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Page'
/api/bookmarks:
get:
operationId: listBookmarks
tags: [Bookmarks]
summary: List archived bookmarks
description: |
Paginated list of archived social-media bookmarks, newest first. Supports
full-text search (`q`), boolean media/type filters, author filter and
ordering. Default page size is 24; use `?page_size=` up to 200.
parameters:
- in: query
name: page
schema: { type: integer }
description: 1-based page number
- in: query
name: page_size
schema: { type: integer, maximum: 200 }
description: Items per page (default 24)
- in: query
name: q
schema: { type: string }
description: Case-insensitive search across `text`, `summary`, `author_screen_name`, `author_name`
- in: query
name: has_video
schema: { type: boolean }
description: Filter to bookmarks containing a video/animated GIF
- in: query
name: has_photo
schema: { type: boolean }
- in: query
name: has_card
schema: { type: boolean }
description: Has an attached link preview card
- in: query
name: is_quote
schema: { type: boolean }
- in: query
name: is_retweet
schema: { type: boolean }
- in: query
name: author
schema: { type: string }
description: Exact (case-insensitive) screen name
- in: query
name: ordering
schema:
type: string
enum: [tweet_created_at, -tweet_created_at, bookmark_created_at,
-bookmark_created_at, favorite_count, -favorite_count,
view_count, -view_count, created_at, -created_at]
description: Sort field (prefix `-` for descending). Default `-tweet_created_at`
responses:
'200':
description: Paginated bookmarks
content:
application/json:
schema:
$ref: '#/components/schemas/BookmarkList'
post:
operationId: createBookmark
tags: [Bookmarks]
summary: Create a single bookmark
description: |
Create one archived bookmark from a flat `Bookmark`-shaped object.
For importing many at once (e.g. from x-bookmarks-exporter), prefer
`POST /api/bookmarks/import`.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BookmarkCreate'
responses:
'201':
description: Bookmark created
content:
application/json:
schema:
$ref: '#/components/schemas/Bookmark'
/api/bookmarks/stats:
get:
operationId: bookmarkStats
tags: [Bookmarks]
summary: Aggregate bookmark counts
description: Returns total count plus per-flag counts (used by the viewer's filter chips).
responses:
'200':
description: Aggregate stats
content:
application/json:
schema:
type: object
properties:
total: { type: integer }
has_video: { type: integer }
has_photo: { type: integer }
has_card: { type: integer }
is_quote: { type: integer }
is_retweet: { type: integer }
/api/bookmarks/import:
post:
operationId: importBookmarks
tags: [Bookmarks]
summary: Bulk upsert bookmarks from export objects
description: |
Upserts many bookmarks in one request. Each item in `bookmarks` accepts the
full normalised export object from `x-bookmarks-exporter` — with nested
`author`, `media`, `card`, `quoted`, `retweet`, engagement counters and
`created_at` (x.com-style RFC date string). Matching is by `tweet_id`
(top-level `id` alias also accepted); existing entries are updated,
new ones created. Returns `{ created, updated, skipped, total }`.
Example body:
```json
{
"bookmarks": [
{
"id": "1234567890",
"url": "https://x.com/user/status/1234567890",
"author": {"screen_name":"user","name":"User","profile_image_url":"..."},
"text": "Hello world",
"media": [{"type":"video","media_url":"...","video":{"poster":"...","best_mp4":"...","duration_ms":12000}}],
"favorite_count": 5, "view_count": 99
}
]
}
```
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [bookmarks]
properties:
bookmarks:
type: array
items:
$ref: '#/components/schemas/BookmarkExportItem'
responses:
'200':
description: Upsert summary
content:
application/json:
schema:
type: object
properties:
created: { type: integer }
updated: { type: integer }
skipped: { type: integer }
total: { type: integer, description: Total bookmarks now in DB }
/api/bookmarks/bulk:
put:
operationId: bulkUpsertBookmarks
tags: [Bookmarks]
summary: Bulk upsert from a flat JSON array
description: |
Like `/import` but accepts a bare JSON array (not wrapped in `{bookmarks:[]}`).
Each item can be a flat `Bookmark`-shaped object (matching
`BookmarkCreate`/`Bookmark`) or a normalised export object; `tweet_id`
(or top-level `id`) is used as the upsert key.
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/BookmarkCreate'
responses:
'200':
description: Upsert summary
content:
application/json:
schema:
type: object
properties:
created: { type: integer }
updated: { type: integer }
skipped: { type: integer }
total: { type: integer }
post:
operationId: bulkUpsertBookmarksPost
tags: [Bookmarks]
summary: Bulk upsert from a flat JSON array (POST alias)
description: Alias of `PUT /api/bookmarks/bulk` for clients that cannot send PUT.
requestBody:
required: true
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/BookmarkCreate'
responses:
'200':
description: Upsert summary
content:
application/json:
schema:
type: object
properties:
created: { type: integer }
updated: { type: integer }
skipped: { type: integer }
total: { type: integer }
/api/bookmarks/{id}:
parameters:
- in: path
name: id
required: true
schema: { type: integer }
description: Internal DB primary key (NOT the tweet_id)
get:
operationId: retrieveBookmark
tags: [Bookmarks]
summary: Retrieve a single bookmark
responses:
'200':
description: Bookmark detail
content:
application/json:
schema:
$ref: '#/components/schemas/Bookmark'
'404':
description: Not found
patch:
operationId: updateBookmark
tags: [Bookmarks]
summary: Partially update a bookmark
description: |
Update any subset of fields. Common use by agents: write an AI-generated
`summary` for the post via `{"summary": "..."}`.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/BookmarkCreate'
responses:
'200':
description: Updated bookmark
content:
application/json:
schema:
$ref: '#/components/schemas/Bookmark'
delete:
operationId: deleteBookmark
tags: [Bookmarks]
summary: Delete a bookmark
responses:
'204':
description: Deleted
'404':
description: Not found
/api/posts:
get:
operationId: listPosts
tags: [Posts]
summary: List all posts
description: Retrieve a paginated list of all Markdown blog posts.
parameters:
- in: query
name: page
schema:
type: integer
description: Page number for pagination
- in: query
name: page_size
schema:
type: integer
description: Number of items per page
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/PostList'
post:
operationId: createPost
tags: [Posts]
summary: Create a new post
description: Create a Markdown blog post. Pass tag slugs in the `tags` array to categorise it.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/PostCreate'
responses:
'201':
description: Post created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Post'
/api/collections:
get:
operationId: listCollections
tags: [Collections]
summary: List all collections
description: Retrieve a paginated list of all image collections.
parameters:
- in: query
name: page
schema:
type: integer
description: Page number for pagination
- in: query
name: page_size
schema:
type: integer
description: Number of items per page
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/CollectionList'
post:
operationId: createCollection
tags: [Collections]
summary: Create a new collection
description: Create a new named image collection.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CollectionCreate'
responses:
'201':
description: Collection created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/CollectionResponse'
/api/collections/{collection_id}:
delete:
operationId: deleteCollection
tags: [Collections]
summary: Delete a collection
description: Permanently delete a collection and all its images.
parameters:
- in: path
name: collection_id
required: true
schema:
type: string
format: uuid
description: The ID of the collection to delete
responses:
'200':
description: Collection deleted successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
/api/collections/{collection_id}/upload_images:
post:
operationId: uploadImagesToCollection
tags: [Collections]
summary: Upload images to a collection
description: |
Upload one or more images to a collection. Only `image/*` content types are accepted.
Optionally provide a parallel `descriptions[]` array — each entry maps to the image at the same index.
parameters:
- in: path
name: collection_id
required: true
schema:
type: string
format: uuid
description: The ID of the collection to upload images to
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: array
items:
type: string
format: binary
description: List of image files to upload. Only image/* content types are allowed.
descriptions:
type: array
items:
type: string
description: Optional list of descriptions for the uploaded images. Each description corresponds to the image at the same index.
required:
- file
responses:
'201':
description: Images uploaded successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum: [success]
example: success
message:
type: string
example: "Successfully uploaded 2 images"
data:
type: array
items:
$ref: '#/components/schemas/Image'
/api/images/{image_id}:
delete:
operationId: deleteImage
tags: [Images]
summary: Delete an image
description: Permanently delete a single image from its collection.
parameters:
- in: path
name: image_id
required: true
schema:
type: string
format: uuid
description: The ID of the image to delete
responses:
'200':
description: Image deleted successfully
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessResponse'
/api/files:
get:
operationId: listFiles
tags: [Files]
summary: List all uploaded files
description: Retrieve a paginated list of all uploaded files.
parameters:
- in: query
name: page
schema:
type: integer
description: Page number for pagination
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/FileList'
post:
operationId: uploadFiles
tags: [Files]
summary: Upload one or more files
description: |
Upload one or multiple files via `multipart/form-data`. Files are stored server-side
in the configured `FILE_UPLOADS_FOLDER`. Returns an array of `FileUpload` objects.
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- files
properties:
files:
type: array
items:
type: string
format: binary
description: One or more files to upload
responses:
'201':
description: Files uploaded successfully
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/FileUpload'
'400':
description: No files provided
'500':
description: >-
None of the uploaded files could be fully persisted to storage
(e.g. a storage write failure or truncated upload). The response
body includes an `errors` array with a `name`/`error` entry per
failed file.
/api/files/{id}:
get:
operationId: getFile
tags: [Files]
summary: Get file details
description: Fetch metadata for a single file by its UUID.
parameters:
- in: path
name: id
required: true
schema:
type: string
format: uuid
responses:
'200':
description: File details
content:
application/json:
schema:
$ref: '#/components/schemas/FileUpload'
'404':
description: File not found
delete:
operationId: deleteFile
tags: [Files]
summary: Delete a file
description: Permanently delete the file record and remove it from disk.
parameters:
- in: path
name: id
required: true
schema:
type: string
format: uuid
responses:
'204':
description: File deleted
'404':
description: File not found
/api/files/{id}/download:
get:
operationId: downloadFile
tags: [Files]
summary: Download or view a file
description: |
Serve the raw file bytes. Images, videos, and audio are returned with
`Content-Disposition: inline` (viewable in browser); all other files use
`attachment` (triggers download).
**HTTP Range requests are supported.** Send a `Range: bytes=start-end`
header to fetch a byte range; the server replies with `206 Partial Content`,
a `Content-Range: bytes start-end/total` header, and `Accept-Ranges: bytes`.
This is what enables seeking inside `<video>` / `<audio>` elements.
parameters:
- in: path
name: id
required: true
schema:
type: string
format: uuid
- in: header
name: Range
required: false
schema:
type: string
example: bytes=0-1023
description: |
Optional byte range. When present the response is `206 Partial Content`
with `Content-Range` and `Accept-Ranges` headers.
responses:
'200':
description: Full file content (no Range header sent).
headers:
Content-Disposition:
schema:
type: string
description: '`inline` for media, `attachment` for other files.'
Accept-Ranges:
schema:
type: string
enum: [bytes]
Content-Length:
schema:
type: integer
'206':
description: Partial content in response to a `Range` request.
headers:
Content-Range:
schema:
type: string
example: bytes 0-1023/9457629
Accept-Ranges:
schema:
type: string
enum: [bytes]
Content-Length:
schema:
type: integer
'416':
description: Range Not Satisfiable (invalid byte range).
'404':
description: File not found
/api/files/{id}/toggle-public:
post:
operationId: toggleFilePublic
tags: [Files]
summary: Toggle public/private visibility
description: |
Toggle a file between public and private:
- **Private → Public**: returns `is_public: true` and a `public_url`
of the form `/public/files/{id}-{filename}` that anyone can access
without authentication.
- **Public → Private**: returns `is_public: false` and `public_url: null`;
existing `public_url` links immediately return 404.
parameters:
- in: path
name: id
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Updated visibility state
content:
application/json:
schema:
type: object
properties:
is_public:
type: boolean
public_url:
type: string
nullable: true
example: /public/files/550e8400-e29b-41d4-a716-446655440000-report.pdf
required:
- is_public
- public_url
/api/files/{id}/set-expiry:
post:
operationId: setFileExpiry
tags: [Files]
summary: Set or clear expiry date for a public file
description: |
Set an expiry datetime for a public file. After the expiry the `public_url` returns 404.
Send `expires_at: null` (or omit the field) to remove the expiry and make the link permanent.
parameters:
- in: path
name: id
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
expires_at:
type: string
format: date-time
nullable: true
description: ISO 8601 datetime. Send null or omit to clear the expiry.
responses:
'200':
description: Updated file details
content:
application/json:
schema:
$ref: '#/components/schemas/FileUpload'
/public/files/{id}-{filename}:
get:
operationId: getPublicFile
tags: [Files]
summary: Access a public file by its shareable URL
description: |
Unauthenticated endpoint. Serves a publicly shared file. The URL is derived
from the file's UUID and original filename (filename is cosmetic — the server
only looks up the record by `id`). Images, videos, and audio are served
`inline` (with HTTP Range support for seeking in media); other files use
`attachment` (download).
External requests (outside the internal network) only get served files that
have `is_public: true` and have not expired. Internal-network requests can
fetch any file regardless of the public flag.
parameters:
- in: path
name: id
required: true
schema:
type: string
format: uuid
description: The FileUpload UUID.
- in: path
name: filename
required: true
schema:
type: string
description: The original filename (cosmetic only; ignored by the server).
- in: header
name: Range
required: false
schema:
type: string
description: Optional byte range for media seeking.
responses:
'200':
description: Full file content (no Range header).
headers:
Content-Disposition:
schema:
type: string
Accept-Ranges:
schema:
type: string
enum: [bytes]
'206':
description: Partial content in response to a `Range` request.
headers:
Content-Range:
schema:
type: string
Accept-Ranges:
schema:
type: string
enum: [bytes]
'404':
description: File not found, is private (from external network), or link has expired.
/api/images/random/:
get:
operationId: getRandomImage
tags: [Images]
summary: Get a random image
description: |
Returns a randomly selected image from the server's image library, resized to the
default dimensions. Each request returns a different image.
**Cache busting:** Add a `?v={timestamp}` query parameter to prevent browsers from
caching repeated calls (the server ignores this parameter).
parameters:
- in: query
name: fit
schema:
type: string
enum: [clip, crop, fill, scale]
default: scale
description: |
How to fit the image into the requested dimensions.
- `scale` — scale to fit, maintaining aspect ratio (may add letterbox)
- `crop` — crop to exact dimensions
- `clip` — clip to fit, no upscaling
- `fill` — fill exact dimensions, may distort
- in: query
name: v
schema:
type: integer
description: Cache-busting value (e.g. Unix timestamp). Ignored by the server.
example: 1774152070
responses:
'200':
description: Image file (JPEG or PNG)
headers:
Cache-Control:
schema:
type: string
description: Caching directives (e.g. `public, max-age=3600`)
X-Image-Source:
schema:
type: string
description: Relative path of the source image that was served
content:
image/jpeg:
schema:
type: string
format: binary
image/png:
schema:
type: string
format: binary
'404':
description: |
No images available, folder not configured, or invalid parameters.
/api/images/random/{width}/{height}/:
get:
operationId: getRandomImageSized
tags: [Images]
summary: Get a random image at specific dimensions
description: |
Returns a randomly selected image resized to `{width}×{height}` pixels.
Minimum 300 px and maximum 4096 px for each dimension.
**Cache busting:** Add a `?v={timestamp}` query parameter to prevent browsers from
caching repeated calls (the server ignores this parameter).
**Use in Markdown / posts:**
```
![Random image](http://your-domain/api/images/random/300/450/?fit=crop&v=1234567890)
```
parameters:
- in: path
name: width
required: true
schema:
type: integer
minimum: 300
maximum: 4096
description: Target width in pixels
example: 300
- in: path
name: height
required: true
schema:
type: integer
minimum: 300
maximum: 4096
description: Target height in pixels
example: 450
- in: query
name: fit
schema:
type: string
enum: [clip, crop, fill, scale]
default: scale
description: |
How to fit the image into the requested dimensions.
- `scale` — scale to fit, maintaining aspect ratio (may add letterbox)
- `crop` — crop to exact dimensions
- `clip` — clip to fit, no upscaling
- `fill` — fill exact dimensions, may distort
- in: query
name: v
schema:
type: integer
description: Cache-busting value (e.g. Unix timestamp). Ignored by the server.
example: 1774152070
responses:
'200':
description: Image file (JPEG or PNG) at the requested dimensions
headers:
Cache-Control:
schema:
type: string
X-Image-Source:
schema:
type: string
description: Relative path of the source image that was served
content:
image/jpeg:
schema:
type: string
format: binary
image/png:
schema:
type: string
format: binary
'404':
description: |
No images available, folder not configured, invalid dimensions, or invalid fit mode.
/import/images/{image_url}:
get:
operationId: importExternalImage
tags: [Files]
summary: Import and cache an external image
description: |
Imports an external image by URL and saves it to the local file store.
The `image_url` path parameter is the image URL **without** the `https://` scheme prefix
(e.g. `example.com/path/to/image.jpg`). The server always tries `https://` first.
**Worker mode** — this endpoint is non-blocking:
- On the **first request** for a given URL the server creates a `FileUpload` record
(marked `is_public: true`) and kicks off a background thread to download and save the file.
The response immediately redirects (HTTP 302) to the original `https://` source URL so the
image is visible right away.
- On **subsequent requests**, once the background download has completed, the response
redirects to the locally-saved public file URL (`/public/files/{uuid}-{filename}`) so the
original host is no longer needed.
**Idempotent** — the same external URL always resolves to the same `FileUpload` record;
the file is only downloaded once.
**Use in Markdown / posts:**
```
![Alt text](http://your-domain/import/images/example.com/path/to/image.jpg)
```
parameters:
- in: path
name: image_url
required: true
schema:
type: string
description: |
External image URL without the scheme prefix.
Example: `graziamagazine.com/wp-content/uploads/2024/12/Elle-Fanning-Pigtails-scaled.jpg`
example: graziamagazine.com/wp-content/uploads/2024/12/Elle-Fanning-Pigtails-scaled.jpg
responses:
'302':
description: |
Redirect to either the original source URL (while background download is in progress)
or the locally-saved public file URL (once download has completed).
headers:
Location:
schema:
type: string
description: URL to redirect to (original source or local `/public/files/…`)
components:
schemas:
Page:
type: object
properties:
id:
type: integer
url:
type: string
title:
type: string
summary:
type: string
content:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
PageCreate:
type: object
required:
- url
- title
properties:
url:
type: string
title:
type: string
summary:
type: string
content:
type: string
PageList:
type: object
properties:
count:
type: integer
next:
type: string
nullable: true
previous:
type: string
nullable: true
results:
type: array
items:
$ref: '#/components/schemas/Page'
Post:
type: object
properties:
id:
type: integer
title:
type: string
content:
type: string
tag_details:
type: array
items:
$ref: '#/components/schemas/Tag'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
PostCreate:
type: object
required:
- title
- content
properties:
title:
type: string
content:
type: string
tags:
type: array
items:
type: string
description: List of tag slugs to associate with the post
PostList:
type: object
properties:
count:
type: integer
next:
type: string
nullable: true
previous:
type: string
nullable: true
results:
type: array
items:
$ref: '#/components/schemas/Post'
Tag:
type: object
properties:
id:
type: integer
name:
type: string
slug:
type: string
description:
type: string
Collection:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
description:
type: string
image_count:
type: integer
created_at:
type: string
format: date-time
CollectionCreate:
type: object
required:
- name
properties:
name:
type: string
description:
type: string
CollectionList:
type: object
properties:
count:
type: integer
next:
type: string
nullable: true
previous:
type: string
nullable: true
results:
type: array
items:
$ref: '#/components/schemas/Collection'
CollectionResponse:
type: object
properties:
status:
type: string
enum: [success]
message:
type: string
data:
$ref: '#/components/schemas/Collection'
Image:
type: object
properties:
id:
type: string
format: uuid
collection:
type: string
format: uuid
title:
type: string
description:
type: string
content_type:
type: string
size:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
url:
type: string
FileUpload:
type: object
properties:
id:
type: string
format: uuid
readOnly: true
name:
type: string
description: Original filename
mime_type:
type: string
readOnly: true
size:
type: integer
readOnly: true
description: File size in bytes
formatted_size:
type: string
readOnly: true
description: Human-readable size (e.g. "1.4 MB")
is_public:
type: boolean
public_token:
type: string
nullable: true
readOnly: true
public_url:
type: string
nullable: true
readOnly: true
example: /public/files/<token>/
expires_at:
type: string
format: date-time
nullable: true
is_expired:
type: boolean
readOnly: true
download_count:
type: integer
readOnly: true
source_url:
type: string
format: uri
nullable: true
description: |
Original external URL this file was imported from via `/import/images/…`.
`null` for files uploaded directly.
example: https://example.com/path/to/image.jpg
created_at:
type: string
format: date-time
readOnly: true
updated_at:
type: string
format: date-time
readOnly: true
FileList:
type: object
properties:
count:
type: integer
next:
type: string
nullable: true
previous:
type: string
nullable: true
results:
type: array
items:
$ref: '#/components/schemas/FileUpload'
SuccessResponse:
type: object
properties:
status:
type: string
enum: [success]
message:
type: string
TagSummary:
type: object
properties:
id:
type: integer
name:
type: string
slug:
type: string
Link:
type: object
properties:
id:
type: integer
readOnly: true
alias:
type: string
description: Unique slug used as the short-link identifier (e.g. /gh → github.com).
original_url:
type: string
description: Target URL. May contain template parameters like `{param,default=value}`.
description:
type: string
nullable: true
link_type:
type: string
enum: [LINK, CUSTOM]
description: LINK redirects to original_url; CUSTOM renders Markdown content.
click_count:
type: integer
readOnly: true
description: Number of times the short link has been visited.
tags:
type: array
readOnly: true
items:
$ref: '#/components/schemas/TagSummary'
created_at:
type: string
format: date-time
readOnly: true
updated_at:
type: string
format: date-time
readOnly: true
LinkList:
type: object
properties:
count:
type: integer
next:
type: string
nullable: true
previous:
type: string
nullable: true
results:
type: array
items:
$ref: '#/components/schemas/Link'
Bookmark:
type: object
description: |
An archived social-media post. The `data` field holds the complete original
normalised export object (kept for round-tripping / the offline viewer page);
the top-level denormalised fields (text, media, card_data, quoted_data,
flags, counters) are the indexed/fast-path versions used by list & filter.
properties:
id: { type: integer, description: Internal DB primary key }
tweet_id: { type: string, description: Source post id (unique) }
url: { type: string, format: uri, description: Canonical x.com post URL }
author_screen_name: { type: string }
author_name: { type: string }
author_profile_image_url: { type: string, format: uri }
text: { type: string, description: Full post text }
summary: { type: string, description: Optional human/agent-written summary }
tweet_created_at: { type: string, format: date-time, nullable: true }
bookmark_created_at: { type: string, format: date-time, nullable: true }
view_count: { type: integer }
favorite_count: { type: integer }
retweet_count: { type: integer }
reply_count: { type: integer }
bookmark_count: { type: integer }
quote_count: { type: integer }
has_video: { type: boolean }
has_photo: { type: boolean }
has_card: { type: boolean }
is_quote: { type: boolean }
is_retweet: { type: boolean }
media:
type: array
description: List of media objects (photo or video/animated_gif with poster)
items: { type: object }
card_data:
type: object
nullable: true
description: Link preview card (title, description, site, image, url)
quoted_data:
type: object
nullable: true
description: Nested quoted post (same shape as an export item)
data:
type: object
description: The complete normalised export object from x-bookmarks-exporter
created_at: { type: string, format: date-time }
updated_at: { type: string, format: date-time }
BookmarkCreate:
type: object
required: [tweet_id]
properties:
tweet_id: { type: string }
url: { type: string, format: uri }
author_screen_name: { type: string }
author_name: { type: string }
author_profile_image_url: { type: string, format: uri }
text: { type: string }
summary: { type: string }
tweet_created_at: { type: string, format: date-time, nullable: true }
bookmark_created_at: { type: string, format: date-time, nullable: true }
view_count: { type: integer }
favorite_count: { type: integer }
retweet_count: { type: integer }
reply_count: { type: integer }
bookmark_count: { type: integer }
quote_count: { type: integer }
has_video: { type: boolean }
has_photo: { type: boolean }
has_card: { type: boolean }
is_quote: { type: boolean }
is_retweet: { type: boolean }
media:
type: array
items: { type: object }
card_data:
type: object
nullable: true
quoted_data:
type: object
nullable: true
data:
type: object
BookmarkExportItem:
type: object
description: |
One element of a bulk-import payload, as produced by x-bookmarks-exporter.
All nested fields are optional except the source post id; the server maps
the nested `author`, `media`, `card`, `quoted`, `retweet`, counters and
`created_at` date string into the flat Bookmark columns and the `data`
JSON blob.
properties:
id: { type: string, description: Source tweet id (also accepted as `tweet_id`) }
url: { type: string, format: uri }
author:
type: object
properties:
screen_name: { type: string }
name: { type: string }
profile_image_url: { type: string, format: uri }
verified: { type: boolean }
description: { type: string }
text: { type: string }
summary: { type: string }
created_at: { type: string, description: x.com-style "Wed Jul 30 12:00:00 +0000 2025" }
bookmark_created_at: { type: string }
view_count: { type: integer }
favorite_count: { type: integer }
retweet_count: { type: integer }
reply_count: { type: integer }
bookmark_count: { type: integer }
quote_count: { type: integer }
media:
type: array
description: |
Each media object carries `type` (photo|video|animated_gif), image
URL variants (`media_url`, `media_thumb`, `media_large`, `media_orig`)
and, for video/gif, a `video` object with `poster`, `best_mp4`,
`duration_ms`, and `variants[]`.
items: { type: object }
card:
type: object
nullable: true
description: Link preview with title/description/site/image/url
quoted:
type: object
nullable: true
description: Nested quoted post (recursive BookmarkExportItem shape)
retweet:
type: object
nullable: true
description: Nested retweeted post
BookmarkList:
type: object
properties:
count: { type: integer }
next: { type: string, nullable: true }
previous: { type: string, nullable: true }
results:
type: array
items:
$ref: '#/components/schemas/Bookmark'