Odeva CLI
The Odeva CLI is the fastest way to build apps on Odeva. It scaffolds a working project, registers the app on the platform, exposes your local server through a public tunnel for webhook testing, and submits the app for marketplace review.
It does the same things you can do in the Odeva admin, with less clicking.
Install
Section titled “Install”npm i -g @odeva/cliRequires Node 20 or Bun 1.1+. Verify:
odeva --version1. Sign in
Section titled “1. Sign in”odeva auth loginOpens your browser, you confirm the device code matches, and a long-lived token is saved to ~/.config/odeva/credentials.json (mode 0600). If your account belongs to multiple organisations, you’ll be prompted to pick one. Switch later with odeva auth select-org.
2. Scaffold an app
Section titled “2. Scaffold an app”odeva app initPrompts for a directory name and writes a Hono-on-Bun starter with:
odeva.app.toml— name, slug, scopes, webhook subscriptions.src/index.ts— Hono server with health, install, and webhook routes.src/install.ts— install handshake that exchanges theinstall_codefor a per-installation API key.src/installations.ts—InstallationStoreinterface backed by SQLite (swap for Postgres at scale).
3. Register the app on Odeva
Section titled “3. Register the app on Odeva”cd my-odeva-appodeva app config linkCreates the app server-side and writes its client_id back to odeva.app.toml. On the first link, the CLI also writes the client_secret to .odeva.env (gitignored, mode 0600) — this is the only time you see the secret. If you lose it, run odeva app config rotate-secret.
4. Run locally with a tunnel
Section titled “4. Run locally with a tunnel”odeva app devStarts your dev server, opens a Cloudflare tunnel, registers the webhook subscriptions from odeva.app.toml against the tunnel URL, writes webhook signing secrets to .env.odeva.local, and tears subscriptions down on Ctrl-C. .odeva.env is loaded into the child process, so your install handler has the credentials it needs.
The shorthand webhook config uses /webhooks/<event> as the handler path:
[webhooks]api_version = "2026-01"subscriptions = ["reservation.created"]Use the object form when the route should differ from the event name:
[[webhooks.subscriptions]]topic = "reservation.created"uri = "/webhooks/reservations"While odeva app dev is running, changes to odeva.app.toml and .odeva.env reload webhook registrations and restart the local app process.
Test the install flow by visiting:
https://<tunnel-url>/install?install_code=<code>(The real install_code is issued by the platform when an organisation clicks Install in the marketplace.)
5. Trigger a webhook locally
Section titled “5. Trigger a webhook locally”With odeva app dev running, send a signed sample payload to the matching local handler:
odeva webhook trigger reservation.createdExample output:
POST http://localhost:3000/webhooks/reservation.created event: reservation.created delivery: evt_test_f6904722334a6558 signed: yes
✓ 200 OK (27ms){"received":true}The generated Hono template verifies x-odeva-signature with ODEVA_WEBHOOK_SECRET or the event-specific ODEVA_WEBHOOK_SECRET__<EVENT> value, then logs the full nested payload for inspection.
6. Try the SDK route
Section titled “6. Try the SDK route”The app template includes a small SDK example at GET /sdk/accommodations. Set the organization slug in .odeva.env:
ODEVA_ORGANIZATION_SLUG=my-orgLet odeva app dev reload, then request:
curl http://localhost:3000/sdk/accommodationsThe route demonstrates a server-side SDK call:
import { OdevaClient } from "@odeva/booking-sdk";
const odeva = new OdevaClient({ organizationSlug });const accommodations = await odeva.searchAccommodations({ startDate: "2026-07-01", endDate: "2026-07-08", guests: 2, limit: 5,});For browser-only sites, see the JavaScript SDK guide for the CDN build and public booking examples.
7. Submit for review
Section titled “7. Submit for review”odeva app submitSubmits the app for marketplace review. Reviewers check that the install flow works, the UI is reasonable, and the requested scopes match the app’s purpose. They don’t review your code.
Check status any time:
odeva app statusIf the app is rejected, the reviewer’s notes appear in odeva app status. Address the feedback and re-run odeva app submit.
Command reference
Section titled “Command reference”| Command | What it does |
|---|---|
odeva auth login | OAuth device-code sign-in. Saves a CLI token. |
odeva auth whoami | Show the active user, organisation, and API URL. |
odeva auth select-org | Switch which organisation the CLI acts on. |
odeva auth logout | Forget the saved credentials. |
odeva app init | Scaffold a new app from a template. |
odeva app config link | Register the app on Odeva. Writes client_id + client_secret. |
odeva app config rotate-secret | Mint a new client_secret. The old one stops working. |
odeva app dev | Run locally with a public tunnel and auto-registered webhooks. |
odeva app submit | Submit the app for marketplace review. |
odeva app status | Show review status and reviewer notes. |
odeva webhook list | List webhook subscriptions on the active organisation. |
odeva webhook trigger <topic> | Fire a sample payload at your local handler. |
Run odeva <command> --help for flags and details on any command.
Configuration
Section titled “Configuration”Environment variables override defaults:
ODEVA_API_URL— point at a non-production API (default:https://booking.odeva.app).ODEVA_TOKEN— bypass the browser flow with a pre-existing CLI token. Useful in CI.XDG_CONFIG_HOME— relocatecredentials.json(defaults to~/.config).