syaOS syaOS / Docs
GitHub Launch

Utility APIs

syaOS provides several utility API endpoints for common operations like fetching link previews, checking iframe embeddability, sharing applets, and admin operations. These endpoints are implemented as Vercel Edge Functions for optimal performance.

Link Preview

Fetches metadata from URLs for rich link previews. Supports Open Graph, Twitter Cards, and standard meta tags with special handling for YouTube URLs.

Endpoint

MethodPathDescription
GET/api/link-previewFetch URL metadata for previews

Request

Query Parameters:
ParameterTypeRequiredDescription
urlstringYesThe URL to fetch metadata from (must be HTTP or HTTPS)

Response

Success Response (200):
{
  "url": "https://example.com/page",
  "title": "Page Title",
  "description": "Page description from meta tags",
  "image": "https://example.com/og-image.jpg",
  "siteName": "Example Site"
}
Response Fields:
FieldTypeDescription
urlstringThe original URL
titlestring?Page title (from <title>, og:title, or twitter:title)
descriptionstring?Page description (from og:description, twitter:description, or meta description)
imagestring?Preview image URL (from og:image or twitter:image)
siteNamestring?Site name (from og:site_name or hostname)
Error Responses:
StatusDescription
400Missing or invalid URL
403Unauthorized origin
405Method not allowed
408Request timeout
429Rate limit exceeded
503Network error

Rate Limits

  • Global limit: 10 requests per minute per IP
  • Per-host limit: 5 requests per minute per IP per target hostname

Example

// Fetch link preview
const response = await fetch('/api/link-preview?url=https://github.com');
const metadata = await response.json();

console.log(metadata);
// {
//   url: "https://github.com",
//   title: "GitHub: Let's build from here",
//   description: "GitHub is where over 100 million developers...",
//   image: "https://github.githubassets.com/images/modules/site/social-cards/campaign-social.png",
//   siteName: "GitHub"
// }

Notes

  • YouTube URLs are handled specially using the oEmbed API for reliable metadata
  • Responses are cached for 1 hour (Cache-Control: public, max-age=3600)
  • HTML entities in metadata are automatically decoded
  • Relative image URLs are converted to absolute URLs

iFrame Check

Checks if a URL allows iframe embedding and optionally proxies content to bypass embedding restrictions. Also supports retrieving cached historical versions of pages.

Endpoint

MethodPathDescription
GET/api/iframe-checkCheck iframe embeddability or proxy content

Request

Query Parameters:
ParameterTypeRequiredDescription
urlstringYesThe URL to check or proxy
modestringNoOperation mode: check, proxy, ai, or list-cache (default: proxy)
yearstringNoYear for Wayback Machine or AI cache (e.g., "2020", "1000 BC")
monthstringNoMonth for Wayback Machine (e.g., "01" for January)
themestringNoTheme name - font overrides are skipped for "macosx" theme

Modes

check Mode

Performs a header-only check to determine if the URL allows iframe embedding.

Response (200):
{
  "allowed": true,
  "reason": null,
  "title": "Page Title"
}
{
  "allowed": false,
  "reason": "X-Frame-Options: DENY",
  "title": "Page Title"
}

proxy Mode (Default)

Proxies the content with embedding-blocking headers removed. Injects:

  • <base> tag for relative URL resolution
  • Click interceptor script for navigation handling
  • History API patch for cross-origin compatibility
  • Font override styles (unless theme is "macosx")

Response: The proxied HTML content with modified headers. Custom Response Headers:
HeaderDescription
X-Proxied-Page-TitleURL-encoded page title (if available)
X-Wayback-Cache"HIT" if content was served from Wayback cache

ai Mode

Retrieves AI-generated historical page versions from cache.

Additional Parameters:
ParameterTypeRequiredDescription
yearstringYesHistorical year (e.g., "1995", "500 BC", "1 CE", "current")
Response (200) - Cache Hit:

Returns the cached HTML content with header X-AI-Cache: HIT.

Response (404) - Cache Miss:
{
  "aiCache": false
}

list-cache Mode

Lists all available cached years (both AI-generated and Wayback Machine) for a URL.

Response (200):
{
  "years": ["current", "2020", "2015", "2010", "1999", "500 BC"]
}

Auto-Proxy Domains

The following domains are automatically proxied regardless of mode:

  • wikipedia.org (and subdomains)
  • wikimedia.org (and subdomains)
  • wikipedia.com
  • cursor.com

Rate Limits

ModeGlobal LimitPer-Host Limit
check / proxy300/min per IP100/min per IP per host
ai / list-cache120/min per IPN/A

Example

// Check if URL can be embedded
const checkResponse = await fetch('/api/iframe-check?url=https://example.com&mode=check');
const { allowed, reason } = await checkResponse.json();

if (!allowed) {
  // Use proxy mode instead
  const iframeSrc = `/api/iframe-check?url=https://example.com&mode=proxy`;
  iframe.src = iframeSrc;
}

// List cached historical versions
const cacheResponse = await fetch('/api/iframe-check?url=https://example.com&mode=list-cache');
const { years } = await cacheResponse.json();
// years: ["2020", "2015", "2010"]

// Load Wayback Machine version
const waybackSrc = `/api/iframe-check?url=https://example.com&mode=proxy&year=2015&month=06`;

Share Applet

Manages sharing of user-created applets (mini HTML/JS applications). Supports creating, retrieving, updating, and deleting shared applets.

Endpoint

MethodPathDescription
GET/api/share-appletRetrieve applet by ID or list all applets
POST/api/share-appletSave or update an applet
DELETE/api/share-appletDelete an applet (admin only)
PATCH/api/share-appletUpdate applet metadata (admin only)

Authentication

Most operations require authentication via headers:

HeaderDescription
AuthorizationBearer token: Bearer <token>
X-UsernameUsername of the authenticated user

GET - Retrieve Applet

Query Parameters:
ParameterTypeRequiredDescription
idstringNoApplet ID to retrieve
liststringNoSet to "true" to list all applets

*One of id or list=true is required.

Response - Single Applet (200):
{
  "content": "<html>...</html>",
  "title": "My Applet",
  "name": "my-applet",
  "icon": "game",
  "windowWidth": 400,
  "windowHeight": 300,
  "createdAt": 1704067200000,
  "createdBy": "username",
  "featured": false
}
Response - List Applets (200):
{
  "applets": [
    {
      "id": "abc123...",
      "title": "Featured Applet",
      "name": "featured-applet",
      "icon": "star",
      "createdAt": 1704067200000,
      "featured": true,
      "createdBy": "ryo"
    },
    {
      "id": "def456...",
      "title": "My Applet",
      "name": "my-applet",
      "icon": "game",
      "createdAt": 1704000000000,
      "featured": false,
      "createdBy": "user123"
    }
  ]
}

POST - Save Applet

Requires authentication.

Request Body:
FieldTypeRequiredDescription
contentstringYesHTML content of the applet
titlestringNoDisplay title
namestringNoApplet name/slug
iconstringNoEmoji or icon
windowWidthnumberNoDefault window width
windowHeightnumberNoDefault window height
shareIdstringNoExisting ID to update (author must match)
Response (200):
{
  "id": "abc123def456...",
  "shareUrl": "https://sya-os.vercel.app/applet-viewer/abc123def456...",
  "updated": false,
  "createdAt": 1704067200000
}
Response Fields:
FieldTypeDescription
idstringUnique applet ID (32 characters)
shareUrlstringFull URL to view the applet
updatedbooleanWhether an existing applet was updated
createdAtnumberTimestamp of creation/update

DELETE - Delete Applet (Admin Only)

Requires admin authentication (user "ryo" with valid token).

Query Parameters:
ParameterTypeRequiredDescription
idstringYesApplet ID to delete
Response (200):
{
  "success": true
}

PATCH - Update Applet (Admin Only)

Currently only supports updating the featured status.

Query Parameters:
ParameterTypeRequiredDescription
idstringYesApplet ID to update
Request Body:
{
  "featured": true
}
Response (200):
{
  "success": true,
  "featured": true
}

Example

// Save a new applet
const response = await fetch('/api/share-applet', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`,
    'X-Username': username
  },
  body: JSON.stringify({
    content: '<html><body><h1>Hello World</h1></body></html>',
    title: 'Hello World Applet',
    icon: '👋',
    windowWidth: 400,
    windowHeight: 300
  })
});

const { id, shareUrl } = await response.json();
console.log(`Applet shared at: ${shareUrl}`);

// Retrieve an applet
const applet = await fetch(`/api/share-applet?id=${id}`).then(r => r.json());

// List all applets
const { applets } = await fetch('/api/share-applet?list=true').then(r => r.json());

Admin API

Administrative endpoints for user management in the chat system. All operations require admin authentication (user "ryo" with valid token).

Endpoint

MethodPathDescription
GET/api/adminQuery users and statistics
POST/api/adminPerform admin actions

Authentication

All admin endpoints require:

HeaderDescription
AuthorizationBearer token: Bearer <token>
X-UsernameMust be "ryo"

Unauthorized requests return 403 Forbidden.

GET Operations

Query Parameters:
ParameterTypeRequiredDescription
actionstringYesOperation to perform
usernamestringFor some actionsTarget username
limitnumberNoResult limit (default: 50)

getStats Action

Get system-wide statistics.

Response:
{
  "totalUsers": 150,
  "totalRooms": 12,
  "totalMessages": 5430
}

getAllUsers Action

List all registered users.

Response:
{
  "users": [
    {
      "username": "alice",
      "lastActive": 1704067200000,
      "banned": false
    },
    {
      "username": "bob",
      "lastActive": 1704000000000,
      "banned": true
    }
  ]
}

getUserProfile Action

Get detailed profile for a specific user.

Additional Parameters:
ParameterTypeRequiredDescription
usernamestringYesTarget username
Response:
{
  "username": "alice",
  "lastActive": 1704067200000,
  "banned": false,
  "banReason": null,
  "bannedAt": null,
  "messageCount": 42,
  "rooms": [
    { "id": "general", "name": "General Chat" },
    { "id": "random", "name": "Random" }
  ]
}

getUserMessages Action

Get recent messages from a specific user.

Additional Parameters:
ParameterTypeRequiredDescription
usernamestringYesTarget username
limitnumberNoMaximum messages to return (default: 50)
Response:
{
  "messages": [
    {
      "id": "msg123",
      "roomId": "general",
      "roomName": "General Chat",
      "content": "Hello everyone!",
      "timestamp": 1704067200000
    }
  ]
}

POST Operations

Request Body:
FieldTypeRequiredDescription
actionstringYesOperation to perform
targetUsernamestringYesUser to act on
reasonstringNoReason for action (for bans)

deleteUser Action

Permanently delete a user account (cannot delete admin "ryo").

Request:
{
  "action": "deleteUser",
  "targetUsername": "spammer"
}
Response:
{
  "success": true
}
Effects:
  • Deletes user record
  • Deletes password hash
  • Invalidates all authentication tokens

banUser Action

Ban a user from the chat system (cannot ban admin "ryo").

Request:
{
  "action": "banUser",
  "targetUsername": "troublemaker",
  "reason": "Spamming in chat"
}
Response:
{
  "success": true
}
Effects:
  • Sets banned: true on user record
  • Stores ban reason and timestamp
  • Invalidates all authentication tokens (forces logout)

unbanUser Action

Remove ban from a user.

Request:
{
  "action": "unbanUser",
  "targetUsername": "reformed-user"
}
Response:
{
  "success": true
}

Example

const adminHeaders = {
  'Authorization': `Bearer ${adminToken}`,
  'X-Username': 'ryo'
};

// Get system statistics
const stats = await fetch('/api/admin?action=getStats', {
  headers: adminHeaders
}).then(r => r.json());

// Get all users
const { users } = await fetch('/api/admin?action=getAllUsers', {
  headers: adminHeaders
}).then(r => r.json());

// Get user profile
const profile = await fetch('/api/admin?action=getUserProfile&username=alice', {
  headers: adminHeaders
}).then(r => r.json());

// Ban a user
await fetch('/api/admin', {
  method: 'POST',
  headers: {
    ...adminHeaders,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    action: 'banUser',
    targetUsername: 'spammer',
    reason: 'Repeated spam violations'
  })
});

Error Handling

All utility APIs return consistent error responses:

{
  "error": "Error message description"
}
Common HTTP Status Codes:
StatusDescription
400Bad request (missing/invalid parameters)
401Unauthorized (authentication required)
403Forbidden (insufficient permissions or blocked origin)
404Not found
405Method not allowed
408Request timeout
429Rate limit exceeded
500Internal server error
503Service unavailable

CORS

All utility APIs implement CORS with origin validation. Allowed origins are configured server-side. Requests from unauthorized origins receive 403 Forbidden.

Related