CattoPic API Documentation

December 15, 2025 · View on GitHub

中文

Overview

CattoPic is an image hosting and management service that provides image upload, storage, format conversion, and random retrieval features.

Basic Information

ItemDescription
Base URLhttps://your-worker.workers.dev
AuthenticationBearer Token (API Key)
Response FormatJSON
EncodingUTF-8

Tech Stack

  • Backend Framework: Hono (Cloudflare Workers)
  • Database: Cloudflare D1 (SQLite)
  • Storage: Cloudflare R2 (Object Storage)

Authentication

All API requests except public endpoints require an API Key in the header:

Authorization: Bearer <your-api-key>

Authentication Failure Response

{
  "success": false,
  "error": "Unauthorized"
}

HTTP Status Code: 401


Public Endpoints

The following endpoints can be accessed without authentication.

Get Random Image

Get a random image with support for tag filtering and format conversion.

Request

GET /api/random

Query Parameters

ParameterTypeRequiredDescriptionExample
tagsstringNoComma-separated tags, image must contain ALL tagslandscape,nature
excludestringNoComma-separated tags to excludeblurry,test
orientationstringNoDirection: landscape / portrait / autoauto
formatstringNoFormat: original / webp / avifwebp

Response

  • Success: Returns a 302 redirect to the selected image URL

    • Location: final image URL (R2 public URL or /cdn-cgi/image/... transformed URL)
    • Cache-Control: no-cache, no-store, must-revalidate
  • Failure (no matching image):

{
  "success": false,
  "error": "No images found matching criteria"
}

curl Examples

# Get random image
curl "https://your-worker.workers.dev/api/random"

# Get random image with tag filtering
curl "https://your-worker.workers.dev/api/random?tags=nature,outdoor&orientation=landscape"

# Get WebP format
curl "https://your-worker.workers.dev/api/random?format=webp" -o random.webp

Use Case Examples

# Use Case 1: Random website background (desktop, landscape)
curl "https://your-worker.workers.dev/api/random?orientation=landscape&format=webp"

# Use Case 2: Mobile wallpaper API (portrait)
curl "https://your-worker.workers.dev/api/random?orientation=portrait&tags=wallpaper"

# Use Case 3: Cat pictures API (exclude NSFW content)
curl "https://your-worker.workers.dev/api/random?tags=cat&exclude=nsfw,private"

# Use Case 4: Nature landscapes (multiple tag combination)
curl "https://your-worker.workers.dev/api/random?tags=nature,landscape&exclude=city"

# Use Case 5: Direct use in HTML img tag
# <img src="https://your-worker.workers.dev/api/random?orientation=auto" />

# Use Case 6: Auto orientation detection (based on User-Agent)
# Mobile devices get portrait images, desktop devices get landscape images
curl -A "Mozilla/5.0 (iPhone)" "https://your-worker.workers.dev/api/random?orientation=auto"

Get Image File

Directly retrieve image files from R2 storage.

Request

GET /r2/{path}

Path Parameters

ParameterTypeDescription
pathstringObject path in R2

Response

  • Success: Returns image binary data

    • Cache-Control: public, max-age=31536000 (1 year cache)
  • Failure:

{
  "success": false,
  "error": "Not found"
}

curl Example

curl "https://your-worker.workers.dev/r2/images/landscape/550e8400-e29b-41d4-a716-446655440000.jpg" -o image.jpg

Image Management Endpoints

List Images

Get all images with pagination, supports tag and orientation filtering.

Request

GET /api/images

Headers

Authorization: Bearer <api-key>

Query Parameters

ParameterTypeDefaultDescription
pagenumber1Page number
limitnumber12Items per page
tagstring-Filter by tag
orientationstring-landscape or portrait
formatstringallall / gif / webp / avif / original

Response

{
  "success": true,
  "images": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "originalName": "photo.jpg",
      "uploadTime": "2024-12-08T10:30:00Z",
      "expiryTime": null,
      "orientation": "landscape",
      "tags": ["nature", "outdoor"],
      "format": "jpg",
      "width": 1920,
      "height": 1080,
      "paths": {
        "original": "images/landscape/550e8400-e29b-41d4-a716-446655440000.jpg",
        "webp": "images/landscape/550e8400-e29b-41d4-a716-446655440000.webp",
        "avif": "images/landscape/550e8400-e29b-41d4-a716-446655440000.avif"
      },
      "sizes": {
        "original": 245632,
        "webp": 156789,
        "avif": 134567
      },
      "urls": {
        "original": "https://your-worker.workers.dev/r2/images/landscape/550e8400-e29b-41d4-a716-446655440000.jpg",
        "webp": "https://your-worker.workers.dev/r2/images/landscape/550e8400-e29b-41d4-a716-446655440000.webp",
        "avif": "https://your-worker.workers.dev/r2/images/landscape/550e8400-e29b-41d4-a716-446655440000.avif"
      }
    }
  ],
  "page": 1,
  "limit": 12,
  "total": 150,
  "totalPages": 13
}

curl Examples

# Get first page
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/images?page=1&limit=12"

# Filter by tag
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/images?tag=nature&orientation=landscape"

Get Image Details

Get detailed information for a specific image.

Request

GET /api/images/{id}

Path Parameters

ParameterTypeDescription
idstringImage UUID

Response

{
  "success": true,
  "image": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "originalName": "photo.jpg",
    "uploadTime": "2024-12-08T10:30:00Z",
    "expiryTime": null,
    "orientation": "landscape",
    "tags": ["nature", "outdoor"],
    "format": "jpg",
    "width": 1920,
    "height": 1080,
    "paths": {
      "original": "images/landscape/550e8400-e29b-41d4-a716-446655440000.jpg",
      "webp": "images/landscape/550e8400-e29b-41d4-a716-446655440000.webp",
      "avif": "images/landscape/550e8400-e29b-41d4-a716-446655440000.avif"
    },
    "sizes": {
      "original": 245632,
      "webp": 156789,
      "avif": 134567
    },
    "urls": {
      "original": "https://your-worker.workers.dev/r2/images/...",
      "webp": "https://your-worker.workers.dev/r2/images/...",
      "avif": "https://your-worker.workers.dev/r2/images/..."
    }
  }
}

Error Responses

{
  "success": false,
  "error": "Invalid image ID"
}
{
  "success": false,
  "error": "Image not found"
}

curl Example

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/images/550e8400-e29b-41d4-a716-446655440000"

Update Image Metadata

Update image tags and expiry time.

Request

PUT /api/images/{id}

Path Parameters

ParameterTypeDescription
idstringImage UUID

Request Body

{
  "tags": ["nature", "outdoor", "landscape"],
  "expiryMinutes": 1440
}
FieldTypeDescription
tagsstring[] | stringNew tag list (array or comma-separated string)
expiryMinutesnumberExpiry time in minutes, 0 to remove expiry

Response

{
  "success": true,
  "image": {
    // Updated image object, same format as get details
  }
}

curl Example

curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tags": ["nature", "outdoor"], "expiryMinutes": 1440}' \
  "https://your-worker.workers.dev/api/images/550e8400-e29b-41d4-a716-446655440000"

Delete Image

Delete an image and all its format versions.

Request

DELETE /api/images/{id}

Path Parameters

ParameterTypeDescription
idstringImage UUID

Response

{
  "success": true,
  "message": "Image deleted"
}

Notes

Delete operation will:

  1. Delete all format versions from R2 (original, webp, avif)
  2. Delete metadata record from database
  3. Auto-cleanup associated tag relationships

curl Example

curl -X DELETE \
  -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/images/550e8400-e29b-41d4-a716-446655440000"

Upload Endpoint

Upload Images

Upload a single image file per request. For multiple files, send multiple requests (the frontend uses concurrent uploads).

Request

POST /api/upload/single

Headers

Authorization: Bearer <api-key>
Content-Type: multipart/form-data

Request Body (FormData)

FieldTypeRequiredDescription
image (or file)FileYesImage file, max 70MB
tagsstringNoComma-separated tags
expiryMinutesnumberNoExpiry time in minutes, 0 for never expires

Upload Limits

LimitValue
Max file size70MB
Supported formatsjpeg, jpg, png, gif, webp, avif

Response

{
  "success": true,
  "result": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "success",
    "urls": {
      "original": "https://your-worker.workers.dev/r2/images/landscape/550e8400-e29b-41d4-a716-446655440000.jpg",
      "webp": "https://your-worker.workers.dev/r2/images/landscape/550e8400-e29b-41d4-a716-446655440000.webp",
      "avif": "https://your-worker.workers.dev/r2/images/landscape/550e8400-e29b-41d4-a716-446655440000.avif"
    },
    "orientation": "landscape",
    "tags": ["nature", "outdoor"],
    "sizes": {
      "original": 245632,
      "webp": 156789,
      "avif": 134567
    },
    "expiryTime": "2024-12-15T10:30:00Z"
  }
}

Auto Features

  • Auto-detect image orientation (landscape/portrait)
  • Auto-generate WebP and AVIF format versions
  • Auto-calculate expiry time

curl Example

# Upload single file
curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "image=@photo.jpg" \
  -F "tags=nature,outdoor" \
  "https://your-worker.workers.dev/api/upload/single"

Tag Management Endpoints

List All Tags

Get all tags with their usage counts.

Request

GET /api/tags

Response

{
  "success": true,
  "tags": [
    { "name": "nature", "count": 45 },
    { "name": "outdoor", "count": 32 },
    { "name": "landscape", "count": 28 },
    { "name": "portrait", "count": 15 }
  ]
}

curl Example

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/tags"

Create New Tag

Create a new tag.

Request

POST /api/tags

Request Body

{
  "name": "mountain"
}
FieldTypeDescription
namestringTag name (auto-converted to lowercase, supports Chinese)

Tag Naming Rules

  • Auto-converted to lowercase
  • Supports Chinese characters
  • Allows hyphens (-) and underscores (_)
  • Maximum 50 characters
  • Auto-trims leading/trailing spaces

Response

{
  "success": true,
  "tag": {
    "name": "mountain",
    "count": 0
  }
}

curl Example

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "mountain"}' \
  "https://your-worker.workers.dev/api/tags"

Rename Tag

Rename a tag, automatically updates all related images.

Request

PUT /api/tags/{name}

Path Parameters

ParameterTypeDescription
namestringOriginal tag name (URL encoded)

Request Body

{
  "newName": "mountains"
}

Response

{
  "success": true,
  "tag": {
    "name": "mountains",
    "count": 12
  }
}

Error Response

{
  "success": false,
  "error": "New name must be different from old name"
}

curl Example

curl -X PUT \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"newName": "mountains"}' \
  "https://your-worker.workers.dev/api/tags/mountain"

Delete Tag

Delete a tag and all associated images.

Request

DELETE /api/tags/{name}

Path Parameters

ParameterTypeDescription
namestringTag name (URL encoded)

Response

{
  "success": true,
  "message": "Tag and associated images deleted",
  "deletedImages": 28
}

curl Example

curl -X DELETE \
  -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/tags/mountain"

Batch Update Tags

Batch add or remove tags for multiple images.

Request

POST /api/tags/batch

Request Body

{
  "imageIds": [
    "550e8400-e29b-41d4-a716-446655440000",
    "660e8400-e29b-41d4-a716-446655440001"
  ],
  "addTags": ["landscape", "nature"],
  "removeTags": ["test", "draft"]
}
FieldTypeDescription
imageIdsstring[]Array of image UUIDs
addTagsstring[]Tags to add
removeTagsstring[]Tags to remove

Response

{
  "success": true,
  "updatedCount": 2
}

curl Example

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "imageIds": ["550e8400-e29b-41d4-a716-446655440000"],
    "addTags": ["landscape"],
    "removeTags": ["draft"]
  }' \
  "https://your-worker.workers.dev/api/tags/batch"

System Endpoints

Validate API Key

Validate if an API Key is valid.

Request

POST /api/validate-api-key

Headers

Authorization: Bearer <api-key>

Response

{
  "success": true,
  "valid": true
}

curl Example

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/validate-api-key"

Get System Config

Get system configuration (upload limits, supported formats, etc.).

Request

GET /api/config

Response

{
  "success": true,
  "config": {
    "maxUploadCount": 50,
    "maxFileSize": 73400320,
    "supportedFormats": ["jpeg", "jpg", "png", "gif", "webp", "avif"],
    "imageQuality": 80
  }
}
FieldDescription
maxUploadCountMax files per upload
maxFileSizeMax file size in bytes
supportedFormatsSupported image formats
imageQualityImage conversion quality (1-100)

curl Example

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/config"

Cleanup Expired Images

Delete all expired images.

Request

POST /api/cleanup

Response

{
  "success": true,
  "deletedCount": 5
}

Notes

Cleanup operation will:

  1. Query all expired images (expiry_time < current time)
  2. Delete all format versions from R2
  3. Delete metadata records from database

curl Example

curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  "https://your-worker.workers.dev/api/cleanup"

Data Types

ImageMetadata

Image metadata object.

interface ImageMetadata {
  id: string;                           // UUID
  originalName: string;                 // Original filename
  uploadTime: string;                   // Upload time (ISO 8601)
  expiryTime?: string;                  // Expiry time (ISO 8601)
  orientation: 'landscape' | 'portrait'; // Orientation
  tags: string[];                       // Tag array
  format: string;                       // Original format
  width: number;                        // Width (pixels)
  height: number;                       // Height (pixels)
  paths: {
    original: string;                   // Original file R2 path
    webp: string;                       // WebP format R2 path
    avif: string;                       // AVIF format R2 path
  };
  sizes: {
    original: number;                   // Original file size (bytes)
    webp: number;                       // WebP file size (bytes)
    avif: number;                       // AVIF file size (bytes)
  };
  urls?: {
    original: string;                   // Original file URL
    webp: string;                       // WebP URL
    avif: string;                       // AVIF URL
  };
}

UploadResult

Upload result object.

interface UploadResult {
  id: string;                           // Image ID on success
  status: 'success' | 'error';          // Status
  urls?: {
    original: string;
    webp: string;
    avif: string;
  };
  orientation?: 'landscape' | 'portrait';
  tags?: string[];
  sizes?: {
    original: number;
    webp: number;
    avif: number;
  };
  expiryTime?: string;
  error?: string;                       // Error message on failure
}

Tag

Tag object.

interface Tag {
  name: string;   // Tag name
  count: number;  // Number of images using this tag
}

ApiResponse

Generic API response format.

interface ApiResponse<T = unknown> {
  success: boolean;
  data?: T;
  error?: string;
}

Error Handling

HTTP Status Codes

CodeMeaning
200Success
400Bad Request
401Unauthorized (missing or invalid API Key)
404Resource Not Found
500Internal Server Error

Error Response Format

All error responses follow a unified format:

{
  "success": false,
  "error": "Error description message"
}

Common Errors

Error MessageDescription
UnauthorizedAPI Key invalid or missing
Invalid image IDImage ID format incorrect (not UUID)
Image not foundImage does not exist
No images found matching criteriaNo images match the criteria
File exceeds maximum size of 10MBFile exceeds size limit
Too many files. Maximum is 20Upload file count exceeds limit
Tag name is requiredTag name is empty
New name must be different from old nameNew tag name same as old

CORS Configuration

All API endpoints have CORS enabled:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

Appendix

Endpoint Reference

EndpointMethodAuthDescription
/api/randomGETNoGet random image
/r2/*GETNoGet image file
/api/imagesGETYesList images
/api/images/:idGETYesGet image details
/api/images/:idPUTYesUpdate image metadata
/api/images/:idDELETEYesDelete image
/api/upload/singlePOSTYesUpload image
/api/tagsGETYesList all tags
/api/tagsPOSTYesCreate new tag
/api/tags/:namePUTYesRename tag
/api/tags/:nameDELETEYesDelete tag
/api/tags/batchPOSTYesBatch update tags
/api/validate-api-keyPOSTYesValidate API Key
/api/configGETYesGet system config
/api/cleanupPOSTYesCleanup expired images

Frontend Request Examples (JavaScript)

const API_URL = 'https://your-worker.workers.dev';
const API_KEY = 'your-api-key';

// Get image list
async function getImages(page = 1, limit = 12) {
  const response = await fetch(`${API_URL}/api/images?page=${page}&limit=${limit}`, {
    headers: {
      'Authorization': `Bearer ${API_KEY}`
    }
  });
  return response.json();
}

// Upload image
async function uploadImage(file, tags = []) {
  const formData = new FormData();
  formData.append('image', file);
  if (tags.length > 0) {
    formData.append('tags', tags.join(','));
  }

  const response = await fetch(`${API_URL}/api/upload/single`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`
    },
    body: formData
  });
  return response.json();
}

// Delete image
async function deleteImage(id) {
  const response = await fetch(`${API_URL}/api/images/${id}`, {
    method: 'DELETE',
    headers: {
      'Authorization': `Bearer ${API_KEY}`
    }
  });
  return response.json();
}