Skip to main content

API Guide

joe-links provides a REST API at /api/v1 for programmatic link management. All API requests require authentication via a Personal Access Token (PAT).

Creating a Personal Access Token

  1. Sign in to joe-links and go to the Dashboard.
  2. Navigate to Settings > API Tokens.
  3. Click Create Token, give it a name, and optionally set an expiration date.
  4. Copy the token immediately -- it is only shown once and cannot be retrieved later.

Authentication

Include your token in the Authorization header as a Bearer token:

curl -H "Authorization: Bearer jl_your_token_here" \
https://go.example.com/api/v1/links

Python (requests)

import requests

headers = {"Authorization": "Bearer jl_your_token_here"}
resp = requests.get("https://go.example.com/api/v1/links", headers=headers)
links = resp.json()

Pagination

List endpoints use cursor-based pagination. The response includes a next_cursor field when more results are available.

Parameters

ParameterDefaultMaxDescription
limit50200Number of items to return per page
cursor----Opaque cursor from a previous response's next_cursor

Example

Fetch the first page:

curl -H "Authorization: Bearer $TOKEN" \
"https://go.example.com/api/v1/links?limit=10"

Response:

{
"links": [ ... ],
"next_cursor": "eyJzbHVnIjoibXktbGluayJ9"
}

Fetch the next page using the cursor:

curl -H "Authorization: Bearer $TOKEN" \
"https://go.example.com/api/v1/links?limit=10&cursor=eyJzbHVnIjoibXktbGluayJ9"

When next_cursor is null, you have reached the last page.

Error Responses

All errors follow a consistent JSON shape:

{
"error": "human-readable error message",
"code": "MACHINE_READABLE_CODE"
}

Common Error Codes

HTTP StatusCodeDescription
400BAD_REQUESTInvalid request body or missing required fields
400INVALID_SLUGSlug format is invalid or uses a reserved prefix
401UNAUTHORIZEDMissing or invalid Bearer token
403FORBIDDENAuthenticated but not authorized for this resource
404NOT_FOUNDResource does not exist
409SLUG_CONFLICTSlug is already taken
409DUPLICATE_OWNERUser is already an owner of this link

API Reference

GET /api/v1/links?limit=50&cursor=...

Returns links owned by the authenticated user. Admins see all links.

POST /api/v1/links
{
"slug": "my-link",
"url": "https://example.com",
"title": "Example",
"description": "An example link",
"tags": ["example", "docs"]
}

The authenticated user becomes the primary owner. slug and url are required. title, description, and tags are optional.

GET /api/v1/links/{id}

Returns a single link. Only owners and admins may access.

PUT /api/v1/links/{id}
{
"url": "https://new-example.com",
"title": "Updated Title",
"description": "Updated description",
"tags": ["updated"]
}

Updates the link's URL, title, description, and tags. The slug is immutable and cannot be changed.

DELETE /api/v1/links/{id}

Returns 204 No Content on success. Only owners and admins may delete.

POST /api/v1/links/suggest
{
"url": "https://reactjs.org/docs/hooks-intro.html",
"title": "Introducing Hooks",
"description": ""
}

Uses a configured LLM to suggest a slug, title, description, and tags for a URL. url is required; the optional title and description are passed to the model as additional context. The browser extension calls this endpoint to pre-fill the create-link form.

{
"slug": "react-hooks",
"title": "Introducing Hooks – React",
"description": "An overview of React Hooks and the problems they solve.",
"tags": ["react", "frontend", "docs"]
}

Suggested values are validated server-side (the slug follows the same rules as a user-supplied slug; over-length title/description are dropped), so any field may come back empty.

StatusMeaning
200Suggestions returned
400url is missing
401Missing or invalid token
502The LLM provider returned an error or malformed response
503AI suggestions are not configured (JOE_LLM_PROVIDER unset)

This endpoint requires the server to be configured with an LLM provider — see Configuration → AI Metadata Suggestions.

Co-Owners

List Owners

GET /api/v1/links/{id}/owners

Returns all owners of a link with their id, email, and is_primary flag.

Add a Co-Owner

POST /api/v1/links/{id}/owners
{
"email": "colleague@example.com"
}

The user must already have an account in joe-links. Returns 201 with the new owner entry.

Remove a Co-Owner

DELETE /api/v1/links/{id}/owners/{uid}

Returns 204 No Content. The primary owner cannot be removed.

Tokens

List Tokens

GET /api/v1/tokens

Returns all tokens for the authenticated user. Token hashes are never included.

Create a Token

POST /api/v1/tokens
{
"name": "CI Pipeline",
"expires_at": "2026-12-31T23:59:59Z"
}

Returns the plaintext token in the response. This is the only time the token value is returned -- store it securely.

Revoke a Token

DELETE /api/v1/tokens/{id}

Soft-deletes the token. Returns 204 No Content.

Swagger UI

For interactive API exploration, visit /api/docs/ on your joe-links instance. The Swagger UI provides a complete reference with request/response schemas and the ability to try requests directly.