Install
openclaw skills install drivethru-odooTalk to an Odoo ERP through its `agent_api` REST module — look up eBay products and inventory, push eBay sale orders and read tracking, run the Accounts Payable PO→vendor-bill flow, and schedule MRP production batches onto workcenters. Use whenever the user needs to read from or write to Odoo for eBay fulfillment, vendor-bill matching, or production planning.
openclaw skills install drivethru-odooThis skill is a thin, JSON-in / JSON-out wrapper over the Odoo agent_api
REST module (/agent_api/v1/*). It authenticates with the X-Agent-API-Key
header and covers three domains, one CLI script each:
| Script | Domain | Odoo endpoint prefix |
|---|---|---|
scripts/sales.py | eBay sales/inventory | /agent_api/v1/ebay/... |
scripts/ap.py | Accounts Payable | /agent_api/v1/ap/... |
scripts/production.py | MRP scheduling | /agent_api/v1/production/... |
All three follow the same calling convention:
echo '<json-args>' | python3 scripts/<script>.py <action>
The action is the first CLI argument; arguments are a JSON object on
stdin. Every script prints a single JSON object on stdout, or
{"error": {"type": ..., "message": ...}} with a non-zero exit code on failure.
The agent host MUST expose ODOO_URL and ODOO_API_KEY in the
environment before this skill is invoked. If either is missing, the scripts exit
with {"error": {"type": "config_error", ...}} (exit code 2) — stop and tell
the user to set them. Do not prompt the user to paste the key into chat; secrets
come from the environment.
Set ODOO_DRY_RUN=true to preview writes without mutating Odoo.
agent_api REST surface, not arbitrary models.agent_api addon.scripts/sales.py| Action | Input (stdin JSON) | Notes |
|---|---|---|
list-products | (none) | Products in the Odoo 'eBay' category. |
inventory | {"skus": ["A-1", "B-2"]} | Stock levels per SKU. |
create-order | an eBay order object (see mock-order) | Idempotent in Odoo; honors dry-run. |
tracking | {"odoo_order_id": 123} | odoo_order_id is numeric, not SO…. |
mock-order | {"sku"?, "buyer_index"?, "num_line_items"?} | Offline — generates a test order. |
Typical test flow (dry run): generate a mock order, then push it.
python3 scripts/sales.py mock-order < /dev/null \
| python3 scripts/sales.py create-order
create-order returns {odoo_order_id, odoo_order_name, already_existed, confirmed, confirm_error, ...}. already_existed: true means Odoo already had
this eBay order — an idempotent skip, not an error.
scripts/ap.py| Action | Input (stdin JSON) |
|---|---|
search-pos | {"search"?, "vendor"?, "state"?, "limit"?} |
get-po | {"po_id": 123} |
update-po-lines | {"po_id": 123, "lines": [{"line_id", "price_unit"}], "freight_cost"?, "fees_cost"?} |
create-bill | {"po_id": 123, "vendor_bill_number"?, "invoice_date"?, "line_ids"?, "reviewer_user_id"?, "review_note"?, "expected_total"?, "tolerance"?} |
get-bill | {"bill_id": 456} |
search-vendors | {"search"?, "limit"?} |
The AP flow is: find the PO (search-pos → get-po), correct line prices /
freight / fees if needed (update-po-lines), then create-bill. The bill is
created in draft; Odoo's create() override auto-adds fee/freight/misc lines,
and the reviewer receives an Odoo activity to verify and post it. Pass
expected_total + tolerance (default 0.05) to have the server flag
mismatches.
scripts/production.py| Action | Input (stdin JSON) |
|---|---|
overview | {"batch_detail"?, "batch_limit"?, "unscheduled_only"?} |
list-batches | {"unscheduled_only"?, "limit"?, "offset"?} |
get-batch | {"batch_id": 142} |
schedule | {"batch_id", "primary_workcenter_id"?, "production_center_id"?, "date_planned_start"?, "date_planned_finished"?, "activity_message"?} |
plan | {"batch_id": 142} |
bulk-schedule | {"atomic"?, "updates": [{"batch_id", ...fields}]} |
list-workcenters | {"active_only"?} |
get-workcenter | {"workcenter_id": 3} |
production-centers | (none) |
decoration-methods | (none) |
Start a planning pass with overview (one round-trip: open batches +
workcenters with their current load + reference data). Pull get-batch for the
batches you intend to place (it returns eligible_workcenters,
eligible_production_centers, decoration readiness, and per-MO durations), then
write decisions with schedule (single) or bulk-schedule (many).
The four writable scheduling fields are primary_workcenter_id,
production_center_id, date_planned_start, date_planned_finished. Partial
updates are fine. The server applies them in safe order and auto-derives
production_center_id from the workcenter and date_planned_finished from the
start unless you set them explicitly. plan runs Odoo's native slot allocator
and requires a workcenter to already be set.
echo '{"batch_id": 142, "primary_workcenter_id": 3,
"date_planned_start": "2026-06-10T14:00:00",
"activity_message": "Auto-scheduled by agent"}' \
| python3 scripts/production.py schedule
Scripts exit non-zero and print {"error": {...}} on failure:
config_error (exit 2) — ODOO_URL / ODOO_API_KEY missing or malformed.api_error — Odoo returned a non-2xx (includes status and body). 401
means a bad ODOO_API_KEY; 404 on get-* means the record doesn't exist.connection_error — Odoo unreachable / timed out.validation_error — bad input JSON or missing required field.unknown_action / usage (exit 2) — bad CLI invocation; the message lists
the valid actions.Surface the human-readable message to the user. Do not retry on
config_error, validation_error, or a 401 api_error.
ODOO_API_KEY as a secret — the scripts read it from the environment
and never echo it.ODOO_DRY_RUN=true until you've confirmed the data mapping. Confirm
vendor-bill creation and production schedule writes with the user before
running them against a live (non-dry-run) Odoo.references/agent_api_endpoints.md — the
full endpoint surface and request/response shapes.references/production_scheduling.md —
the MRP data model (batches, workcenters, production centers, decoration
methods) and how the four scheduling fields interact.