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
then run commands like 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_products
Ordersread: read_orders · write: write_orders
Customersread: read_customers · write: write_customers
Analytics / Accountingread: read_finance · write:
HRread: read_hr · write: write_hr
Webhooksread: admin_webhooks · write: admin_webhooks

Products (write_products)

GET /v1/productsList products (search, limit)
POST /v1/productsCreate product + default variant
GET /v1/products/:idProduct with variants + total stock
PATCH /v1/products/:idUpdate product
DELETE /v1/products/:idSoft-delete product
POST /v1/products/bulkBulk update prices/status

Orders (write_orders)

GET /v1/ordersList orders (status, limit)
POST /v1/ordersCreate order (stock + journal auto-post)
GET /v1/orders/:idOrder with line items

Customers (write_customers)

GET /v1/customersList customers (search, limit)
POST /v1/customersCreate customer
GET /v1/customers/:idGet customer
PATCH /v1/customers/:idUpdate customer
DELETE /v1/customers/:idSoft-delete customer

Analytics (read_finance)

GET /v1/analytics/dashboardSales, profit, stock KPIs
GET /v1/analytics/salesSales totals + daily breakdown (period in days)
GET /v1/analytics/inventoryStock value + low-stock counts
GET /v1/analytics/profitRevenue, COGS, gross/net profit

Accounting (read_finance)

GET /v1/finance/accountsChart of accounts
GET /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, tax

HR (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 events
POST /v1/webhooksCreate webhook (url + events, secret returned once)
DELETE /v1/webhooks/:idDelete webhook

Identity (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 + scopes
ohala healthCheck the API is reachable
ohala products listList all products
ohala products create <name>Create a product (--price, --stock, --cost, --sku, --barcode, --category-id)
ohala orders listList recent orders
ohala analytics dashboardShow dashboard KPIs
ohala webhooks listList webhooks
ohala helpShow help

Every 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.received

Submit 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.adjusted

Delivery 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);
}