Gateway
Open tools at /a/<slug> or /<workspace>/<slug>. The hidden gateway path proxies the deployed tool and signs x-td-user for the app.
Documentation
Build, run, deploy, share, and operate agent-built internal tools. Everything here is public and written for both humans and coding agents: CLI commands, SDK calls, HTTP contracts, and the rules every tool must follow.
Getting started
A tool is a small app deployed behind the Tooldrop gateway. Start from the scaffold, let Claude Code or Codex build inside the contract, then deploy a version the workspace can open.
Install the CLI from your platform and sign in with the browser-approved device flow.
Scaffold a tool. The scaffold ships with the SDK, gateway guard, AGENTS.md, and CLAUDE.md.
Set required env vars, run locally, validate the contract, and deploy a version.
npm install -g https://tooldrop.app/cli.tgz
tooldrop login --url https://tooldrop.app
tooldrop init revenue-dashboard
cd revenue-dashboard
tooldrop env set DATABASE_URL --description "Read replica for reporting" --tag postgres
tooldrop dev
tooldrop validate
tooldrop deploy -m "first version"Getting started
Six things Tooldrop owns so your agent-built app doesn't have to.
Open tools at /a/<slug> or /<workspace>/<slug>. The hidden gateway path proxies the deployed tool and signs x-td-user for the app.
Builders store API keys, database URLs, OAuth credentials, and MCP endpoints as encrypted app env vars.
Each tool gets app-owned key/value storage behind td.storage. Free apps use Tool Storage (small caps); on Pro an app can be upgraded to a managed database — the same protocol with far larger caps plus a provisioned DATABASE_URL for advanced SQL. Pro editors browse and update records from the app database page, API, or CLI. Managed Postgres apps cannot be downgraded to Tool Storage because SQL tables cannot be preserved there.
Every deploy keeps source, author, status, message, and rollback metadata. Rollback repoints traffic to an existing ready version. On Pro, --staging deploys build without going live so editors can preview (?sbv=N) and promote when ready.
Pro workspaces can declare recurring jobs in tooldrop.json. Tooldrop generates the scheduler wrapper, records runs, and shows logs at the app level.
Pro workspaces can declare public webhook endpoints in tooldrop.json. Tooldrop publishes stable platform URLs, forwards deliveries into app /api/webhooks/* handlers, and records delivery status plus logs.
Tooldrop automatically records app 5xx gateway failures and Pro workspaces can browse/search app log lines written with td.log(), td.warn(), and td.error(). Editors can also read app, cron, and webhook logs through the CLI, HTTP API, or MCP.
A /s/<token> page starts invite-only, then can opt into guest viewer access or source remixing without env values.
The scaffold carries AGENTS.md and CLAUDE.md so coding agents know auth, secrets, storage, UI, and deployment rules.
Convert existing apps
A migration plan for teams bringing an existing page, dashboard, or internal app into Tooldrop without carrying over the old auth, secret, database, scheduler, or shell assumptions.
List framework, routes, auth/session code, env vars, database writes, file/blob storage, cron or queue workers, external APIs, and any pages that recreate workspace chrome.
Convert one useful page or workflow first. Prefer a stable read-only route, then move writes, background jobs, and larger data changes after the Tooldrop contract is passing.
Add tooldrop.json, declare required env keys, set framework/build/output fields when detection is not enough, and run with tooldrop dev so identity, env vars, base path, and storage match production.
Remove local auth, secret files, deployment dashboards, global nav, account/team settings, and host scheduler config. Tooldrop owns those surfaces; the tool keeps only the app workflow.
Run tooldrop validate, fix every contract failure, use tooldrop deploy --staging for risky changes on Pro, then publish with a human deploy message.
You are converting an existing app into a Tooldrop tool.
First read AGENTS.md or run tooldrop docs.
Then make a narrow conversion plan:
1. Inventory routes, auth, env vars, data writes, crons, and local/global chrome.
2. Convert one page or workflow at a time.
3. Remove app-owned auth and use td.user() / useTDUser().
4. Move secrets to Tooldrop env vars and app-owned state to td.storage.
5. Convert scheduled work to /api/cron/* plus tooldrop.json crons.
6. Convert inbound webhooks to /api/webhooks/* plus tooldrop.json webhooks.
7. Run tooldrop validate before deploy.
Do not deploy until the human has reviewed the converted workflow.Open the converted route through tooldrop dev, not plain framework dev, and verify base-path-safe links, loading, empty, and error states.
Run tooldrop validate before every deploy and fix contract failures instead of bypassing them.
Run a local production build or tooldrop deploy --preflight-build after changing framework config, env use, cron routes, or server-only imports.
Use a staged deploy on Pro for high-risk conversions, then promote only after an editor previews the version.
Write a deploy message that names the converted workflow, for example Converted invoice review to Tooldrop auth and storage.
Convert existing apps
Tooldrop owns sign-in and workspace access. The converted app keeps only workflow-specific permissions.
Delete app-owned login, logout, signup, session-cookie, next-auth, Clerk, Auth0, or custom JWT flows unless the human explicitly needs the tool to administer those systems.
Read the current viewer with server-side td.user() or client-side useTDUser() inside TDProvider; do not infer identity from browser storage or query params.
Move app-specific authorization into small domain checks, for example an allowed email/domain list, a role table read from an external system, or records stored in td.storage.
Keep the scaffold proxy.ts, lib/tooldrop/, and /api/td/* bridge intact. If the source app has middleware, merge only app-specific redirects and leave Tooldrop identity checks in place.
Remove auth screens from the converted page. The viewer reaches the tool through the Tooldrop workspace session and should land directly in the workflow.
Convert existing apps
Secrets become Tooldrop env vars, app-owned state moves to `td.storage`, and existing business systems stay behind server-side drivers or APIs.
Move secrets from .env, code constants, deployment settings, or old platform config into Tooldrop env vars with tooldrop env set KEY --description ... --tag ..., and declare required keys in tooldrop.json.
Keep external business systems external. Use normal server-side drivers or SDKs with process.env.DATABASE_URL, API keys, or base URLs; never expose those values to client components.
Move app-owned preferences, saved filters, drafts, notes, review states, and small CRUD records into td.storage; do not add SQLite, an ORM, or new production tables for that state.
For existing local JSON, SQLite, or browser-local data, write a small import script or route that reads records and calls td.storage.set() / tooldrop db set with namespaced keys.
When the converted app outgrows Tool Storage, upgrade the app to a managed database on Pro before bringing a new provider. The same td.storage calls keep working and a managed DATABASE_URL can support direct SQL.
For existing production databases, start read-only when possible, add LIMITs to exploratory reads, parameterize queries, and confirm static egress or relay setup for IP-allowlisted hosts.
Convert existing apps
Scheduled work should be explicit in `tooldrop.json`, implemented as `/api/cron/*` handlers, and observable from the app's Scheduled jobs page.
Convert host scheduler files, worker entrypoints, and external cron callbacks into Next.js GET route handlers under /api/cron/* when the tool uses the Next scaffold.
Declare each schedule in tooldrop.json with name, path, and cron schedule; deploy live when changing schedules because scheduler config follows the live version.
Cron handlers run without a human viewer. Do not call td.user(); use server-side env vars, external APIs, td.storage, and idempotent job state such as sync:last-run.
Use td.cron.log() for progress lines that should appear on the app's Scheduled jobs page, and return concise JSON or text because the response is saved with the run.
Copy the latest scaffold proxy.ts if validation reports an old cron bypass marker.
{
"slug": "revenue-dashboard",
"name": "Revenue Dashboard",
"crons": [
{
"name": "daily-sync",
"path": "/api/cron/daily-sync",
"schedule": "0 12 * * *"
}
]
}Convert existing apps
Inbound webhook callbacks should be explicit in `tooldrop.json`, implemented as `/api/webhooks/*` handlers, and observable from the app's Webhooks page.
Convert third-party webhook receivers into Next.js route handlers under /api/webhooks/* when the tool uses the Next scaffold.
Declare each public endpoint in tooldrop.json with name, path, and optional methods; live deploys publish stable /webhooks/<deploy-slug>/<endpoint> URLs.
Webhook handlers run without a human viewer. Do not call td.user(); verify provider signatures with server-side env vars before trusting payloads.
Use td.webhook.log() for delivery-visible notes, and return concise JSON or text because the response is saved with the delivery.
Copy the latest scaffold proxy.ts if validation reports an old webhook bypass marker.
{
"slug": "revenue-dashboard",
"name": "Revenue Dashboard",
"webhooks": [
{
"name": "stripe-events",
"path": "/api/webhooks/stripe",
"methods": ["POST"]
}
]
}Reference
Everything builders and agents run day to day. Install once, then work from inside the tool directory.
tooldrop login --url <platform-url>Connect a local machine to the workspace with the browser-approved device flow.
tooldrop init <name>Scaffold a new tool with Next.js, the Tooldrop SDK, proxy guard, AGENTS.md, and CLAUDE.md.
tooldrop dev [-p 4711]Run the tool locally behind the same gateway contract used in production.
tooldrop validateCheck tooldrop.json, framework settings, gateway files, SDK files, and obvious secret leaks.
tooldrop deploy -m "what changed"Run the local typecheck when available, package source, create a version, build on Tooldrop hosting, and publish through the workspace gateway.
tooldrop deploy --preflight-buildRun npm run build locally before uploading so build errors fail fast instead of waiting on the remote builder.
tooldrop deploy --stagingBuild a preview version that stays off the live URL. Editors preview it at ?sbv=N and promote it from the app's settings page (Pro).
tooldrop env ls|get|set|pull|rmManage encrypted app env vars and metadata. Values are explicit to retrieve and injected server-side.
tooldrop env ... --globalManage workspace-global env vars. Writes require admin or Clerk org:env:manage; app env vars override globals on conflicts.
tooldrop db ls|get|set|rm|info|upgrade|downgradeInspect and update an app's database from the CLI, or upgrade its storage tier to a managed database (Pro, editor access required). Downgrade is only for key/value-only managed stores; managed Postgres stays managed.
tooldrop logs [app|errors|cron|webhook|all]Read app logs, gateway/runtime errors, scheduled job run logs, and public webhook delivery logs from the CLI (Pro for app log browsing).
tooldrop apps|versions|rollback|cloneDiscover apps, inspect version history, repoint live traffic, or download source for more work.
tooldrop share|installTurn on a public share page, opt into guest join/remix access, or remix a shared source bundle from another Tooldrop workspace.
tooldrop docs|mcpPrint the agent guide or expose Tooldrop tools to Claude Code, Codex, and other MCP clients.
Reference
The tool runtime APIs. Server helpers come from @/lib/tooldrop/server, client helpers from @/lib/tooldrop/client.
td.user()serverReturns the signed-in viewer from the gateway JWT: { id, email, name, role }.
process.env.KEYserverRead platform env vars server-side after declaring them in tooldrop.json and setting values with the CLI. Env vars configure normal drivers and SDKs; they are not named database handles.
td.storageserverPer-app key/value storage for preferences, saved views, notes, review states, and records. Backed by Tool Storage on Free, or the app's managed database (far larger caps) on Pro — the same calls either way.
td.cron.log()serverWrite a log line to the current scheduled job run. No-ops outside scheduled job requests.
td.webhook.log()serverWrite a log line to the current public webhook delivery. No-ops outside webhook handler requests.
td.log() / td.warn() / td.error()serverWrite app-level log lines visible from the app Logs page, CLI, API, and MCP. Do not log secrets, payload bodies, or customer data you would not show an editor.
TDProvider + useTDUser()clientExpose the current Tooldrop user to client components through the tool's /api/td/user bridge.
tdStorageclientClient helper for Tool Storage calls through the app's /api/td/* route.
import { td } from "@/lib/tooldrop/server";
const me = await td.user();
const databaseUrl = process.env.DATABASE_URL;
await td.storage.set(`prefs:${me.id}`, { density: "compact" });
const prefs = await td.storage.get(`prefs:${me.id}`);Reference
Tool Storage is the app-owned database. Pro editors can inspect and update those records from the workspace UI, CLI, MCP, or HTTP API.
Runtime app code should keep using td.storage. The app database surface is for humans and agents operating the records around that app.
Reads and writes require app edit access plus Pro. Listing values, setting keys, and deleting records are all audit logged.
The API lives at /api/apps/:slug/storage. Use values=0 when a list only needs key metadata.
tooldrop db ls --app revenue-dashboard --prefix task:
tooldrop db get task:123 --app revenue-dashboard
tooldrop db set task:123 '{"title":"Review contract","done":false}' --app revenue-dashboard
tooldrop db rm task:123 --app revenue-dashboardReference
Pro workspaces can browse app runtime logs and automatic gateway failures from the app UI, CLI, HTTP API, and MCP.
Tooldrop automatically records app 5xx gateway failures. Server code can add explicit lines with td.log(), td.warn(), and td.error().
CLI and API access use the same app editor permission model. Read app, cron, and webhook logs with tooldrop logs or GET /api/apps/:slug/logs.
Logs store metadata and messages only. Do not log request bodies, webhook payloads, cookies, env values, tokens, or sensitive customer data.
tooldrop logs errors --app revenue-dashboard
tooldrop logs app --level warn --source runtime --app revenue-dashboard
tooldrop logs cron --details --app revenue-dashboard
tooldrop logs webhook --json --app revenue-dashboardimport { td } from "@/lib/tooldrop/server";
export async function GET() {
await td.log("Starting report refresh");
try {
// run app work here
await td.log("Report refresh finished");
return Response.json({ ok: true });
} catch (err) {
await td.error(err instanceof Error ? err.message : "Report refresh failed");
return Response.json({ error: "refresh failed" }, { status: 500 });
}
}Scheduled jobs
Pro workspaces can run recurring work from Next.js tools with platform-owned scheduling, run history, and logs.
Declare each job in tooldrop.json. Live deploys generate the scheduler wrapper and expose runs on the app's Scheduled jobs page.
Job handlers live under /api/cron/*. They run without a human viewer, so use server-side env vars and td.storage instead of td.user().
Use td.cron.log() for messages you want saved with the run.
{
"slug": "revenue-dashboard",
"name": "Revenue Dashboard",
"crons": [
{
"name": "daily-sync",
"path": "/api/cron/daily-sync",
"schedule": "0 12 * * *"
}
]
}import { td } from "@/lib/tooldrop/server";
export async function GET() {
await td.cron.log("Starting daily sync");
// Read secrets from process.env and store lightweight state in td.storage.
const previous = await td.storage.get("sync:last-run");
await td.storage.set("sync:last-run", {
previous,
finishedAt: new Date().toISOString(),
});
await td.cron.log("Daily sync finished");
return Response.json({ ok: true });
}Webhooks
Pro workspaces can publish stable webhook URLs from Next.js tools with platform-owned delivery history and logs.
Declare each endpoint in tooldrop.json. Live deploys publish stable URLs on the app's Webhooks page.
Handlers live under /api/webhooks/*. They run without a human viewer, so verify provider signatures with server-side env vars before trusting payloads.
Use td.webhook.log() for messages you want saved with the delivery.
{
"slug": "revenue-dashboard",
"name": "Revenue Dashboard",
"webhooks": [
{
"name": "stripe-events",
"path": "/api/webhooks/stripe",
"methods": ["POST"]
}
]
}import { td } from "@/lib/tooldrop/server";
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get("stripe-signature");
// Verify provider signatures with a server-side env var before trusting body.
if (!signature || !process.env.STRIPE_WEBHOOK_SECRET) {
await td.webhook.log("Missing Stripe signature", "warn");
return Response.json({ error: "missing signature" }, { status: 400 });
}
await td.webhook.log("Received Stripe event");
await td.storage.set("stripe:last-event", {
bytes: body.length,
receivedAt: new Date().toISOString(),
});
return Response.json({ ok: true });
}Building with agents
The four rules every coding agent must follow inside a tool, plus the MCP server that teaches them automatically.
No Clerk, next-auth, custom login pages, or session cookies inside a tool. Read identity with td.user().
Declare required keys in tooldrop.json, set app values with tooldrop env set, use tooldrop env set --global only for workspace-wide shared values, and read them server-side through process.env. Before asking the human for a credential, check workspace-global vars (tooldrop env ls --global / td_list_env global:true) and reuse a matching key — its value is injected into dev, previews, and deploys automatically. Do not use tooldrop.json connections.
Preferences, workflow state, saved filters, notes, and small records belong in td.storage.
Every deploy becomes version history. Use a short human sentence that says what changed.
When talking to viewers or builders, say Tooldrop hosting, remote build, or deployed runtime; do not name the underlying infrastructure provider.
You are editing a Tooldrop tool.
First read the local AGENTS.md or run:
tooldrop docs
If MCP is available, call td_docs before making changes.
Follow the tool contract:
- no app auth code
- no hardcoded secrets
- app-owned state goes in td.storage
- run tooldrop validate before deployAdd the Tooldrop MCP server to Claude Code, Codex, or any MCP client:
claude mcp add tooldrop -- tooldrop mcptd_docstd_whoamitd_list_appstd_logstd_list_envtd_env_settd_database_listtd_database_gettd_database_settd_database_deletetd_database_tiertd_validatetd_deploytd_versionstd_rollbacktd_clonetd_sharetd_installBuilding with agents
Tool Storage is the default database for a tool. These rules decide what lives there and when to reach for something bigger.
Tool Storage is the default lightweight database for a tool. It is platform-managed, scoped by tool, and accessed through td.storage.
For shared records, key by record type, for example task:<id>, note:<id>, or approval:<orderId>.
For per-user records, include me.id, for example prefs:<userId> or saved-view:<userId>:<viewId>.
For small collections, store one record per key and discover them with td.storage.list("task:").
Pro editors can inspect and update those records outside the tool with the app database page, tooldrop db, or /api/apps/:slug/storage. Treat those writes like production data changes.
Do not add SQLite, an ORM, or new tables just to save preferences, drafts, notes, review flags, saved filters, or small CRUD records.
Use server-side environment variables for external services: declare required database URLs, API keys, OAuth credentials, or MCP endpoints in tooldrop.json, set them with tooldrop env set, and read them with process.env.KEY.
Tooldrop does not provide named database connections. Do not add tooldrop.json connections or call td.query("main-db", ...); external databases are ordinary server-side drivers configured by env vars.
Use a direct DATABASE_URL only when the deployed runtime can reach that host. Public TLS Postgres/MySQL URLs work with pg, mysql2, or provider SDKs; private IPs, localhost tunnels, Unix sockets, Cloud SQL sockets, and VPC-only endpoints require a connector, proxy, static egress allowlist, or narrow HTTP API.
Sizing rule of thumb: Tool Storage caps at 5000 keys, 256KB per value, and 50MB per app — preferences, saved views, and small CRUD records. When data outgrows that, is unbounded (event streams, sync pipelines, imported datasets), or needs SQL (joins, rollups, indexes), the first choice on Pro is to upgrade the app to a managed database (app database page, or tooldrop db upgrade): the same td.storage calls keep working with far larger caps, records copy over, and a fully-managed DATABASE_URL is injected for direct SQL. Otherwise bring your own — a free-tier Neon/Supabase Postgres set with tooldrop env set DATABASE_URL.
Managed tier: upgrading an app provisions a database it owns. td.storage transparently uses it with the larger caps, and (where the platform has a database provider configured) process.env.DATABASE_URL points at that same managed Postgres for advanced/relational/chart data — no signup, connection string, or allowlist. Managed Postgres apps stay on the managed tier so SQL tables and deployment database state are not lost.
API reference
Every request authenticates with one of four modes. The tables below note which mode each endpoint expects.
CLI bearer
Stored in ~/.tooldrop.json after login and used by the CLI plus MCP server.
Workspace session
Browser session checked by the platform gateway before a tool is opened.
App token
Runtime token issued to deployed tools or tooldrop dev for storage calls.
Public token
Share-link token embedded in /s/<token>; never grants env values or private app data.
| Method | Endpoint | Auth |
|---|---|---|
| GET | /api/meReturn the current user and platform URL. | CLI bearer |
| GET | /api/appsList apps visible to the signed-in user. | CLI bearer |
| POST | /api/appsRegister or sync a tool before dev or deploy. | CLI bearer |
| GET | /api/apps/:slugReturn app metadata, env metadata, and recent versions. | CLI bearer |
| POST | /api/apps/:slug/devIssue local dev credentials, env values, and the gateway signing secret. | Builder |
| POST | /api/apps/:slug/deployUpload source files and create a new deploy version. Pass staging: true to build without going live (Pro). | Editor |
| GET | /api/apps/:slug/versionsList version history and the currently served version. | Viewer |
| POST | /api/apps/:slug/rollbackActivate an existing ready version — roll back to an older one or promote a staged one. | Editor |
| GET | /api/apps/:slug/bundleDownload source for clone or handoff. Optional ?version=N. | Builder |
| GET | /api/apps/:slug/logsRead app logs, gateway errors, cron run logs, and webhook delivery logs. Query: ?type=app|errors|cron|webhook|all&level=error&source=gateway&q=text&limit=50. | Editor + Pro |
| Method | Endpoint | Auth |
|---|---|---|
| GET | /api/apps/:slug/storageList Tool Storage records. Query: ?prefix=task:&limit=100&values=0. Values are included by default unless values=0. | Editor + Pro |
| GET | /api/apps/:slug/storage?key=KEYRead one Tool Storage record by key. | Editor + Pro |
| POST | /api/apps/:slug/storageCreate or replace one JSON-serializable value: { key, value }. | Editor + Pro |
| DELETE | /api/apps/:slug/storageDelete one record: { key }. | Editor + Pro |
| GET | /api/apps/:slug/databaseReport the app's storage tier, managed database status, and usage stats. | Editor + Pro |
| POST | /api/apps/:slug/databaseUpgrade to a managed database (needs managedDatabase). Provisions it and copies existing records over. | Editor + Pro |
| DELETE | /api/apps/:slug/databaseDowngrade key/value-only managed storage to Tool Storage. Managed Postgres databases are refused because SQL tables cannot be preserved. | Editor + Pro |
| Method | Endpoint | Auth |
|---|---|---|
| GET | /api/apps/:slug/envList env metadata. Add ?values=1 to explicitly include decrypted values. | Editor |
| POST | /api/apps/:slug/envSet or rotate one encrypted env var with description and tags. | Editor |
| DELETE | /api/apps/:slug/envRemove an env var from the vault. | Editor |
| GET | /api/settings/variablesList workspace-global env metadata visible to the caller. Add ?values=1 to include decrypted values the caller can use. | Workspace member |
| POST | /api/settings/variablesSet or rotate one workspace-global env var: { key, value, description, scope, allowedUsers }. | Admin or org:env:manage |
| DELETE | /api/settings/variablesRemove one workspace-global env var: { key }. | Admin or org:env:manage |
| GET | /api/apps/:slug/shareReturn the active share link and stats if one exists. | Viewer |
| POST | /api/apps/:slug/shareCreate, update, or regenerate the public share link. New links default to no guest join and no remix. | Editor |
| DELETE | /api/apps/:slug/shareRevoke the active share link. | Editor |
| Method | Endpoint | Auth |
|---|---|---|
| ANY | /a/:deploySlug/*Gateway proxy that signs x-td-user and serves the current tool version. | Workspace session |
| ANY | /webhooks/:deploySlug/:endpointPublic webhook receiver that records delivery status, forwards to the generated wrapper, and returns the app handler response. | Public endpoint |
| GET | /api/tooldrop-cron/:jobGenerated scheduled-job wrapper that records start, finish, status, and log lines before invoking the tool's /api/cron/* handler. | Host secret |
| ANY | /api/tooldrop-webhook/:endpointGenerated webhook wrapper that forwards public deliveries into the tool's /api/webhooks/* handler. | Platform secret |
| POST | /api/connectorRuntime bridge for Tool Storage operations plus cron, webhook, and app log reporting. Tools call this through the SDK. | App token |
| POST | /api/cli/device/startStart CLI browser login by creating a short-lived device code. | Public |
| GET | /api/cli/device/poll?code=...Poll for browser approval and return a one-time CLI token. | Public code |
Operations
If DATABASE_URL is correct but every query times out (connect ETIMEDOUT), the database probably only accepts connections from IPs on its allowlist. Tooldrop solves this with one permanent egress IP: the database admin allowlists that single address once, and every tool — local tooldrop dev and deployed — reaches the database through it. Your own machine's IP is never involved.
Tooldrop egress IP
8.235.38.133tooldrop-egressAll Tooldrop database traffic — tooldrop dev on any machine and every deployed tool — reaches your database from one permanent platform IP. Have your database admin add that single address to the allowlist, labeled tooldrop-egress. It never changes, and it is the only entry Tooldrop ever needs. Your own machine's IP is never involved.
DATABASE_URL at the relay addressYour workspace admin registers the database with the Tooldrop egress relay and gives you a relay address (host and port). Use that as the host in DATABASE_URL with your normal database credentials: tooldrop env set DATABASE_URL.
Nothing else changes: server-only code, the usual driver (pg, mysql2, or a provider SDK), parameterized queries, and a read replica with a read-only user when possible. The connection is end-to-end between your driver and the database — the relay only forwards packets, so credentials and TLS still terminate at the database.
Console → SQL → the instance → Connections → Networking → Authorized networks → Add network. Name: tooldrop-egress, Network: the Tooldrop IP as x.x.x.x/32, then Save. Applies in about a minute.
The instance's security group → Edit inbound rules → add a rule for the database port with source x.x.x.x/32, and put tooldrop-egress in the rule's Description.
The server's Networking page → Firewall rules → add a rule named tooldrop-egress.
Operations
Defaults the platform enforces so a quick internal tool never becomes an incident.
Tools never own authentication. Direct deployment URLs are guarded by proxy.ts and should reject requests without x-td-user.
Scheduled jobs enter through a generated wrapper protected by CRON_SECRET. Put job handlers under /api/cron/* and do not expose them to browsers.
Webhooks enter through public /webhooks/<deploy-slug>/<endpoint> URLs, then a generated wrapper protected by TOOLDROP_WEBHOOK_SECRET. Put handlers under /api/webhooks/* and verify provider signatures inside the handler.
App logs capture metadata only: level, source, message, path, method, status, duration, request id, and optional user attribution. Do not log secrets, request bodies, cookies, env values, tokens, or sensitive customer data.
Uploaded bundles exclude .env files, local state, build output, node_modules, and logs. Env values remain on the platform.
Tool Storage is intentionally small: 5000 keys per app, 256KB per value, and 50MB total per app. A Pro app upgraded to a managed database keeps the same protocol with far larger caps.
Public remix links publish source code when enabled. They never publish env values, data, credentials, or member lists.
Something missing? The Markdown docs carry the same content in one file.