{
  "auth": {
    "credentials": "Each third-party product (\"integration\") is registered in the btelo admin console and gets ONE pair of credentials: (integration_id, secret). The same pair is used for BOTH server-to-server API calls (HMAC below) AND OAuth login (client_id=integration_id, client_secret=secret on the /api/oauth/token endpoint). The secret can be viewed or rotated by the admin at any time. A disabled integration is rejected by both flows.",
    "hmac": {
      "clock_skew_limit": "±300 seconds",
      "description": "Server-to-server API endpoints (including /admin/integrations/* for admin integrations) require 3 headers. The secret is never transmitted — only a time-limited HMAC signature.",
      "examples": {
        "curl": "KEY_ID=vibe-remote; SECRET=bti_your_secret; TS=$(date +%s)\nSIGN=$(python3 -c \"import hmac,hashlib,os; print(hmac.new(os.environ['SECRET'].encode(), f\\\"$KEY_ID:$TS\\\".encode(), hashlib.sha256).hexdigest()[:16])\")\ncurl -H \"Btelo-Key-Id: $KEY_ID\" -H \"Btelo-Ts: $TS\" -H \"Btelo-Sign: $SIGN\" https://btelo.com/api/v1/tiers",
        "go": "keyID := \"vibe-remote\"\nsecret := \"bti_your_secret\"\nts := fmt.Sprintf(\"%d\", time.Now().Unix())\nmac := hmac.New(sha256.New, []byte(secret))\nmac.Write([]byte(keyID + \":\" + ts))\nsign := hex.EncodeToString(mac.Sum(nil))[:16]\nreq.Header.Set(\"Btelo-Key-Id\", keyID)\nreq.Header.Set(\"Btelo-Ts\", ts)\nreq.Header.Set(\"Btelo-Sign\", sign)",
        "node": "const crypto = require('crypto');\nconst keyId = 'vibe-remote', secret = 'bti_your_secret';\nconst ts = Math.floor(Date.now()/1000).toString();\nconst sign = crypto.createHmac('sha256', secret).update(`${keyId}:${ts}`).digest('hex').slice(0,16);\nheaders['Btelo-Key-Id']=keyId; headers['Btelo-Ts']=ts; headers['Btelo-Sign']=sign;",
        "python": "import hmac, hashlib, time\nkey_id, secret = 'vibe-remote', 'bti_your_secret'\nts = str(int(time.time()))\nsign = hmac.new(secret.encode(), f'{key_id}:{ts}'.encode(), hashlib.sha256).hexdigest()[:16]\nheaders = {'Btelo-Key-Id': key_id, 'Btelo-Ts': ts, 'Btelo-Sign': sign}"
      },
      "headers": {
        "Btelo-Key-Id": "The integration_id (public identifier).",
        "Btelo-Sign": "First 16 hex chars of HMAC-SHA256(\"\u003cintegration_id\u003e:\u003ctimestamp\u003e\", \u003csecret\u003e)",
        "Btelo-Ts": "Current Unix timestamp (seconds). Must be within ±5 minutes of server time."
      },
      "scheme": "HMAC-SHA256",
      "sign_formula": "sign = hex(HMAC_SHA256(secret, integration_id + \":\" + unix_ts))[:16]"
    },
    "oauth2": {
      "access_token_format": "RS256 JWT (validate with JWKS above). TTL ~1 hour.",
      "authorize_url": "https://btelo.com/api/oauth/authorize",
      "jwks_url": "https://btelo.com/api/oauth/jwks.json",
      "redirect_uri_policy": "Must be in the integration's registered redirect_uris allow-list, OR any https://*.btelo.com URI (auto-allowed).",
      "supported_grants": "authorization_code",
      "token_url": "https://btelo.com/api/oauth/token",
      "userinfo_url": "https://btelo.com/api/oauth/userinfo"
    }
  },
  "base_url": "https://btelo.com",
  "brand": {
    "logo_black": "https://btelo.com/public/logo-spin.png",
    "logo_svg": "https://btelo.com/public/logo-spin.svg",
    "logo_white": "https://btelo.com/public/logo-spin-white.png"
  },
  "conventions": {
    "content_type": "application/json; charset=utf-8 for JSON endpoints.",
    "ids": "User IDs are 32-char hex. Integration IDs are human slugs (e.g. \"vibe-remote\").",
    "pagination": "Not used — list endpoints return full arrays (bounded collections).",
    "timestamps": "RFC3339 for body/response fields. Unix seconds for HMAC headers."
  },
  "description": "btelo.com HTTP API. This document is the complete, authoritative contract — any third party (human or AI) should be able to integrate using ONLY this JSON, without additional communication.",
  "endpoints": [
    {
      "description": "Returns the N most recently published blog posts.",
      "example": "GET https://btelo.com/api/blog/posts?n=5",
      "group": "Blog",
      "method": "GET",
      "parameters": [
        {
          "default": 20,
          "description": "Number of posts to return (1–20).",
          "in": "query",
          "maximum": 20,
          "minimum": 1,
          "name": "n",
          "required": false,
          "type": "integer"
        }
      ],
      "path": "/api/blog/posts",
      "response": {
        "content_type": "application/json",
        "schema": {
          "count": "integer — number of posts returned",
          "post_fields": {
            "author_name": "Author display name (omitted when none)",
            "cover_image_url": "Absolute cover image URL (omitted when none)",
            "published_at": "Publication date (YYYY-MM-DD)",
            "slug": "URL slug of the post",
            "summary": "Short summary / excerpt",
            "title": "Post title",
            "url": "Canonical post URL",
            "view_count": "Total view count"
          },
          "posts": "array of post objects"
        }
      }
    },
    {
      "description": "Start an OAuth 2.0 authorization code flow. Redirects the user to the btelo.com login page, then back to your redirect_uri with ?code=... on success.",
      "example": "GET https://btelo.com/api/oauth/authorize?client_id=CLIENT_ID\u0026redirect_uri=https://yourapp.com/callback\u0026response_type=code\u0026state=xyz\u0026scope=openid+profile+email",
      "group": "OAuth2 SSO",
      "method": "GET",
      "parameters": [
        {
          "description": "Your integration_id (same value used for HMAC Btelo-Key-Id).",
          "in": "query",
          "name": "client_id",
          "required": true,
          "type": "string"
        },
        {
          "description": "URI to redirect to after authorization. Must match a registered URI for the client.",
          "in": "query",
          "name": "redirect_uri",
          "required": true,
          "type": "string"
        },
        {
          "description": "Must be \"code\".",
          "in": "query",
          "name": "response_type",
          "required": true,
          "type": "string"
        },
        {
          "description": "Opaque value echoed back in the redirect to prevent CSRF.",
          "in": "query",
          "name": "state",
          "required": false,
          "type": "string"
        },
        {
          "description": "Space-separated scopes (e.g. \"openid profile email\").",
          "in": "query",
          "name": "scope",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/api/oauth/authorize",
      "response": {
        "error": "302 redirect to redirect_uri?error=...",
        "success": "302 redirect to redirect_uri?code=AUTH_CODE\u0026state=..."
      }
    },
    {
      "description": "Exchange an authorization code for a JWT access token, or refresh an existing token.",
      "example": "POST https://btelo.com/api/oauth/token  body: grant_type=authorization_code\u0026code=CODE\u0026redirect_uri=...\u0026client_id=...\u0026client_secret=...",
      "group": "OAuth2 SSO",
      "method": "POST",
      "parameters": [
        {
          "description": "\"authorization_code\" or \"refresh_token\".",
          "in": "body (form)",
          "name": "grant_type",
          "required": true,
          "type": "string"
        },
        {
          "description": "The authorization code from /api/oauth/authorize (required when grant_type=authorization_code).",
          "in": "body (form)",
          "name": "code",
          "required": false,
          "type": "string"
        },
        {
          "description": "Must match the redirect_uri used in /api/oauth/authorize.",
          "in": "body (form)",
          "name": "redirect_uri",
          "required": false,
          "type": "string"
        },
        {
          "description": "Required when grant_type=refresh_token.",
          "in": "body (form)",
          "name": "refresh_token",
          "required": false,
          "type": "string"
        },
        {
          "description": "Your integration_id.",
          "in": "body (form)",
          "name": "client_id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Your integration secret (same value used for HMAC signing). TLS-protected in transit.",
          "in": "body (form)",
          "name": "client_secret",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/api/oauth/token",
      "response": {
        "content_type": "application/json",
        "schema": {
          "access_token": "Signed JWT (RS256) — use as Bearer token",
          "expires_in": "Seconds until the access token expires",
          "refresh_token": "Opaque token to obtain a new access token",
          "scope": "Granted scopes",
          "token_type": "\"Bearer\""
        }
      }
    },
    {
      "description": "Returns the authenticated user's profile. Requires a valid Bearer access token obtained from /api/oauth/token.",
      "example": "GET https://btelo.com/api/oauth/userinfo  Authorization: Bearer \u003caccess_token\u003e",
      "group": "OAuth2 SSO",
      "method": "GET",
      "parameters": [
        {
          "description": "Bearer \u003caccess_token\u003e",
          "in": "header",
          "name": "Authorization",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/api/oauth/userinfo",
      "response": {
        "content_type": "application/json",
        "schema": {
          "email": "User email address",
          "name": "Display name",
          "picture": "Avatar URL",
          "sub": "Unique user ID"
        }
      }
    },
    {
      "description": "Returns the RSA public key set (JWKS) used to verify JWT access tokens issued by /api/oauth/token.",
      "example": "GET https://btelo.com/api/oauth/jwks.json",
      "group": "OAuth2 SSO",
      "method": "GET",
      "parameters": [],
      "path": "/api/oauth/jwks.json",
      "response": {
        "content_type": "application/json",
        "schema": "RFC 7517 JSON Web Key Set"
      }
    },
    {
      "description": "Returns the currently logged-in user's profile (session cookie required). Returns 401 if not authenticated.",
      "example": "GET https://btelo.com/api/auth/me",
      "group": "Auth",
      "method": "GET",
      "parameters": [],
      "path": "/api/auth/me",
      "response": {
        "content_type": "application/json",
        "schema": {
          "avatar_url": "Avatar URL",
          "bio": "User bio / description",
          "email": "Email address",
          "id": "User ID",
          "name": "Display name",
          "provider": "Auth provider (google, email)"
        }
      }
    },
    {
      "description": "Returns the authenticated user's editable profile. Auth: session cookie (browser) or Btelo-Api-Key + user_id (server-to-server).",
      "example": "GET https://btelo.com/api/profile  (Cookie: btelo_session=... or Authorization: Bearer ...)",
      "group": "Profile",
      "method": "GET",
      "parameters": [],
      "path": "/api/profile",
      "response": {
        "content_type": "application/json",
        "schema": {
          "avatar_url": "Avatar URL",
          "bio": "User bio / description",
          "email": "Email address",
          "id": "User ID",
          "name": "Display name",
          "provider": "Auth provider (google, email)"
        }
      }
    },
    {
      "description": "Update the authenticated user's profile. Send only the fields you want to change. Auth: session cookie (browser) or Btelo-Api-Key + user_id (server-to-server).",
      "example": "PUT https://btelo.com/api/profile  body: {\"name\":\"New Name\",\"bio\":\"Hello world\"}",
      "group": "Profile",
      "method": "PUT",
      "parameters": [
        {
          "description": "Display name.",
          "in": "body (JSON)",
          "name": "name",
          "required": false,
          "type": "string"
        },
        {
          "description": "Avatar image URL.",
          "in": "body (JSON)",
          "name": "avatar_url",
          "required": false,
          "type": "string"
        },
        {
          "description": "Short bio / description.",
          "in": "body (JSON)",
          "name": "bio",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/api/profile",
      "response": {
        "content_type": "application/json",
        "schema": {
          "avatar_url": "Avatar URL (updated)",
          "bio": "Bio (updated)",
          "email": "Email address",
          "id": "User ID",
          "name": "Display name (updated)",
          "provider": "Auth provider"
        }
      }
    },
    {
      "description": "Send an email to a user immediately, or schedule it for a future time. Requires Btelo-Api-Key header.",
      "example": "POST https://btelo.com/api/email/send  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...  body: {\"user_id\":\"abc\",\"subject\":\"Hello\",\"html\":\"\u003ch1\u003eHi\u003c/h1\u003e\"}",
      "group": "Email",
      "method": "POST",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        },
        {
          "description": "Target user ID.",
          "in": "body (JSON)",
          "name": "user_id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Email subject line.",
          "in": "body (JSON)",
          "name": "subject",
          "required": true,
          "type": "string"
        },
        {
          "description": "Email HTML body.",
          "in": "body (JSON)",
          "name": "html",
          "required": true,
          "type": "string"
        },
        {
          "description": "RFC3339 timestamp for scheduled send (e.g. \"2026-04-20T10:00:00Z\"). Omit for immediate send.",
          "in": "body (JSON)",
          "name": "send_at",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/api/email/send",
      "response": {
        "content_type": "application/json",
        "schema": {
          "email": "Target email address",
          "id": "Scheduled email ID",
          "send_at": "Scheduled send time (RFC3339)",
          "status": "\"sending\" (immediate) or \"scheduled\" (future)",
          "subject": "Email subject",
          "user_id": "Target user ID"
        }
      }
    },
    {
      "description": "List scheduled emails. Optionally filter by user_id. Requires Btelo-Api-Key header.",
      "example": "GET https://btelo.com/api/email/scheduled?user_id=abc  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...",
      "group": "Email",
      "method": "GET",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        },
        {
          "description": "Filter by target user ID.",
          "in": "query",
          "name": "user_id",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/api/email/scheduled",
      "response": {
        "content_type": "application/json",
        "schema": {
          "emails": "Array of {id, user_id, subject, send_at, sent_at, canceled}"
        }
      }
    },
    {
      "description": "Cancel a pending scheduled email before it is sent. Requires Btelo-Api-Key header.",
      "example": "DELETE https://btelo.com/api/email/scheduled/abc123  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...",
      "group": "Email",
      "method": "DELETE",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        },
        {
          "description": "Scheduled email ID.",
          "in": "path",
          "name": "id",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/api/email/scheduled/{id}",
      "response": {
        "status": "204 No Content on success, 404 if not found or already sent"
      }
    },
    {
      "description": "Health check. Returns 200 OK with body \"ok\".",
      "example": "GET https://btelo.com/api/health",
      "group": "Utility",
      "method": "GET",
      "parameters": [],
      "path": "/api/health",
      "response": {
        "body": "ok",
        "content_type": "text/plain"
      }
    },
    {
      "description": "Returns this API documentation as JSON.",
      "example": "GET https://btelo.com/api/doc",
      "group": "Utility",
      "method": "GET",
      "parameters": [],
      "path": "/api/doc",
      "response": {
        "content_type": "application/json"
      }
    },
    {
      "description": "List all available subscription tiers with pricing. Auth: Btelo-Api-Key header.",
      "example": "GET https://btelo.com/api/v1/tiers  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...",
      "group": "Product API",
      "method": "GET",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/api/v1/tiers",
      "response": {
        "content_type": "application/json",
        "schema": "{ tiers: [{ slug, name, level, monthly_cents, yearly_cents, trial_days }] }"
      }
    },
    {
      "description": "Get a user's current subscription status. Optionally check access to a specific tier. Auth: Btelo-Api-Key + user_id.",
      "example": "GET https://btelo.com/api/v1/subscription?user_id=abc\u0026tier=pro  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...",
      "group": "Product API",
      "method": "GET",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        },
        {
          "description": "Btelo user ID.",
          "in": "query",
          "name": "user_id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Check if user has access to this tier level (returns has_access field).",
          "in": "query",
          "name": "tier",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/api/v1/subscription",
      "response": {
        "content_type": "application/json",
        "schema": "{ active, tier, status, plan, cancel_at_period_end, current_period_end, has_access? }"
      }
    },
    {
      "description": "Create a Stripe Checkout URL for a subscription. Redirect the user to this URL to start checkout. Auth: Btelo-Api-Key + user_id.",
      "example": "POST https://btelo.com/api/v1/subscription/checkout-url?user_id=abc  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...  body: {\"tier\":\"pro\",\"plan\":\"monthly\",\"success_url\":\"https://yourapp.com/ok\",\"cancel_url\":\"https://yourapp.com/cancel\"}",
      "group": "Product API",
      "method": "POST",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        },
        {
          "description": "Btelo user ID.",
          "in": "query",
          "name": "user_id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Tier slug (starter, pro, premium).",
          "in": "body (JSON)",
          "name": "tier",
          "required": true,
          "type": "string"
        },
        {
          "description": "monthly or yearly.",
          "in": "body (JSON)",
          "name": "plan",
          "required": true,
          "type": "string"
        },
        {
          "description": "URL to redirect after successful checkout.",
          "in": "body (JSON)",
          "name": "success_url",
          "required": true,
          "type": "string"
        },
        {
          "description": "URL to redirect if user cancels.",
          "in": "body (JSON)",
          "name": "cancel_url",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/api/v1/subscription/checkout-url",
      "response": {
        "content_type": "application/json",
        "schema": "{ url: \"https://checkout.stripe.com/...\" }"
      }
    },
    {
      "description": "Get a user's coin balance. Auth: Btelo-Api-Key + user_id.",
      "example": "GET https://btelo.com/api/v1/coins?user_id=abc  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...",
      "group": "Product API",
      "method": "GET",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        },
        {
          "description": "Btelo user ID.",
          "in": "query",
          "name": "user_id",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/api/v1/coins",
      "response": {
        "content_type": "application/json",
        "schema": "{ user_id, coins }"
      }
    },
    {
      "description": "Deduct coins from a user's balance. Returns 402 if insufficient coins. Auth: Btelo-Api-Key + user_id.",
      "example": "POST https://btelo.com/api/v1/coins/deduct?user_id=abc  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...  body: {\"amount\":100,\"reason\":\"image generation\"}",
      "group": "Product API",
      "method": "POST",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        },
        {
          "description": "Btelo user ID.",
          "in": "query",
          "name": "user_id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Number of coins to deduct.",
          "in": "body (JSON)",
          "name": "amount",
          "required": true,
          "type": "integer"
        },
        {
          "description": "Reason for deduction (logged).",
          "in": "body (JSON)",
          "name": "reason",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/api/v1/coins/deduct",
      "response": {
        "content_type": "application/json",
        "schema": "{ user_id, deducted, remaining }"
      }
    },
    {
      "description": "Create a Stripe Checkout URL for a one-time coin purchase. Auth: Btelo-Api-Key + user_id.",
      "example": "POST https://btelo.com/api/v1/coins/checkout-url?user_id=abc  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...  body: {\"slug\":\"coins-500\",\"success_url\":\"https://yourapp.com/ok\",\"cancel_url\":\"https://yourapp.com/cancel\"}",
      "group": "Product API",
      "method": "POST",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        },
        {
          "description": "Btelo user ID.",
          "in": "query",
          "name": "user_id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Coin product slug (coins-500, coins-1000, coins-5000).",
          "in": "body (JSON)",
          "name": "slug",
          "required": true,
          "type": "string"
        },
        {
          "description": "URL to redirect after successful checkout.",
          "in": "body (JSON)",
          "name": "success_url",
          "required": true,
          "type": "string"
        },
        {
          "description": "URL to redirect if user cancels.",
          "in": "body (JSON)",
          "name": "cancel_url",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/api/v1/coins/checkout-url",
      "response": {
        "content_type": "application/json",
        "schema": "{ url: \"https://checkout.stripe.com/...\" }"
      }
    },
    {
      "description": "Returns the AI proxy API documentation as JSON. Lists all AI endpoints, auth requirements, and request/response formats.",
      "example": "GET https://btelo.com/api/ai/readme",
      "group": "AI Proxy",
      "method": "GET",
      "parameters": [],
      "path": "/api/ai/readme",
      "response": {
        "content_type": "application/json"
      }
    },
    {
      "description": "Anthropic-compatible chat completions via MiniMax. Requires AI API key.",
      "example": "POST https://btelo.com/v1/messages",
      "group": "AI Proxy",
      "method": "POST",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/v1/messages"
    },
    {
      "description": "OpenAI image generation with auto-resize. Requires AI API key.",
      "example": "POST https://btelo.com/v1/images/generations",
      "group": "AI Proxy",
      "method": "POST",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/v1/images/generations"
    },
    {
      "description": "OpenAI text-to-speech proxy. Requires AI API key.",
      "example": "POST https://btelo.com/v1/audio/speech",
      "group": "AI Proxy",
      "method": "POST",
      "parameters": [
        {
          "description": "API key ID.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min).",
          "in": "header",
          "name": "Btelo-Ts",
          "required": true,
          "type": "string"
        },
        {
          "description": "First 16 hex chars of HMAC-SHA256(key_id:timestamp, secret).",
          "in": "header",
          "name": "Btelo-Sign",
          "required": true,
          "type": "string"
        }
      ],
      "path": "/v1/audio/speech"
    },
    {
      "description": "Register a new integration. Auth: admin session cookie OR HMAC from an integration with is_admin=true. Returns the created integration including the generated secret.",
      "example": "POST https://btelo.com/admin/integrations  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...  body: {\"id\":\"auto-social\",\"name\":\"Auto Social\",\"redirect_uris\":[\"https://auto-social.com/auth/cb\"]}",
      "group": "Admin API",
      "method": "POST",
      "parameters": [
        {
          "description": "Admin integration ID (HMAC auth path). Omit when using session cookie.",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": false,
          "type": "string"
        },
        {
          "description": "Unix timestamp (±5 min). Required with HMAC auth.",
          "in": "header",
          "name": "Btelo-Ts",
          "required": false,
          "type": "string"
        },
        {
          "description": "HMAC signature. Required with HMAC auth.",
          "in": "header",
          "name": "Btelo-Sign",
          "required": false,
          "type": "string"
        },
        {
          "description": "URL-safe slug chosen by caller (e.g. \"auto-social\"). Must be unique.",
          "in": "body (JSON)",
          "name": "id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Human-readable product name.",
          "in": "body (JSON)",
          "name": "name",
          "required": true,
          "type": "string"
        },
        {
          "description": "Allowed OAuth redirect URIs. Omit/empty if the integration only uses HMAC API. URIs under *.btelo.com are auto-allowed.",
          "in": "body (JSON)",
          "name": "redirect_uris",
          "required": false,
          "type": "array\u003cstring\u003e"
        },
        {
          "description": "If true, the new integration can call /admin/* endpoints. Default false.",
          "in": "body (JSON)",
          "name": "is_admin",
          "required": false,
          "type": "boolean"
        }
      ],
      "path": "/admin/integrations",
      "response": {
        "content_type": "application/json",
        "errors": "400 invalid body · 409 id already exists",
        "schema": {
          "created_at": "YYYY-MM-DD HH:MM:SS",
          "disabled": "boolean",
          "id": "integration_id",
          "is_admin": "boolean",
          "name": "display name",
          "redirect_uris": "array of registered redirect URIs",
          "secret": "plaintext secret (bti_\u003chex\u003e). Store securely. Can be viewed again via GET."
        },
        "status": "201 Created"
      }
    },
    {
      "description": "List all registered integrations. Secrets are included (plaintext) because they must be retrievable for re-display.",
      "example": "GET https://btelo.com/admin/integrations  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...",
      "group": "Admin API",
      "method": "GET",
      "parameters": [
        {
          "description": "Admin integration ID (HMAC auth path).",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": false,
          "type": "string"
        },
        {
          "description": "Unix timestamp.",
          "in": "header",
          "name": "Btelo-Ts",
          "required": false,
          "type": "string"
        },
        {
          "description": "HMAC signature.",
          "in": "header",
          "name": "Btelo-Sign",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/admin/integrations",
      "response": {
        "content_type": "application/json",
        "schema": "array of integration objects (same shape as POST /admin/integrations response)",
        "status": "200 OK"
      }
    },
    {
      "description": "Delete an integration permanently. Existing OAuth tokens already issued keep working until expiry; new logins and HMAC calls are rejected immediately.",
      "example": "DELETE https://btelo.com/admin/integrations/vibe-remote  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...",
      "group": "Admin API",
      "method": "DELETE",
      "parameters": [
        {
          "description": "integration_id to delete.",
          "in": "path",
          "name": "id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Admin integration ID (HMAC auth path).",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": false,
          "type": "string"
        },
        {
          "description": "Unix timestamp.",
          "in": "header",
          "name": "Btelo-Ts",
          "required": false,
          "type": "string"
        },
        {
          "description": "HMAC signature.",
          "in": "header",
          "name": "Btelo-Sign",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/admin/integrations/{id}",
      "response": {
        "status": "204 No Content"
      }
    },
    {
      "description": "Rotate the secret of an existing integration. The previous secret is invalidated immediately. Returns the new plaintext secret.",
      "example": "POST https://btelo.com/admin/integrations/vibe-remote/rotate  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...",
      "group": "Admin API",
      "method": "POST",
      "parameters": [
        {
          "description": "integration_id.",
          "in": "path",
          "name": "id",
          "required": true,
          "type": "string"
        },
        {
          "description": "Admin integration ID (HMAC auth path).",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": false,
          "type": "string"
        },
        {
          "description": "Unix timestamp.",
          "in": "header",
          "name": "Btelo-Ts",
          "required": false,
          "type": "string"
        },
        {
          "description": "HMAC signature.",
          "in": "header",
          "name": "Btelo-Sign",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/admin/integrations/{id}/rotate",
      "response": {
        "content_type": "application/json",
        "schema": {
          "id": "integration_id",
          "secret": "new plaintext secret (bti_\u003chex\u003e)"
        },
        "status": "200 OK"
      }
    },
    {
      "description": "Enable or disable an integration without deleting it. Disabled integrations cannot authenticate (OAuth and HMAC both return 401).",
      "example": "POST https://btelo.com/admin/integrations/vibe-remote/disable  Btelo-Key-Id: ... Btelo-Ts: ... Btelo-Sign: ...  body: {\"disabled\":true}",
      "group": "Admin API",
      "method": "POST",
      "parameters": [
        {
          "description": "integration_id.",
          "in": "path",
          "name": "id",
          "required": true,
          "type": "string"
        },
        {
          "description": "true to disable, false to re-enable.",
          "in": "body (JSON)",
          "name": "disabled",
          "required": true,
          "type": "boolean"
        },
        {
          "description": "Admin integration ID (HMAC auth path).",
          "in": "header",
          "name": "Btelo-Key-Id",
          "required": false,
          "type": "string"
        },
        {
          "description": "Unix timestamp.",
          "in": "header",
          "name": "Btelo-Ts",
          "required": false,
          "type": "string"
        },
        {
          "description": "HMAC signature.",
          "in": "header",
          "name": "Btelo-Sign",
          "required": false,
          "type": "string"
        }
      ],
      "path": "/admin/integrations/{id}/disable",
      "response": {
        "status": "204 No Content"
      }
    }
  ],
  "error_responses": {
    "401": "Missing / invalid auth — HMAC signature mismatch, stale timestamp, unknown key_id, or disabled integration.",
    "403": "Authenticated but not authorized (e.g. calling /admin/* without is_admin).",
    "404": "Resource not found (user, integration, subscription, etc.).",
    "409": "Conflict (e.g. creating an integration whose id already exists).",
    "429": "Rate limit (currently not enforced).",
    "5xx": "Server error. Retry with exponential backoff.",
    "format": "Errors are JSON {\"error\":\"\u003cmessage\u003e\"} for JSON endpoints. Some older endpoints return plain text."
  },
  "quickstart": [
    "1. Ask a btelo admin to create an Integration for you. You will receive (integration_id, secret).",
    "2. Store the secret server-side. Never ship it to browsers. The secret itself is never sent over the wire — each request carries only a short-lived HMAC signature derived from it.",
    "3. For API calls (server-to-server): sign each request with the HMAC scheme documented in `auth.hmac` (secret stays local; only `Btelo-Sign` is transmitted, valid for ±5 min).",
    "4. For end-user login (OAuth2 SSO): send users to /api/oauth/authorize, exchange the code at /api/oauth/token using your integration_id + secret as client_id + client_secret.",
    "5. If you are an automation (e.g. AI agent) that needs to REGISTER OTHER INTEGRATIONS programmatically, ask the admin to set is_admin=true on your integration. You can then call /admin/integrations/* via the same HMAC auth."
  ],
  "version": "1"
}
