Developer Portal
Build on Ohala — SDKs, APIs, Plugins, and CLI tools.
Getting Started
Welcome to the Ohala Developer Portal. This guide covers everything you need to build on Ohala Business OS.
1. Create Your Workspace
Sign up at guerison.website/signup. Each workspace gets isolated data, users, and settings.
2. Get Your API Key
Navigate to Settings → API Keys, create a key, select the scopes for the resources you'll use (e.g. Products read/write, Finance read), and store it securely. The full key (ohala_...) is shown only once — treat it like a password. A key without the required scope gets a 403.
3. Install the SDK
npm install @ohala/client
4. Make Your First Call
import { OhalaClient } from "@ohala/client";
const ohala = new OhalaClient({
apiKey: "ohala_your_key_here"
});
const products = await ohala.products.list();
console.log(products.data);You can also use the CLI instead:
npx @ohala/cli login
npx ohala products list.API Reference
Base URL: https://guerison.website/api/v1
Authentication
Authorization: Bearer ohala_your_api_key_here
Authenticate with an API key from Settings → API Keys. Responses are wrapped in { data: ... }; errors return { error: "message" } with a 4xx/5xx status. The only exceptions are /v1/me and /v1/health, which return their fields directly.
Scopes
Productsread: read_products · write: write_productsOrdersread: read_orders · write: write_ordersCustomersread: read_customers · write: write_customersAnalytics / Accountingread: read_finance · write: —HRread: read_hr · write: write_hrWebhooksread: admin_webhooks · write: admin_webhooksProducts (write_products)
GET /v1/productsList products (search, limit)POST /v1/productsCreate product + default variantGET /v1/products/:idProduct with variants + total stockPATCH /v1/products/:idUpdate productDELETE /v1/products/:idSoft-delete productPOST /v1/products/bulkBulk update prices/statusOrders (write_orders)
GET /v1/ordersList orders (status, limit)POST /v1/ordersCreate order (stock + journal auto-post)GET /v1/orders/:idOrder with line itemsCustomers (write_customers)
GET /v1/customersList customers (search, limit)POST /v1/customersCreate customerGET /v1/customers/:idGet customerPATCH /v1/customers/:idUpdate customerDELETE /v1/customers/:idSoft-delete customerAnalytics (read_finance)
GET /v1/analytics/dashboardSales, profit, stock KPIsGET /v1/analytics/salesSales totals + daily breakdown (period in days)GET /v1/analytics/inventoryStock value + low-stock countsGET /v1/analytics/profitRevenue, COGS, gross/net profitAccounting (read_finance)
GET /v1/finance/accountsChart of accountsGET /v1/finance/journalsJournal entries (filters + paging)GET /v1/finance/reports/:typetrial_balance, general_ledger, income_statement, balance_sheet, cash_flow, journals, audit_trail, budget_vs_actual, exceptions, taxHR (read_hr / write_hr)
GET /v1/hr/employeesEmployees (search)GET /v1/hr/attendanceAttendance (from/to)GET /v1/hr/leave-requestsLeave requests (status)GET /v1/hr/payrollPayroll records (period)Webhooks (admin_webhooks)
GET /v1/webhooksList webhooks + available eventsPOST /v1/webhooksCreate webhook (url + events, secret returned once)DELETE /v1/webhooks/:idDelete webhookIdentity (no scope)
GET /v1/meTenant, key name, and scopes (unwrapped)GET /v1/healthService health check (unwrapped)JavaScript SDK
npm install @ohala/client
Client API
const ohala = new OhalaClient({ apiKey: "ohala_xxx" });
// Identity & health (no scope) — return fields directly
await ohala.me();
await ohala.health();
// Products
await ohala.products.list({ search: "leather" });
await ohala.products.get("product-uuid");
await ohala.products.create({ name: "Bag", price: 1500, stock_quantity: 10 });
await ohala.products.bulkUpdate(["id1","id2"], { price: 2000 });
// Orders — needs a variant_id (from products.get)
await ohala.orders.list({ status: "completed" });
await ohala.orders.create({
items: [{ variant_id: "uuid", quantity: 2 }],
payment_method: "cash", amount_paid: 3000
});
// Analytics & accounting (read_finance)
await ohala.analytics.dashboard();
await ohala.accounting.reports("trial_balance");
// HR (read_hr)
await ohala.hr.employees();
// Webhooks (admin_webhooks)
await ohala.webhooks.list();
const webhook = await ohala.webhooks.create({
url: "https://my-app.com/webhook",
events: ["order.created", "payment.received"]
});
// webhook.data.secret (whsec_...) is returned only here — save it to verify deliveries.me() and health() return their fields directly (not wrapped in { data }); every other method returns the standard envelope. Missing scope throws OhalaError with status 403. Full SDK source: sdk/index.ts in the repository.
CLI Reference
Run commands ad-hoc with npx @ohala/cli <command>, or install globally with npm install -g @ohala/cli and use ohala <command>. Authenticate once with ohala login — it verifies your key against the live API before saving it to ~/.ohala/config.json. For scripts, set the OHALA_API_KEY environment variable instead.
npx @ohala/cli [command]
ohala loginAuthenticate with API key (verified against /v1/me)ohala whoamiShow workspace + scopesohala healthCheck the API is reachableohala products listList all productsohala products create <name>Create a product (--price, --stock, --cost, --sku, --barcode, --category-id)ohala orders listList recent ordersohala analytics dashboardShow dashboard KPIsohala webhooks listList webhooksohala helpShow helpEvery command above is backed by a real API call — nothing is stubbed. Set OHALA_API_URL to point the CLI at a different environment.
Building Plugins
Plugins are event-driven extensions. You define a manifest declaring the system events you care about; when one fires, Ohala POSTs the event payload to your webhook URL. Your plugin code runs on your own infrastructure — Ohala never executes third-party code.
Plugin Manifest (plugin.json)
{
"slug": "my-awesome-plugin",
"name": "My Awesome Plugin",
"version": "1.0.0",
"description": "Does something amazing.",
"author": { "name": "You", "email": "you@example.com" },
"icon": "zap",
"categories": ["inventory", "sales"],
"price": 0,
"hooks": [{
"event": "order.created",
"handler": "onOrderCreated",
"priority": 10
}],
"webhook_url": "https://your-service.com/ohala-webhook"
}Hooks for the same event run in ascending priority order. Each handler's response (an optional { "modified": { ... } }) is merged into the payload passed to the next handler.
Webhook Delivery (Ohala → Your Service)
POST https://your-service.com/ohala-webhook
Headers:
Content-Type: application/json
X-Ohala-Event: order.created
X-Ohala-Tenant: tenant-uuid
Body: {
"event": "order.created",
"data": { "order_id": "uuid", "total": 5000, "customer": "Jane" },
"timestamp": "2026-08-05T12:00:00Z"
}
Respond with HTTP 2xx to mark the delivery successful. Optionally return
{ "modified": { ... } } to pass data to the next handler in the chain.Available System Hooks (35)
order.createdorder.confirmedorder.paidorder.shippedorder.cancelledorder.refundedproduct.createdproduct.updatedproduct.deletedproduct.low_stockcustomer.createdcustomer.updatedpayment.receivedpayment.failedpayment.refundedinventory.updatedstock.adjustedstock.transferredproduction.startedproduction.completedemployee.createdemployee.updatedpayroll.processedinvoice.createdinvoice.paidjournal.postedtenant.createdtenant.activateduser.inviteduser.joinedplugin.installedplugin.activatedplugin.deactivatedplugin.uninstalledwebhook.receivedSubmit via Developer Portal at /app/marketplace/developer. Full SDK docs at Developer SDK.
Webhooks Guide
Tenant webhooks let you push business events to any HTTPS endpoint. Create one via Settings → Webhooks or the API (POST /v1/webhooks). Ohala POSTs the event to your URL with a 10-second timeout and records every delivery (status, response code, duration) in the dashboard.
Available Events (12)
order.createdorder.updatedorder.paidproduct.createdproduct.updatedproduct.deletedcustomer.createdcustomer.updatedpayment.receivedpayment.failedstock.lowstock.adjustedDelivery Payload
POST https://your-app.com/webhook
Headers:
Content-Type: application/json
X-Ohala-Signature: sha256=<hex-hmac>
Body: {
"event": "order.created",
"data": { "order_id": "uuid", "total": 5000 },
"timestamp": "2026-08-05T12:00:00Z"
}Each webhook is created with a whsec_... secret, returned only once in the create response. Deliveries are signed with HMAC-SHA256 over the raw request body — verify the X-Ohala-Signature header before trusting a payload. If a webhook has no secret set, no signature header is sent.
Verifying Signatures
import crypto from "crypto";
// rawBody = the exact request body string you received
function verifySignature(rawBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody).digest("hex");
// Header arrives prefixed as "sha256=<hex>" — strip the prefix.
const received = signature.replace(/^sha256=/, "");
const a = Buffer.from(received);
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}