Make API docs LLM-friendly

- Add operationId to all 20 API operations
- Add tag groups (Pages, Posts, Collections, Images, Files) with descriptions
- Enrich info.description with concept table, usage patterns, and examples
- Add /llms.txt (llmstxt.org standard) with plain-text API overview
- Add /.well-known/ai-plugin.json (OpenAI plugin manifest)
- Add URL routes for llms.txt and ai-plugin.json

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-03-22 15:23:51 +11:00
co-authored by Copilot
parent 8cfe7fd775
commit 9d0f1fc87c
4 changed files with 223 additions and 22 deletions
+5
View File
@@ -3,6 +3,7 @@ from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from django.views.static import serve
from django.views.generic import TemplateView
from links.views import LinkDetailView, LinkUpdateView, CustomLinkView
from links.file_views import PublicFileView, import_image_view
from django.urls import path, include, re_path
@@ -41,6 +42,10 @@ urlpatterns = [
name='public-file',
),
# LLM-friendly discovery endpoints
path('llms.txt', serve, {'document_root': settings.STATIC_ROOT, 'path': 'llms.txt'}),
path('.well-known/ai-plugin.json', serve, {'document_root': settings.STATIC_ROOT, 'path': '.well-known/ai-plugin.json'}),
# Include main app URLs with locale
path('', include('links.urls')),
]
+18
View File
@@ -0,0 +1,18 @@
{
"schema_version": "v1",
"name_for_human": "GoLinks",
"name_for_model": "golinks",
"description_for_human": "Manage short links, bookmarked pages, image collections, file uploads, and blog posts on your self-hosted GoLinks server.",
"description_for_model": "GoLinks is a self-hosted link and content management API. Use it to: bookmark URLs (server auto-extracts title/description/screenshot), create Markdown blog posts with tags, manage image collections with bulk upload, upload and share files via public token URLs, serve random images at any dimensions from a personal library, and import external images for self-hosting. All list endpoints are paginated with page/page_size params. No authentication required.",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "/static/openapi.yaml",
"is_user_authenticated": false
},
"logo_url": "/static/images/logo.png",
"contact_email": "",
"legal_info_url": ""
}
+65
View File
@@ -0,0 +1,65 @@
# GoLinks API
> GoLinks is a self-hosted link and content management system. It lets you create short links, bookmark pages with auto-extracted metadata, manage image collections, upload files, write Markdown blog posts, and serve random images from a personal library.
## What you can do with this API
- **Bookmark pages** (`/api/pages`) — POST a URL and the server auto-fetches the title, description and screenshot in the background
- **Write blog posts** (`/api/posts`) — Create Markdown posts with tag categorisation
- **Manage image collections** (`/api/collections`) — Group images into named albums; upload multiple images at once
- **Upload & share files** (`/api/files`) — Upload any file, make it public with a shareable token URL, set expiry dates
- **Random image endpoint** (`/api/images/random/{width}/{height}/`) — Returns a random image from the server's library at any size; add `?fit=crop` to crop to exact dimensions
- **Import external images** (`/import/images/{url-without-scheme}`) — Reference an external image URL; the server downloads and self-hosts it in the background
## API reference
- Full OpenAPI 3.0.3 spec: /static/openapi.yaml
- Interactive docs: /ui/api-docs/
## Key patterns
### Embedding images in Markdown
```markdown
![placeholder](https://your-domain/api/images/random/400/300/?fit=crop&v=1234567890)
![photo](https://your-domain/import/images/example.com/path/to/photo.jpg)
```
### File sharing workflow
1. Upload a file: `POST /api/files` (multipart/form-data, field: `files`)
2. Make it public: `POST /api/files/{id}/toggle-public` → get `public_url`
3. Share the URL: `/public/files/{token}/` — no authentication required
### Pagination
All list endpoints accept `?page=` (1-based) and `?page_size=`. Responses are enveloped:
```json
{ "count": 42, "next": "...", "previous": null, "results": [...] }
```
## Authentication
No authentication is currently required.
## Operations quick reference
| operationId | Method | Path | Description |
|---|---|---|---|
| listPages | GET | /api/pages | List bookmarked pages |
| createPage | POST | /api/pages | Bookmark a new URL |
| listPosts | GET | /api/posts | List blog posts |
| createPost | POST | /api/posts | Create a blog post |
| listCollections | GET | /api/collections | List image collections |
| createCollection | POST | /api/collections | Create a collection |
| deleteCollection | DELETE | /api/collections/{id} | Delete a collection |
| uploadImagesToCollection | POST | /api/collections/{id}/upload_images | Upload images |
| deleteImage | DELETE | /api/images/{id} | Delete an image |
| listFiles | GET | /api/files | List uploaded files |
| uploadFiles | POST | /api/files | Upload files |
| getFile | GET | /api/files/{id} | Get file metadata |
| deleteFile | DELETE | /api/files/{id} | Delete a file |
| downloadFile | GET | /api/files/{id}/download | Download/view file |
| toggleFilePublic | POST | /api/files/{id}/toggle-public | Toggle public sharing |
| setFileExpiry | POST | /api/files/{id}/set-expiry | Set expiry date |
| getPublicFile | GET | /public/files/{token}/ | Access public file |
| getRandomImage | GET | /api/images/random/ | Random image (default size) |
| getRandomImageSized | GET | /api/images/random/{w}/{h}/ | Random image at W×H |
| importExternalImage | GET | /import/images/{url} | Import & cache external image |
+135 -22
View File
@@ -1,18 +1,80 @@
openapi: 3.0.3
info:
title: GoLinks API
description: API documentation for GoLinks service
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. |
| **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, call `POST /api/files/{id}/toggle-public` to get a public token,
then share `/public/files/{token}/` — no authentication required for the recipient.
## 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: 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: 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/pages:
get:
operationId: listPages
tags: [Pages]
summary: List all pages
description: Retrieve a paginated list of all pages
description: Retrieve a paginated list of all bookmarked pages.
parameters:
- in: query
name: page
@@ -33,8 +95,13 @@ paths:
$ref: '#/components/schemas/PageList'
post:
operationId: createPage
tags: [Pages]
summary: Create a new page
description: Create a new page with the provided data
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:
@@ -51,8 +118,10 @@ paths:
/api/posts:
get:
operationId: listPosts
tags: [Posts]
summary: List all posts
description: Retrieve a paginated list of all posts
description: Retrieve a paginated list of all Markdown blog posts.
parameters:
- in: query
name: page
@@ -73,8 +142,10 @@ paths:
$ref: '#/components/schemas/PostList'
post:
operationId: createPost
tags: [Posts]
summary: Create a new post
description: Create a new post with the provided data
description: Create a Markdown blog post. Pass tag slugs in the `tags` array to categorise it.
requestBody:
required: true
content:
@@ -91,8 +162,10 @@ paths:
/api/collections:
get:
operationId: listCollections
tags: [Collections]
summary: List all collections
description: Retrieve a paginated list of all image collections
description: Retrieve a paginated list of all image collections.
parameters:
- in: query
name: page
@@ -113,8 +186,10 @@ paths:
$ref: '#/components/schemas/CollectionList'
post:
operationId: createCollection
tags: [Collections]
summary: Create a new collection
description: Create a new image collection
description: Create a new named image collection.
requestBody:
required: true
content:
@@ -131,8 +206,10 @@ paths:
/api/collections/{collection_id}:
delete:
operationId: deleteCollection
tags: [Collections]
summary: Delete a collection
description: Delete a collection and all its images
description: Permanently delete a collection and all its images.
parameters:
- in: path
name: collection_id
@@ -151,8 +228,12 @@ paths:
/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 with optional descriptions
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
@@ -203,8 +284,10 @@ paths:
/api/images/{image_id}:
delete:
operationId: deleteImage
tags: [Images]
summary: Delete an image
description: Delete a specific image from a collection
description: Permanently delete a single image from its collection.
parameters:
- in: path
name: image_id
@@ -223,8 +306,10 @@ paths:
/api/files:
get:
operationId: listFiles
tags: [Files]
summary: List all uploaded files
description: Retrieve a paginated list of all uploaded files
description: Retrieve a paginated list of all uploaded files.
parameters:
- in: query
name: page
@@ -240,8 +325,12 @@ paths:
$ref: '#/components/schemas/FileList'
post:
operationId: uploadFiles
tags: [Files]
summary: Upload one or more files
description: Upload one or multiple files. Files are stored in FILE_UPLOADS_FOLDER on the server.
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:
@@ -271,7 +360,10 @@ paths:
/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
@@ -290,8 +382,10 @@ paths:
description: File not found
delete:
operationId: deleteFile
tags: [Files]
summary: Delete a file
description: Deletes the file record and removes it from disk
description: Permanently delete the file record and remove it from disk.
parameters:
- in: path
name: id
@@ -307,8 +401,12 @@ paths:
/api/files/{id}/download:
get:
operationId: downloadFile
tags: [Files]
summary: Download or view a file
description: Serves the file content. Images are served inline; other files as attachments.
description: |
Serve the raw file bytes. Images are returned with `Content-Disposition: inline`
(viewable in browser); all other files use `attachment` (triggers download).
parameters:
- in: path
name: id
@@ -324,8 +422,14 @@ paths:
/api/files/{id}/toggle-public:
post:
operationId: toggleFilePublic
tags: [Files]
summary: Toggle public/private visibility
description: Makes a private file public (generating a public token) or makes a public file private immediately.
description: |
Toggle a file between public and private:
- **Private → Public**: generates a `public_token` and returns a `public_url`
(`/public/files/{token}/`) that anyone can access without authentication.
- **Public → Private**: immediately revokes the token; existing `public_url` links stop working.
parameters:
- in: path
name: id
@@ -353,7 +457,12 @@ paths:
/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
@@ -383,8 +492,12 @@ paths:
/public/files/{token}/:
get:
operationId: getPublicFile
tags: [Files]
summary: Access a public file by token
description: Serves a publicly shared file. Returns 404 if the file is private or the link has expired.
description: |
Unauthenticated endpoint. Serves a publicly shared file by its opaque token.
Returns 404 if the file has been made private or if the expiry date has passed.
parameters:
- in: path
name: token
@@ -399,6 +512,8 @@ paths:
/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
@@ -406,8 +521,6 @@ paths:
**Cache busting:** Add a `?v={timestamp}` query parameter to prevent browsers from
caching repeated calls (the server ignores this parameter).
tags:
- Images
parameters:
- in: query
name: fit
@@ -454,6 +567,8 @@ paths:
/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.
@@ -466,8 +581,6 @@ paths:
```
![Random image](http://your-domain/api/images/random/300/450/?fit=crop&v=1234567890)
```
tags:
- Images
parameters:
- in: path
name: width
@@ -531,6 +644,8 @@ paths:
/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.
@@ -554,8 +669,6 @@ paths:
```
![Alt text](http://your-domain/import/images/example.com/path/to/image.jpg)
```
tags:
- Files
parameters:
- in: path
name: image_url