Trail API

Multi-user link journaling service

Executive Overview

Trail is a production-ready, multi-user link journaling API service that enables users to create, share, and engage with short-form content entries (140 characters) with optional media attachments and automatic URL preview enrichment.

Key Capabilities

Google OAuth 2.0
Secure authentication with JWT tokens
Link Journaling
140-char posts with URL previews, auto short URL resolution
Media Upload
Images & videos (20MB max each), up to 3 per entry
Full-Text Search
Fast search with relevance ranking
Engagement
Claps and threaded comments
RSS Feeds
Global and per-user feeds
Notifications
Real-time updates for mentions & claps
Moderation
User muting and content reporting
View Tracking
Unique view counts for entries, comments & profiles

System Architecture

graph LR Client[Client Application] API[Trail API] Auth[Auth Middleware] RateLimit[Rate Limiter] DB[(MariaDB)] Iframely[Iframely API] Client -->|HTTPS/JSON| API API --> Auth API --> RateLimit Auth -->|JWT Verify| API RateLimit -->|Throttle| API API -->|SQL| DB API -->|URL Preview| Iframely

Quick Stats

30+
API Endpoints
RESTful
API Design
JSON
Data Format
HTTPS
Secure Transport
OAuth 2.0
Authentication
UTF-8
Character Encoding
Tech Stack: PHP 8.4.16-nmm1 • Slim Framework • MariaDB • JWT Authentication • Iframely URL Enrichment

Quick Start Guide

📖 Public Access: You can view entries, comments, and user profiles without authentication. Authentication is only required to create, edit, or delete content.

Public API Usage (No Authentication)

# List all public entries curl https://trail.services.kibotu.net/api/entries?limit=20 # Search entries curl https://trail.services.kibotu.net/api/entries?q=example&limit=20 # Get a specific entry curl https://trail.services.kibotu.net/api/entries/abc123 # View user's entries curl https://trail.services.kibotu.net/api/users/alice/entries # Search user's entries curl https://trail.services.kibotu.net/api/users/alice/entries?q=example

Authenticated API Usage

To create or modify content, follow these steps:

Step 1: Get Your API Token

  1. Sign in to Trail at https://trail.services.kibotu.net using Google OAuth
  2. Navigate to your Profile page
  3. Find the API Token section (above Muted Users)
  4. Click the eye icon to reveal your token
  5. Click the copy icon to copy it to your clipboard

Step 2: Create Your First Entry

# Replace YOUR_API_TOKEN with your actual token curl -X POST https://trail.services.kibotu.net/api/entries \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"text":"Hello from Trail API! 🚀"}'

Step 3: List Your Entries

# List entries you created curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/profile
Success! You're now using the Trail API. Your API token never expires and can be regenerated anytime from your profile page.
💡 Pro Tips:
  • Store your API token securely (e.g., environment variables)
  • Never commit tokens to version control
  • Regenerate your token if it's compromised
  • Use the same token across all your applications

Authentication & Security

How to Authenticate

API Token Authentication

All API requests require authentication using your personal API token. Each user has a unique, persistent token that never expires.

Getting Your Token:
  1. Sign in at https://trail.services.kibotu.net
  2. Go to your Profile page
  3. Find the API Token section
  4. Click the eye icon to reveal your token
  5. Copy the token to use in your requests
Using Your Token:
# Include your token in the Authorization header curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/entries # Example: Create a new entry curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"text":"Hello from API!"}' \ https://trail.services.kibotu.net/api/entries
Token Properties:
  • Format: 64-character hexadecimal string
  • Expiration: Never expires (until you regenerate it)
  • Regeneration: Available anytime from your profile page
  • Security: Treat like a password - never share or commit to version control
  • Scope: Full access to all API endpoints (same permissions as your account)
🔒 Security Best Practices:
  • Store tokens in environment variables, not in code
  • Never commit tokens to version control (add to .gitignore)
  • Regenerate your token immediately if compromised
  • Use HTTPS for all API requests (enforced by server)
  • Keep your token private - it provides full account access

Security Model

Rate Limiting

  • Authentication: 5 attempts per 5 minutes
  • General API: 180 requests per minute
  • Hourly Limit: 3000 requests per hour
  • Response: HTTP 429 when exceeded

Security Features

  • CORS: Configurable cross-origin resource sharing
  • CSRF Protection: Token-based CSRF prevention
  • Content Sanitization: XSS prevention on all user input
  • Bot Protection: User-agent validation and pattern detection
  • SQL Injection: Prepared statements for all queries

Authorization Levels

Public
No authentication required
User
Authenticated users
Admin
Administrative privileges

Core Concepts

Entries

Short-form posts (max 280 characters) that can include:

  • Text: UTF-8 text with emoji support
  • URLs: Automatic preview enrichment via Iframely
  • Images: Up to 3 images (20MB each, JPEG/PNG/GIF/WebP/SVG/AVIF)
  • Videos: MP4, WebM, MOV (20MB each) with custom player controls
  • Timestamps: Custom creation dates for imports
  • Hash IDs: Secure, obfuscated entry identifiers

Users

User profiles with Google OAuth authentication:

  • Nickname: Unique @username for public profiles
  • Avatar: Google photo or Gravatar fallback
  • Bio: Optional profile description
  • Privacy: Public profile pages and RSS feeds
  • Statistics: Entry, comment counts, last activity, and login history

Profile stats object:

{ "stats": { "entry_count": 42, "link_count": 18, "comment_count": 7, "last_entry_at": "2026-02-09 14:30:00", "previous_login_at": "2026-02-08 09:15:22" } }
  • entry_count: Total entries by the user
  • link_count: Entries containing a URL preview
  • comment_count: Total comments by the user
  • last_entry_at: Timestamp of the most recent entry (null if none)
  • previous_login_at: Timestamp of the login before the current session (null if first login)

Engagement

Social interaction features:

  • Claps: 1-50 claps per user per entry/comment
  • Comments: Threaded discussions (max 280 chars)
  • Mentions: @username mentions with notifications
  • Views: Unique view tracking for entries, comments, and profiles

View Tracking

Analytics and view counting for content:

  • Entry views: Track unique views per entry
  • Comment views: Track unique views per comment
  • Profile views: Track unique profile page views
  • Deduplication: 24-hour window for unique view counting
  • Anonymous support: Optional fingerprint for anonymous users

Pagination

Cursor-based pagination for infinite scroll:

  • Parameters: ?limit=20&before=TIMESTAMP
  • Response: has_more and next_cursor fields
  • Default: 20 items per page
  • Maximum: 100 items per page (public), 50 (user-specific)

Search

Full-text search with relevance ranking:

  • Query: ?q=search+term parameter
  • Full-text: 4+ characters (relevance ranked)
  • LIKE search: 1-3 characters (pattern matching)
  • Max length: 200 characters
  • Public access: No authentication required
  • Filtering: Authenticated users get personalized results (muted users hidden)

Moderation

User-controlled content filtering:

  • Muting: Hide all content from specific users
  • Reporting: Flag entries/comments for review
  • Hiding: Personally hide individual entries
  • Admin tools: Full moderation capabilities

API Endpoints

All endpoints organized by user journey and functionality. Use the search box to quickly find specific endpoints.

Public Endpoints

No authentication required

GET /api/health PUBLIC
Health check endpoint - Returns API status
curl https://trail.services.kibotu.net/api/health
GET /api/config PUBLIC
Get public configuration values (e.g., max_text_length, max_images_per_entry)
curl https://trail.services.kibotu.net/api/config
GET /api/rss PUBLIC
Global RSS feed of all public entries
curl https://trail.services.kibotu.net/api/rss
GET /api/users/{nickname}/rss PUBLIC
User-specific RSS feed by nickname
curl https://trail.services.kibotu.net/api/users/alice/rss
GET /api/users/{nickname} PUBLIC
Get public profile by nickname - Includes stats (entry_count, link_count, comment_count, last_entry_at, previous_login_at)
curl https://trail.services.kibotu.net/api/users/alice
GET /api/users/{nickname}/entries PUBLIC
List entries by user nickname - Supports cursor-based pagination
curl https://trail.services.kibotu.net/api/users/alice/entries?limit=20
GET /api/entries PUBLIC
List all entries with optional search - Supports cursor-based pagination and full-text search
curl https://trail.services.kibotu.net/api/entries?limit=20 # With search: curl https://trail.services.kibotu.net/api/entries?q=example&limit=20
GET /api/entries/{id} PUBLIC
Get a single entry by hash ID
curl https://trail.services.kibotu.net/api/entries/abc123
GET /api/entries/{id}/claps PUBLIC
Get clap count for entry
curl https://trail.services.kibotu.net/api/entries/123/claps
GET /api/tags PUBLIC
List all tags with entry counts - Optional ?search= query parameter for autocomplete
curl https://trail.services.kibotu.net/api/tags?search=python
GET /api/tags/{slug}/entries PUBLIC
List entries with a specific tag - Supports cursor pagination (?limit=20&before=cursor)
curl https://trail.services.kibotu.net/api/tags/python/entries?limit=20
GET /api/entries/{id}/tags PUBLIC
Get tags for a specific entry
curl https://trail.services.kibotu.net/api/entries/123/tags
GET /api/entries/{id}/comments PUBLIC
List comments for entry
curl https://trail.services.kibotu.net/api/entries/123/comments
GET /api/comments/{id}/claps PUBLIC
Get clap count for comment
curl https://trail.services.kibotu.net/api/comments/456/claps

Core User Endpoints

Profile and entry management (requires auth)

GET /api/profile USER 180/min
Get own profile - Includes stats (entry_count, link_count, comment_count, last_entry_at, previous_login_at)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/profile
PUT /api/profile USER 180/min
Update own profile
curl -X PUT \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"nickname":"alice","bio":"Hello world"}' \ https://trail.services.kibotu.net/api/profile
POST /api/entries USER 180/min
Create a new entry - Max 280 characters, max 3 images
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"text":"Check this out! https://example.com 🎉"}' \ https://trail.services.kibotu.net/api/entries
PUT /api/entries/{id} USER 180/min
Update own entry. Body: {text, image_ids?, skip_updated_at?}. Set skip_updated_at:true to preserve original timestamp.
curl -X PUT \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"text":"Updated text","skip_updated_at":true}' \ https://trail.services.kibotu.net/api/entries/123
DELETE /api/entries/{id} USER 180/min
Delete own entry
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/entries/123

Engagement

Claps, comments, and view tracking

POST /api/entries/{id}/claps USER 180/min
Add clap to entry (1-50 claps)
curl -X POST \ -b cookies.txt \ -H "Content-Type: application/json" \ -d '{"count":5}' \ https://trail.services.kibotu.net/api/entries/123/claps
PUT /api/entries/{id}/tags USER 180/min
Replace all tags for an entry (owner or admin only) - Idempotent operation
curl -X PUT \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"tags":["python","tutorial","machine-learning"]}' \ https://trail.services.kibotu.net/api/entries/123/tags
POST /api/entries/{id}/tags USER 180/min
Add a single tag to an entry (owner or admin only)
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"tag":"python"}' \ https://trail.services.kibotu.net/api/entries/123/tags
DELETE /api/entries/{id}/tags/{slug} USER 180/min
Remove a tag from an entry (owner or admin only)
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/entries/123/tags/python
POST /api/entries/{id}/comments USER 180/min
Create comment on entry
curl -X POST \ -b cookies.txt \ -H "Content-Type: application/json" \ -d '{"text":"Great post!"}' \ https://trail.services.kibotu.net/api/entries/123/comments
PUT /api/comments/{id} USER 180/min
Update own comment
curl -X PUT \ -b cookies.txt \ -H "Content-Type: application/json" \ -d '{"text":"Updated comment"}' \ https://trail.services.kibotu.net/api/comments/456
DELETE /api/comments/{id} USER 180/min
Delete own comment
curl -X DELETE \ -b cookies.txt \ https://trail.services.kibotu.net/api/comments/456
POST /api/comments/{id}/claps USER 180/min
Add clap to comment
curl -X POST \ -b cookies.txt \ -H "Content-Type: application/json" \ -d '{"count":3}' \ https://trail.services.kibotu.net/api/comments/456/claps
POST /api/entries/{id}/views PUBLIC 180/min
Record entry view - Tracks unique views with 24h deduplication
curl -X POST \ -H "Content-Type: application/json" \ -d '{"fingerprint":"optional-client-fingerprint"}' \ https://trail.services.kibotu.net/api/entries/123/views
POST /api/comments/{id}/views PUBLIC 180/min
Record comment view - Tracks unique views with 24h deduplication
curl -X POST \ -H "Content-Type: application/json" \ -d '{"fingerprint":"optional-client-fingerprint"}' \ https://trail.services.kibotu.net/api/comments/456/views
POST /api/users/{nickname}/views PUBLIC 180/min
Record profile view - Tracks unique profile views with 24h deduplication
curl -X POST \ -H "Content-Type: application/json" \ -d '{"fingerprint":"optional-client-fingerprint"}' \ https://trail.services.kibotu.net/api/users/alice/views

Media Upload

Image and video upload (requires auth)

POST /api/images/upload/init USER 180/min
Initialize chunked media upload - Images or videos (20MB max)
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"filename":"video.mp4","total_size":10240000}' \ https://trail.services.kibotu.net/api/images/upload/init
POST /api/images/upload/chunk USER 180/min
Upload media chunk (512KB chunks)
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -F "upload_id=abc123" \ -F "chunk_index=0" \ -F "chunk=@chunk0.bin" \ https://trail.services.kibotu.net/api/images/upload/chunk
POST /api/images/upload/complete USER 180/min
Complete chunked upload - Validates and processes media
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"upload_id":"abc123"}' \ https://trail.services.kibotu.net/api/images/upload/complete
GET /api/images/{id} USER
Serve media by ID (images and videos)
curl -b cookies.txt https://trail.services.kibotu.net/api/images/789
DELETE /api/images/{id} USER 180/min
Delete own media (image or video)
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/images/789

Moderation

Content reporting and user muting (requires auth)

POST /api/entries/{id}/report USER 180/min
Report entry for moderation
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"reason":"spam"}' \ https://trail.services.kibotu.net/api/entries/123/report
POST /api/comments/{id}/report USER 180/min
Report comment for moderation
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"reason":"harassment"}' \ https://trail.services.kibotu.net/api/comments/456/report
POST /api/users/{id}/mute USER 180/min
Mute user (hide their content)
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/users/789/mute
DELETE /api/users/{id}/mute USER 180/min
Unmute user
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/users/789/mute
GET /api/users/{id}/mute-status USER 180/min
Check if user is muted
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/users/789/mute-status
GET /api/filters USER 180/min
Get content filters
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/filters

Notifications

Real-time updates (requires auth)

GET /api/notifications USER 180/min
List notifications
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/notifications
PUT /api/notifications/{id}/read USER 180/min
Mark notification as read
curl -X PUT \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/notifications/123/read
PUT /api/notifications/read-all USER 180/min
Mark all notifications as read
curl -X PUT \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/notifications/read-all
DELETE /api/notifications/{id} USER 180/min
Delete notification
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/notifications/123
GET /api/notifications/preferences USER 180/min
Get notification preferences
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/notifications/preferences
PUT /api/notifications/preferences USER 180/min
Update notification preferences
curl -X PUT \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"email_mentions":true}' \ https://trail.services.kibotu.net/api/notifications/preferences

Admin Endpoints

Administrative functions (requires admin)

GET /api/admin/entries ADMIN 180/min
List all entries (admin only)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/entries
POST /api/admin/entries/{id} ADMIN 180/min
Update any entry (admin only)
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"text":"Updated text"}' \ https://trail.services.kibotu.net/api/admin/entries/123
DELETE /api/admin/entries/{id} ADMIN 180/min
Delete any entry (admin only)
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/entries/123
PUT /api/admin/entries/tags ADMIN 180/min
Batch set tags for multiple entries (admin only) - Max 100 entries per request
curl -X PUT \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"entries":[{"id":"abc123","tags":["python","ai"]},{"id":"def456","tags":["rust"]}]}' \ https://trail.services.kibotu.net/api/admin/entries/tags
GET /api/admin/users ADMIN 180/min
List all users (admin only)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/users
DELETE /api/admin/users/{id} ADMIN 180/min
Delete user (admin only)
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/users/123
DELETE /api/admin/users/{id}/entries ADMIN 180/min
Delete all user entries (admin only)
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/users/123/entries
DELETE /api/admin/users/{id}/comments ADMIN 180/min
Delete all user comments (admin only)
curl -X DELETE \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/users/123/comments
POST /api/admin/cache/clear ADMIN 180/min
Clear application cache (admin only)
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/cache/clear
GET /api/admin/error-logs ADMIN 180/min
View error logs (admin only)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/error-logs
GET /api/admin/error-stats ADMIN 180/min
Get error statistics (admin only)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/error-stats
POST /api/admin/error-logs/cleanup ADMIN 180/min
Cleanup old error logs (admin only)
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/error-logs/cleanup
POST /api/admin/images/prune ADMIN 180/min
Prune orphaned images (admin only)
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/images/prune
GET /api/admin/short-links ADMIN 180/min
List short URLs (t.co, bit.ly, etc.) pending resolution (admin only)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/short-links
GET /api/admin/short-links/stats ADMIN 180/min
Get short link statistics - total, pending, failed counts (admin only)
curl -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/short-links/stats
POST /api/admin/short-links/resolve ADMIN 180/min
Resolve short URLs to their final destinations (admin only) - Processes untried URLs first, then retries oldest failures
curl -X POST \ -H "Authorization: Bearer YOUR_API_TOKEN" \ https://trail.services.kibotu.net/api/admin/short-links/resolve

Advanced Features

The POST /api/entries endpoint supports advanced features for importing content from other platforms (e.g., Twitter/X):

Custom Creation Dates

Import entries with their original timestamps using Twitter date format:

"created_at": "Fri Nov 28 10:54:34 +0000 2025"

Format: Day Mon DD HH:MM:SS ±ZZZZ YYYY (e.g., timezone +0000 for UTC, -0800 for PST)

Inline Media Upload

Upload images or videos directly in the request (base64 encoded):

"media": [{ "data": "base64_encoded_media_data...", "filename": "photo.jpg", "mime_type": "image/jpeg", "image_type": "post" }]

Images: JPEG, PNG, GIF (animated supported), WebP, SVG, AVIF. Max 20MB each.
Videos: MP4, WebM, MOV. Max 20MB each. Custom player with progress bar.
Maximum 3 media items per entry.

Raw Upload Mode ADMIN

Skip image processing for faster imports (requires admin privileges):

"raw_upload": true

When enabled, images are saved without resizing or WebP conversion. Use for trusted sources only. Requires admin privileges.

Initial Claps

Set engagement metrics when importing:

"initial_claps": 25

Valid range: 1-50 (normal mode), 1-100,000 (with raw_upload admin mode). Claps are attributed to the authenticated user (author).

Initial Views ADMIN

Set view counts when importing (requires raw_upload mode):

"initial_views": 1500

Only available with raw_upload: true (admin mode). Sets the initial view count for the entry.

Response Format

Successful entry creation returns:

{ "id": 123, "created_at": "2025-11-28 10:54:34", "images": [{ "id": 456, "url": "/uploads/images/1/1_1738246496_abc.webp", "width": 1200, "height": 800, "file_size": 45678 }], "clap_count": 25, "user_clap_count": 25, "view_count": 1500 }

Full-Text Search

The GET /api/entries and GET /api/users/{nickname}/entries endpoints support full-text search:

# Search is public - no authentication required curl https://trail.services.kibotu.net/api/entries?q=search+term&limit=20 # Search user's entries curl https://trail.services.kibotu.net/api/users/alice/entries?q=example&limit=20
  • Full-text mode: 4+ characters with relevance ranking
  • LIKE mode: 1-3 characters with pattern matching
  • Max length: 200 characters
  • Public access: No authentication required
  • Personalization: Authenticated users get filtered results (muted users hidden)

Cursor-Based Pagination

Efficient pagination for infinite scroll implementations:

// First page GET /api/entries?limit=20 // Next page (use next_cursor from response) GET /api/entries?limit=20&before=2025-01-15%2010:30:00

Response format:

{ "entries": [...], "has_more": true, "next_cursor": "2025-01-15 10:30:00", "limit": 20 }

URL Preview Enrichment

Automatic link unfurling using Iframely API:

  • Detects URLs in entry text automatically
  • Fetches title, description, and thumbnail
  • Caches previews to minimize API calls
  • Graceful fallback if preview fails

Short URL Resolution

Automatic resolution of shortened URLs to their final destinations:

  • Automatic on create: Short URLs (t.co, bit.ly, tinyurl.com, etc.) are resolved when posting new entries
  • Better previews: Preview metadata is fetched from the final URL, not the shortener
  • Graceful fallback: If resolution fails, the original short URL is preserved
  • Admin migration tool: Bulk resolve existing short URLs via the admin dashboard

Supported shorteners:

t.co, bit.ly, tinyurl.com, goo.gl, ow.ly, is.gd, buff.ly, j.mp, dlvr.it, fb.me, lnkd.in, rebrand.ly, cutt.ly, and 20+ more

Performance & Limits

Rate Limits

5
Auth attempts / 5 min
180
Requests / minute
3000
Requests / hour
429
HTTP status on exceed

Content Limits

Resource Limit Notes
Entry text 280 characters UTF-8, emoji supported
Comment text 280 characters UTF-8, emoji supported
Images per entry 3 max Per entry or comment
Media size 20MB max Per image or video
Image formats JPEG, PNG, GIF, WebP, SVG, AVIF Static images converted to WebP, animated GIFs preserved
Video formats MP4, WebM, MOV Stored as-is with custom player
Search query 200 characters Auto-sanitized
Initial claps 1-50 (normal), 1-100,000 (admin) For imports only (higher limit with raw_upload)
Initial views 0+ (no limit) Admin only (requires raw_upload)
Claps per action 1-50 range Per user per entry

Pagination Limits

  • Default limit: 20 items per page
  • Max limit (public): 100 items per page
  • Max limit (user-specific): 50 items per page
  • Cursor format: ISO 8601 timestamp

Caching

  • Image ETags: Browser caching for static images
  • No-cache headers: Session-dependent content
  • URL previews: Cached in database to reduce API calls

Technical Reference

Response Formats

Entry Response

{ "id": 123, "text": "Entry text", "created_at": "2025-02-09 12:00:00", "user_id": 456, "user_nickname": "alice", "clap_count": 10, "comment_count": 3, "view_count": 42 }

Comment Response

{ "id": 789, "text": "Comment text", "created_at": "2025-02-09 12:30:00", "user_id": 456, "user_nickname": "alice", "clap_count": 5, "view_count": 18 }

Profile Response (GET /api/profile, GET /api/users/{nickname})

{ "id": 456, "nickname": "alice", "name": "Alice", "bio": "Hello world", "created_at": "2025-06-15 08:00:00", "stats": { "entry_count": 42, "link_count": 18, "comment_count": 7, "last_entry_at": "2026-02-09 14:30:00", "previous_login_at": "2026-02-08 09:15:22" } }

Error Response

{ "error": "Error message", "code": "ERROR_CODE" // Optional }

HTTP Status Codes

Code Meaning Description
200 OK Request successful
201 Created Resource created successfully
400 Bad Request Invalid request parameters
401 Unauthorized Authentication required or token expired
403 Forbidden Insufficient permissions
404 Not Found Resource not found
429 Too Many Requests Rate limit exceeded
500 Internal Server Error Server error occurred

Error Codes

Code Description
AUTH_REQUIRED Authentication required for this operation
INVALID_TOKEN JWT token is invalid or expired
RATE_LIMIT_EXCEEDED Too many requests, please slow down
VALIDATION_ERROR Request validation failed

Date Formats

ISO 8601 (Default)

"2025-02-09 12:00:00" // YYYY-MM-DD HH:MM:SS

Twitter Format (For Imports)

"Fri Nov 28 10:54:34 +0000 2025" // Day Mon DD HH:MM:SS ±ZZZZ YYYY

Supported timezones: UTC (+0000), PST (-0800), EST (-0500), etc.

Hash IDs

Entry IDs are obfuscated using hash IDs for security and aesthetics:

  • Format: Alphanumeric string (e.g., "abc123xyz")
  • Usage: Use in URLs and API calls instead of numeric IDs
  • Decoding: Automatically handled by API
  • Security: Prevents enumeration attacks