VISIMADE
← Developer Hub

Getting Started

  • Authentication
  • AI Coding Agents

Core APIs

  • Pages API
  • Page Assets API
  • Page API Features
  • Functions API

Data APIs

Guides

Reference

Functions API

Serverless functions that run on Cloudflare Workers. Call built-in utilities or deploy your own custom functions with optional SQLite databases.

Overview

Visimade Functions are serverless endpoints that run on Cloudflare Workers. Each function has a unique slug and is accessible at /api/functions/:slug. Functions can be called by anyone (no auth needed) or managed by their owner (auth required).

Architecture: When you call a function, the Visimade API looks up its Worker URL in the database, proxies your request to the Cloudflare Worker, and returns the response. Rate limiting and access control happen at the proxy layer. Functions can optionally include a SQLite database for persistent storage.

Built-in Functions
FunctionDescription
unfurl-urlFetch a URL and extract Open Graph metadata (title, description, image, favicon)

Calling Functions

Call any public function by sending a POST request with JSON parameters. No authentication required.

# Unfurl a URL to get its Open Graph metadata
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"url": "https://github.com"}' \
  https://visimade.com/api/functions/unfurl-url
Response
{
  "success": true,
  "data": {
    "url": "https://github.com",
    "title": "GitHub: Let's build from here",
    "description": "GitHub is where over 100 million developers shape the future of software.",
    "image": "https://github.githubassets.com/assets/hero-og-image.jpg",
    "siteName": "GitHub",
    "type": "website",
    "favicon": "https://github.com/favicon.ico"
  }
}

Client-Side SDK

All Visimade pages have window.PageFunctions available automatically. Use it to call functions from your page's JavaScript:

// Generic call — works with any function
const result = await PageFunctions.call('unfurl-url', { url: 'https://github.com' });

// Convenience wrapper for unfurl-url
const result = await PageFunctions.unfurl('https://github.com');

if (result.success) {
  console.log(result.data.title);       // "GitHub: Let's build from here"
  console.log(result.data.image);       // "https://github.githubassets.com/..."
  console.log(result.data.description); // "GitHub is where over 100 million..."
  console.log(result.data.favicon);     // "https://github.com/favicon.ico"
}

Tip: Use unfurl-url to build link preview cards, social embeds, or bookmark-style displays in your pages.


Endpoints
GET

/api/functions

List all public functions

GET

/api/functions/:slug

Get function metadata

POST

/api/functions/:slug

Call/invoke a function (no auth)

POST

/api/functions

Create a new function (auth, functions:write)

PATCH

/api/functions/:slug

Update function code or metadata (owner only)

DELETE

/api/functions/:slug

Delete a function (owner only)

POST

/api/functions/:slug/database

Execute SQL on function's SQLite database (owner only)


GET /api/functions

List all public functions. No authentication required.

curl https://visimade.com/api/functions
Response
{
  "functions": [
    {
      "slug": "unfurl-url",
      "displayName": "Unfurl URL",
      "description": "Fetch a URL and extract Open Graph metadata",
      "owner": "cosmic",
      "hasDatabase": false,
      "createdAt": "2026-02-10T06:49:00Z",
      "updatedAt": "2026-02-10T06:49:00Z"
    }
  ]
}

POST /api/functions/:slug

Call a function. The request body is forwarded to the Worker and the response is returned. No authentication required for public functions. Rate limited to 60 calls/minute per IP.

unfurl-url Parameters
FieldTypeDescription
urlstringThe URL to unfurl (required)
unfurl-url Response Fields
FieldTypeDescription
urlstringCanonical URL (from og:url or final redirect)
titlestring | nullPage title (og:title > twitter:title > <title>)
descriptionstring | nullPage description (og:description > meta description)
imagestring | nullPreview image URL (og:image > twitter:image)
siteNamestringSite name (og:site_name or hostname)
typestring | nullContent type (og:type, e.g., "website", "article")
faviconstring | nullFavicon URL

POST /api/functions

Create a new function. Your JavaScript code is deployed as a Cloudflare Worker. Optionally create a SQLite database for persistent storage, or share another function's database. Requires authentication with functions:write scope.

Request Body
FieldTypeDescription
slugstringURL identifier (lowercase, hyphens, max 100 chars). Must be unique.
codestringJavaScript source code (ES modules format, max 1MB)
displayNamestring?Human-readable name
descriptionstring?What the function does
enableDatabaseboolean?Set to true to create a new SQLite database bound as env.DB
useDatabasestring?Slug of another function whose SQLite database to share (must be your own function). Mutually exclusive with enableDatabase.
# Create a function without a database
curl -X POST \
  -H "Authorization: Bearer vm_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "hello-world",
    "displayName": "Hello World",
    "description": "Returns a greeting",
    "code": "export default { async fetch(request) { const { name } = await request.json(); return Response.json({ success: true, data: { message: \"Hello, \" + (name || \"World\") + \"!\" } }); } };"
  }' \
  https://visimade.com/api/functions

# Create a function WITH a new database
curl -X POST \
  -H "Authorization: Bearer vm_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "todo-api",
    "displayName": "Todo API",
    "description": "A simple todo list with persistent storage",
    "enableDatabase": true,
    "code": "export default { async fetch(request, env) { await env.DB.prepare(\"CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, done INTEGER DEFAULT 0)\").run(); if (request.method === \"POST\") { const { title } = await request.json(); const r = await env.DB.prepare(\"INSERT INTO todos (title) VALUES (?)\").bind(title).run(); return Response.json({ success: true, data: { id: r.meta.last_row_id } }); } const { results } = await env.DB.prepare(\"SELECT * FROM todos\").all(); return Response.json({ success: true, data: results }); } };"
  }' \
  https://visimade.com/api/functions

# Create a function that SHARES another function's database
curl -X POST \
  -H "Authorization: Bearer vm_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "todo-stats",
    "displayName": "Todo Stats",
    "description": "Read-only stats for the todo database",
    "useDatabase": "todo-api",
    "code": "export default { async fetch(request, env) { const { results } = await env.DB.prepare(\"SELECT COUNT(*) as total, SUM(done) as completed FROM todos\").all(); return Response.json({ success: true, data: results[0] }); } };"
  }' \
  https://visimade.com/api/functions

Shared databases: Use useDatabase to bind the same SQLite database to multiple functions. Both functions get full read/write access via env.DB. The database is only deleted when the last function using it is deleted. You can only share databases from functions you own.

Response (201)
{
  "slug": "todo-api",
  "displayName": "Todo API",
  "description": "A simple todo list with persistent storage",
  "workerUrl": "https://vm-fn-todo-api.visimade.workers.dev",
  "isPublic": true,
  "hasDatabase": true,
  "createdAt": "2026-02-10T12:00:00Z"
}

PATCH /api/functions/:slug

Update a function's code, metadata, or visibility. Only the function owner can update. If code is provided, the Worker is redeployed (database bindings are preserved automatically). Requires functions:write scope.

# Update just the description
curl -X PATCH \
  -H "Authorization: Bearer vm_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"description": "Returns a personalized greeting"}' \
  https://visimade.com/api/functions/hello-world

# Redeploy with new code
curl -X PATCH \
  -H "Authorization: Bearer vm_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"code": "export default { async fetch(request) { return Response.json({ success: true, data: { message: \"Hello v2!\" } }); } };"}' \
  https://visimade.com/api/functions/hello-world

DELETE /api/functions/:slug

Delete a function. Removes the Cloudflare Worker, its SQLite database (if any), and the database record. Only the function owner can delete. Requires functions:write scope. Returns 204.

curl -X DELETE \
  -H "Authorization: Bearer vm_your_token_here" \
  https://visimade.com/api/functions/hello-world

Database (SQLite)

Functions can optionally include a SQLite database for persistent storage. Use enableDatabase: true to create a new database, or useDatabase: "other-slug" to share an existing function's database. Both approaches bind the database to your Worker as env.DB.

How it works: The database is a full SQLite instance running on Cloudflare's edge network. Your Worker code accesses it via env.DB using the SQLite client API. You can also run queries externally via the POST /api/functions/:slug/database endpoint. Multiple functions can share the same database — the database is only deleted when the last function using it is removed.

SQLite Client API (inside your Worker)
MethodDescription
env.DB.prepare(sql)Create a prepared statement
.bind(p1, p2, ...)Bind parameters to the statement
.run()Execute a write query (INSERT, UPDATE, DELETE, CREATE TABLE)
.all()Execute a read query and return all rows
.first()Execute a read query and return the first row

Tip: Use CREATE TABLE IF NOT EXISTS at the start of your fetch handler to ensure tables exist on every request. This is idempotent and fast.


POST /api/functions/:slug/database

Execute SQL against a function's SQLite database. Only the function owner can use this endpoint. Useful for schema setup, migrations, data inspection, and seeding. Requires functions:write scope.

Request Body
FieldTypeDescription
sqlstringSQL statement to execute (max 100KB)
paramsarray?Bind parameters for the SQL statement
# Create a table
curl -X POST \
  -H "Authorization: Bearer vm_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{
    "sql": "CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_at TEXT DEFAULT (datetime('\''now'\'')))"
  }' \
  https://visimade.com/api/functions/todo-api/database

# Insert a row
curl -X POST \
  -H "Authorization: Bearer vm_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"sql": "INSERT INTO items (name) VALUES (?)", "params": ["Buy groceries"]}' \
  https://visimade.com/api/functions/todo-api/database

# Query rows
curl -X POST \
  -H "Authorization: Bearer vm_your_token_here" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT * FROM items"}' \
  https://visimade.com/api/functions/todo-api/database
Response
{
  "success": true,
  "results": [
    { "id": 1, "name": "Buy groceries", "created_at": "2026-02-14 12:00:00" }
  ],
  "meta": {
    "changes": 0,
    "duration": 0.5,
    "last_row_id": 0,
    "rows_read": 1,
    "rows_written": 0
  }
}

Writing Worker Code

Functions run as Cloudflare Workers using the ES modules format. Your code must export a default object with a fetch handler that accepts a Request and returns a Response. If your function has a database, the second argument (env) contains the SQLite binding.

Basic function (no database)
// Minimal function template
export default {
  async fetch(request) {
    // Only accept POST
    if (request.method !== 'POST') {
      return Response.json({ success: false, error: 'Method not allowed' }, { status: 405 });
    }

    // Parse input
    const params = await request.json();

    // Your logic here
    const result = { greeting: 'Hello, ' + (params.name || 'World') + '!' };

    // Return result
    return Response.json({ success: true, data: result });
  },
};
Function with SQLite database
// Function with SQLite database (created with enableDatabase: true)
export default {
  async fetch(request, env) {
    // Create table on first run (idempotent)
    await env.DB.prepare(`
      CREATE TABLE IF NOT EXISTS items (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        created_at TEXT DEFAULT (datetime('now'))
      )
    `).run();

    // POST — insert a new item
    if (request.method === 'POST') {
      const { name } = await request.json();
      const result = await env.DB.prepare(
        'INSERT INTO items (name) VALUES (?)'
      ).bind(name).run();
      return Response.json({
        success: true,
        data: { id: result.meta.last_row_id }
      });
    }

    // GET — list all items
    const { results } = await env.DB.prepare('SELECT * FROM items').all();
    return Response.json({ success: true, data: results });
  },
};

Rules: Workers must respond within 30 seconds. Code must be under 1MB. Use Response.json() to return JSON. Always return { success: true, data: {...} } or { success: false, error: "..." } for consistency.

Calling your function: Once created, anyone can call it with PageFunctions.call('your-slug', params) from any Visimade page, or via POST /api/functions/your-slug from any HTTP client.

On this page

  • Overview
  • Calling Functions
  • Client-Side SDK
  • GET - List Functions
  • GET - Function Info
  • POST - Call Function
  • POST - Create Function
  • PATCH - Update Function
  • DELETE - Delete Function
  • Database (SQLite)
  • POST - Query Database
  • Writing Worker Code