Expose an API to another Owner's App
Let an App of another person or Workspace call your App's API: open the API's paths with a Public Path, and check an API key that both Apps keep as a Secret.
Your own Apps, or two Apps of one Workspace, do not need this: name the calling App one of your App's Callers, and it calls without a key, with nothing open to the internet. Basemodo vouches for a Caller only between Apps of the same Owner, and refuses one of another Owner with caller_of_another_owner. Between different Owners, your App opens its API itself and checks who calls.
On the called App
1. Make a key and keep it as a Secret
Make a long random key, and keep it as a Secret of the called App, typed or piped so it stays out of your shell's history:
openssl rand -hex 32 > api-key.txtbasemodo env set API_KEY < api-key.txtbasemodo restartThe App reads it as process.env.API_KEY once it starts again (basemodo env set, basemodo restart).
2. Open the API with a Public Path
A Private App asks every visitor to sign in, and another Owner's App cannot. Declare the API's paths as Public Paths, with public_paths in the called App's basemodo.toml, and deploy it:
public_paths = ["/api/*"]basemodo deployOnly those paths open: the App's pages and every other route stay as they were. A request on a Public Path carries no identity headers and has stricter limits than signed-in visitors (Public Paths). basemodo status lists what is open.
3. Check the key on every request
Anyone on the internet can reach a Public Path, so the App refuses every request under it that does not carry the key. Compare it in constant time:
import { timingSafeEqual } from "node:crypto";
function hasKey(request: Request): boolean { const sent = Buffer.from(request.headers.get("authorization") ?? ""); const key = Buffer.from(`Bearer ${process.env.API_KEY}`); return sent.length === key.length && timingSafeEqual(sent, key);}
// In each /api route: if (!hasKey(request)) return new Response("Unauthorized", { status: 401 });On the calling App
Give the key to the calling App's Owner over a channel you both trust, never in code or a repository. They keep it as a Secret of their App:
basemodo env set CRM_API_KEY < api-key.txtbasemodo restartTheir App's server code calls the called App's public URL (its basemodo.app address, or its custom domain) with the key:
const res = await fetch("https://crm.basemodo.app/api/contacts", { headers: { authorization: `Bearer ${process.env.CRM_API_KEY}` },});Not http://<slug>.apps.internal: that address reaches only Apps of the same Owner. The key stays on the server: never put it in a page, where every visitor could read it.
Changing the key
Give each calling App a key of its own (API_KEY_REPORTS, API_KEY_INVOICES), and accept any of them, so you can take one back without stopping the others. To replace a key: set the new one beside the old on the called App and accept both, let the calling App switch to the new one, then take the old one off with basemodo env unset.
When the Apps come to share an Owner
If a Transfer leaves both Apps with the same Owner (a person's two Apps, or two Apps of one Workspace), switch to Callers: name the calling App, move the paths from public_paths to [callers] paths, and take the key off both Apps.