BETAFINDS PUBLISHER API ======================= Version 1. Documentation optimized for LLM agents and humans. MENTAL MODEL ------------ An author on BetaFinds has startups (each with a unique slug). For an approved (APPROVED) startup the author can: 1. Publish UPDATES — entries in the "What's new" feed: releases, improvements, fixes, news. Startup subscribers receive an email summary within ~10 minutes after publishing. 2. Create RELEASES — versions with files for Windows/macOS/Android/Linux. Files are uploaded directly to storage via a one-time link (presigned PUT), downloaded by visitors from https://downloads.betafinds.com/{slug}/{version}/{file}. Typical agent flow after building a new version of an app: create_release → request_asset_upload (for each file) → PUT the file → complete_asset_upload AUTHENTICATION -------------- An API token is created on the https://betafinds.com/profile page (the "API tokens" section). Format: blt_<48 hex chars>. Passed in the header: Authorization: Bearer blt_... All responses are JSON: {"ok": true, "result": ...} or {"ok": false, "error": "..."}. Base URL: https://betafinds.com TWO WAYS TO INTEGRATE --------------------- A) MCP (recommended for Claude and other LLM agents): Endpoint: https://betafinds.com/api/mcp (Streamable HTTP, stateless) Claude Code: claude mcp add betafinds https://betafinds.com/api/mcp --transport http \ --header "Authorization: Bearer blt_YOUR_TOKEN" Tools: create_startup, update_startup, upload_image, list_categories, list_my_startups, publish_update, create_release, request_asset_upload, complete_asset_upload, get_releases. B) REST API — methods below. REST METHODS ------------ GET /api/v1/me Verify the token. Returns the owner's id, name, email. GET /api/v1/startups List of your startups with their current store links. result: [{slug, name, status, tagline, url, appStoreUrl, testflightUrl, googlePlayUrl}] Publishing is only possible when status = "APPROVED". GET /api/v1/categories Public list of categories (no token). result: [{slug, name}]. Use slug in create_startup (categorySlug). A category name also works — matching is case-insensitive; if not found, the startup is created without a category (the response includes a warning). POST /api/v1/images Upload an image (logo/screenshot). Body = raw file bytes, the Content-Type header = the image type. Max 2 MB; JPEG/PNG/WebP/GIF. curl -X POST "https://betafinds.com/api/v1/images" -H "$AUTH" \ -H "Content-Type: image/png" --data-binary @icon.png result: {path, url} — use url in create_startup. POST /api/v1/startups Create a new startup (submitted for review). | Field | Type | Req. | Description | |--------------|----------|------|----------------------------------------| | name | string | yes | Name | | tagline | string | yes | Tagline, up to 160 characters | | description | string | yes | Full description, min 50 characters | | url | string | yes | Product website URL | | categorySlug | string | no | Category slug (see https://betafinds.com/categories) | | tags | string[] | no | Tags, up to 6 | | logo | string | no | logo url from POST /api/v1/images | | screenshots | string[] | no | screenshot urls (up to 5) from /images | | appStoreUrl, testflightUrl, googlePlayUrl | string | no | App store links (iOS/Android) | result: {slug, url, status}. Images are accepted only as urls returned by POST /api/v1/images (upload the files first). PATCH /api/v1/startups/{slug} Update your startup (e.g. add store links after the app ships). Send only the fields you want to change; an empty string clears a link. Moderation status is not changed. | Field | Type | Req. | Description | |--------------|----------|------|----------------------------------------| | url | string | no | Product website URL | | tagline | string | no | Tagline, up to 160 characters | | description | string | no | Full description, min 50 characters | | categorySlug | string | no | Category slug | | tags | string[] | no | Tags, up to 6 | | appStoreUrl, testflightUrl, googlePlayUrl | string | no | App store links (iOS/Android) | result: {slug, url, status, updated: [changed fields]}. Example — add App Store and RuStore links: curl -X PATCH "https://betafinds.com/api/v1/startups/my-app" -H "$AUTH" \ -H "Content-Type: application/json" \ -d '{"appStoreUrl":"https://apps.apple.com/app/id000"}' GET /api/v1/startups/{slug}/updates Public list of a startup's updates (no token required). POST /api/v1/startups/{slug}/updates Publish an update. | Parameter | Type | Req. | Description | |-----------|--------|------|--------------------------------------------| | title | string | yes | Title, 3–200 characters | | body | string | yes | Text in Markdown, 10–20000 characters | | type | string | no | RELEASE / UPDATE / FIX / NEWS (default) | | version | string | no | Version, e.g. "1.4.0" | GET /api/v1/startups/{slug}/releases Public list of releases with files and download links (no token required). POST /api/v1/startups/{slug}/releases Create a release. | Parameter | Type | Req. | Description | |-----------|---------|------|--------------------------------------------------| | version | string | yes | Version, unique within the startup | | notes | string | no | Release notes in Markdown | | announce | boolean | no | Whether to publish an update in the feed (default true)| result: {id, version, ...} — id is needed to upload files. POST /api/v1/releases/{release_id}/assets Request a file upload link. | Parameter | Type | Req. | Description | |-----------|--------|------|-------------------------------------------| | platform | string | yes | WINDOWS / MACOS / ANDROID / LINUX | | filename | string | yes | File name, e.g. "MyApp-1.4.0.dmg" | | size | number | yes | Exact file size in bytes | | sha256 | string | no | File SHA-256 (hex) | result: {assetId, uploadUrl, downloadUrl} Maximum file size: 1 GB. uploadUrl is valid for 1 hour. Then upload the file with an HTTP PUT request (body = file): curl -T MyApp-1.4.0.dmg "UPLOAD_URL" POST /api/v1/assets/{asset_id}/complete Confirm the upload. The server verifies the file's presence and size in storage. After that the file appears on the startup page and becomes available at downloadUrl (https://downloads.betafinds.com/...). EXAMPLE: FULL RELEASE CYCLE (bash) ---------------------------------- TOKEN="blt_YOUR_TOKEN" BASE="https://betafinds.com/api/v1" AUTH="Authorization: Bearer $TOKEN" # 1. Create a release with an announcement RELEASE_ID=$(curl -s -X POST "$BASE/startups/my-app/releases" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{"version":"1.4.0","notes":"- Dark theme\n- Fixed crash on startup"}' \ | jq -r '.result.id') # 2. Request a file upload FILE="MyApp-1.4.0.dmg" SIZE=$(stat -f%z "$FILE" 2>/dev/null || stat -c%s "$FILE") RESP=$(curl -s -X POST "$BASE/releases/$RELEASE_ID/assets" \ -H "$AUTH" -H "Content-Type: application/json" \ -d "{\"platform\":\"MACOS\",\"filename\":\"$FILE\",\"size\":$SIZE}") UPLOAD_URL=$(echo "$RESP" | jq -r '.result.uploadUrl') ASSET_ID=$(echo "$RESP" | jq -r '.result.assetId') # 3. Upload the file directly to storage curl -T "$FILE" "$UPLOAD_URL" # 4. Confirm curl -s -X POST "$BASE/assets/$ASSET_ID/complete" -H "$AUTH" EXAMPLE: PUBLISH NEWS (python) ------------------------------ import requests resp = requests.post( "https://betafinds.com/api/v1/startups/my-app/updates", headers={"Authorization": "Bearer blt_YOUR_TOKEN"}, json={ "type": "FIX", "title": "Fixed sync", "body": "Fixed a rare data loss during offline sync.", }, ) print(resp.json()) LIMITS AND RULES ---------------- - Publishing is only for your own startups with APPROVED status. - A release version is unique within the startup (409 on a duplicate). - Be moderate: the updates feed is visible to all BetaFinds visitors, spam leads to removal of updates and a ban. - Errors: 401 (missing/invalid token), 403 (not your startup), 404 (not found), 409 (conflict), 422 (invalid parameters). Questions: admin@betafinds.com