Publish API

Deploy apps to gapp.so directly from the command line or AI coding tools. No web UI required.

Quick Start

Publish a simple HTML app in one curl command:

curl -X POST https://gapp.so/api/apps/publish \
  -H "Authorization: Bearer <your-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "my-app",
    "title": "My App",
    "tagline": "A simple demo app",
    "framework": "html",
    "files": {
      "index.html": "<base64-encoded content>"
    }
  }'

Authentication

Create a Personal Access Token from Dashboard → Creator Tools. Include it as a Bearer token in the Authorization header.

Authorization: Bearer gapp_a1b2c3d4e5f6...

Tokens have the format gapp_ + 40 hex characters. Keep them secret — they grant full publish access to your account.

Endpoint

POST /api/apps/publish

Creates a new app or updates an existing one (upsert by slug). If an app with the given slug exists and you own it, it will be updated with a new version. Otherwise, a new app is created.

Required Fields

slugstring

Unique URL identifier for your app. 3–50 characters, lowercase letters, numbers, and hyphens only. If an app with this slug exists and you own it, it will be updated.

framework"html" | "react" | "vue"

The framework your app uses. Use "react" for React/Vite/Next.js projects, "vue" for Vue projects, or "html" for plain HTML/JS.

filesRecord<string, string>

Your source files as a JSON object. Keys are file paths (e.g., "index.html", "src/App.tsx"), values are base64-encoded file contents.

Optional Fields

titlestring

App title (3–200 chars). Required when creating a new app, optional for updates.

taglinestring

Short tagline (1–300 chars). Required when creating a new app, optional for updates.

descriptionstring

Longer description (max 2000 chars).

tagsstring[]

Up to 5 tags (2–30 chars each).

primaryCategorystring

One of: "simulation", "strategy", "quiz", "visual-novel", "casual", "creative", "tool".

languagesstring[]

Languages your app supports: "zh", "en". Auto-detected if omitted.

sourceType"paste" | "zip"

Defaults to "paste".

File Encoding

All file contents must be base64-encoded. The server decodes text files to UTF-8 and keeps binary files (images, fonts) as-is for storage.

# Encode a file to base64
cat index.html | base64

# In Node.js
const fs = require('fs')
const b64 = fs.readFileSync('index.html').toString('base64')

# In Python
import base64
b64 = base64.b64encode(open('index.html', 'rb').read()).decode()

Allowed extensions: .html, .css, .js, .jsx, .ts, .tsx, .vue, .json, .svg, .md, .txt

Limits: Max 100 files, 10 MB total (decoded). Dependencies (node_modules, etc.) are filtered automatically.

React / Vite Example

Upload your source files — gapp.so builds React projects server-side. No need to run npm run build locally.

# Bash script to publish a React project
TOKEN="gapp_your_token_here"
SLUG="my-react-app"

# Collect source files (exclude node_modules, dist, etc.)
FILES="{}"
for f in $(find . -type f \
  -not -path '*/node_modules/*' \
  -not -path '*/dist/*' \
  -not -path '*/.git/*' \
  -not -name 'package-lock.json' \
  -not -name '.DS_Store'); do
  # Strip leading ./
  name="${f#./}"
  b64=$(base64 < "$f" | tr -d '\n')
  FILES=$(echo "$FILES" | jq --arg k "$name" --arg v "$b64" '. + {($k): $v}')
done

curl -X POST https://gapp.so/api/apps/publish \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n \
    --arg slug "$SLUG" \
    --arg title "My React App" \
    --arg tagline "Built with React + Vite" \
    --argjson files "$FILES" \
    '{slug: $slug, title: $title, tagline: $tagline,
      framework: "react", files: $files}'
  )"

Response

On success, the API returns the app details and URLs:

{
  "success": true,
  "action": "created",   // or "updated"
  "app": {
    "id": "clx...",
    "slug": "my-app",
    "title": "My App",
    "version": 1,
    "landing_url": "https://gapp.so/p/my-app",
    "run_url": "https://gapp.so/my-app",
    "deployment_type": "static"
  }
}

Error Codes

CodeStatusMeaning
UNAUTHORIZED401Missing or invalid Bearer token.
VALIDATION_ERROR400Request body failed validation. Check the field and message in the response.
RESERVED_SLUG400The slug is reserved by the platform.
FORBIDDEN403You don't own the app with this slug.
FILE_TOO_LARGE400Total file size exceeds 10 MB.
NO_SOURCE_FILES400No valid source files found after filtering.
UPLOAD_FAILED500File upload to storage failed. Retry the request.

Creating vs Updating

Creating a new app

If no app with the given slug exists, title and tagline are required. The response will have action: "created".

Updating an existing app

If an app with the slug exists and you own it, a new version is published. Only slug, framework, and files are required — other fields update only if provided. The response will have action: "updated".

AI Tool Integration

For the best experience with Claude Code, Cursor, or other AI tools:

  1. Go to Dashboard → Creator Tools and generate a publish key
  2. Download the publish skill file with your key embedded
  3. Place it in your project's .claude/commands/ directory
  4. Say "publish this app" and your AI tool handles the rest