Sales Orders API
Manage sales orders and quotations via the REST API.
Overview
| Operation |
Method |
Endpoint |
| Create |
POST |
/restapi/1.0/object/sale.order |
| List/Search |
GET |
/restapi/1.0/object/sale.order |
| Read |
GET |
/restapi/1.0/object/sale.order/{id} |
| Update |
PUT |
/restapi/1.0/object/sale.order/{id} |
| Delete |
DELETE |
/restapi/1.0/object/sale.order/{id} |
Authentication
X-API-Key: your_api_key_here
Sale Order Fields
Core Fields
| Field |
Type |
Required |
Description |
partner_id |
Integer |
Yes |
Customer ID |
date_order |
Datetime |
No |
Order date (auto-set) |
commitment_date |
Datetime |
No |
Delivery/commitment date |
validity_date |
Date |
No |
Quotation expiry date |
user_id |
Integer |
No |
Salesperson ID |
team_id |
Integer |
No |
Sales team ID |
origin |
String |
No |
Source document |
client_order_ref |
String |
No |
Customer reference |
note |
Text |
No |
Internal notes |
Computed Fields (Read-Only)
| Field |
Type |
Description |
name |
String |
Order reference (S00001) |
amount_untaxed |
Float |
Subtotal |
amount_tax |
Float |
Tax amount |
amount_total |
Float |
Total amount |
state |
String |
Order state |
invoice_status |
String |
Invoice status |
Order States
| State |
Label |
Description |
draft |
Quotation |
Draft quote |
sent |
Quotation Sent |
Quote sent to customer |
sale |
Sales Order |
Confirmed order |
done |
Locked |
Completed order |
cancel |
Cancelled |
Cancelled order |
Invoice Status
| Status |
Description |
upselling |
Upselling opportunity |
invoiced |
Fully invoiced |
to invoice |
To be invoiced |
no |
Nothing to invoice |
Sale Order Line Fields
| Field |
Type |
Required |
Description |
order_id |
Integer |
Yes |
Parent order ID |
product_id |
Integer |
Yes |
Product ID |
name |
String |
No |
Description |
product_uom_qty |
Float |
Yes |
Quantity |
price_unit |
Float |
No |
Unit price |
discount |
Float |
No |
Discount % |
tax_id |
List[Int] |
No |
Tax IDs |
Line Computed Fields
| Field |
Type |
Description |
price_subtotal |
Float |
Line subtotal |
price_total |
Float |
Line total (with tax) |
qty_delivered |
Float |
Delivered quantity |
qty_invoiced |
Float |
Invoiced quantity |
API Examples
Create a Quotation
curl -X POST "https://your-odoo.com/restapi/1.0/object/sale.order" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"partner_id": 123,
"user_id": 5,
"commitment_date": "2025-12-20 10:00:00",
"note": "Customer prefers morning delivery"
}'
Add Order Lines
curl -X POST "https://your-odoo.com/restapi/1.0/object/sale.order.line" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"order_id": 456,
"product_id": 789,
"product_uom_qty": 2,
"price_unit": 150.00,
"name": "Motorized Blinds - Living Room"
}'
Get Orders by State
# Get confirmed sales orders
curl -X GET "https://your-odoo.com/restapi/1.0/object/sale.order" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('state','=','sale')]" \
--data-urlencode "fields=['name','partner_id','date_order','amount_total','state']" \
--data-urlencode "limit=100" \
--data-urlencode "order=date_order desc"
Get Order with Lines
# Get order details
curl -X GET "https://your-odoo.com/restapi/1.0/object/sale.order/456" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "fields=['name','partner_id','date_order','amount_total','amount_untaxed','amount_tax','state','commitment_date','note','user_id']"
# Get order lines
curl -X GET "https://your-odoo.com/restapi/1.0/object/sale.order.line" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('order_id','=',456)]" \
--data-urlencode "fields=['product_id','name','product_uom_qty','price_unit','price_subtotal','qty_delivered']"
Search Orders by Customer
curl -X GET "https://your-odoo.com/restapi/1.0/object/sale.order" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('partner_id','=',123)]" \
--data-urlencode "fields=['name','date_order','amount_total','state']" \
--data-urlencode "order=date_order desc"
Search by Date Range
# Orders from this month
curl -X GET "https://your-odoo.com/restapi/1.0/object/sale.order" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('state','=','sale'),('date_order','>=','2025-12-01 00:00:00'),('date_order','<=','2025-12-31 23:59:59')]" \
--data-urlencode "fields=['name','partner_id','date_order','amount_total']"
Update Order
curl -X PUT "https://your-odoo.com/restapi/1.0/object/sale.order/456" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"commitment_date": "2025-12-25 14:00:00",
"note": "Updated delivery date per customer request"
}'
Confirm Quotation (Change State)
# Note: For state changes, use Odoo methods via JSON-RPC
# REST API can update some fields but complex workflows
# may require action_confirm() method
curl -X PUT "https://your-odoo.com/restapi/1.0/object/sale.order/456" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"state": "sale"
}'
Customers (Partners)
curl -X GET "https://your-odoo.com/restapi/1.0/object/res.partner" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('customer_rank','>',0)]" \
--data-urlencode "fields=['name','email','phone','street','city']" \
--data-urlencode "limit=50"
Products
curl -X GET "https://your-odoo.com/restapi/1.0/object/product.product" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('sale_ok','=',True)]" \
--data-urlencode "fields=['name','default_code','list_price','qty_available']"
Salespersons
curl -X GET "https://your-odoo.com/restapi/1.0/object/res.users" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('share','=',False)]" \
--data-urlencode "fields=['name','email']"
Domain Filter Examples
# Confirmed orders only
[('state', '=', 'sale')]
# Draft quotations
[('state', '=', 'draft')]
# By customer
[('partner_id', '=', 123)]
# By salesperson
[('user_id', '=', 5)]
# This week's orders
[('date_order', '>=', '2025-12-09'), ('date_order', '<=', '2025-12-15')]
# High value orders
[('amount_total', '>=', 1000)]
# Pending invoice
[('invoice_status', '=', 'to invoice')]
# Orders with specific reference
[('name', 'ilike', 'S029')]
# Multiple states
[('state', 'in', ['draft', 'sent'])]
Python Client Example
import requests
from datetime import datetime, timedelta
class SalesOrderClient:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url.rstrip('/')
self.headers = {
'X-API-Key': api_key,
'Content-Type': 'application/json'
}
def create_quotation(self, partner_id: int, **kwargs) -> dict:
"""Create a new quotation."""
data = {'partner_id': partner_id, **kwargs}
response = requests.post(
f"{self.base_url}/restapi/1.0/object/sale.order",
headers=self.headers,
json=data
)
response.raise_for_status()
return response.json().get('sale.order', {})
def add_line(self, order_id: int, product_id: int, qty: float, price: float = None, name: str = None) -> dict:
"""Add a line to an order."""
data = {
'order_id': order_id,
'product_id': product_id,
'product_uom_qty': qty
}
if price:
data['price_unit'] = price
if name:
data['name'] = name
response = requests.post(
f"{self.base_url}/restapi/1.0/object/sale.order.line",
headers=self.headers,
json=data
)
response.raise_for_status()
return response.json().get('sale.order.line', {})
def get_orders_by_state(self, state: str, limit: int = 100) -> list:
"""Get orders by state."""
params = {
'domain': str([('state', '=', state)]),
'fields': "['name','partner_id','date_order','amount_total','state']",
'limit': limit,
'order': 'date_order desc'
}
response = requests.get(
f"{self.base_url}/restapi/1.0/object/sale.order",
headers=self.headers,
params=params
)
response.raise_for_status()
return response.json().get('sale.order', [])
def get_order_detail(self, order_id: int) -> dict:
"""Get full order details."""
params = {
'fields': "['name','partner_id','date_order','amount_total','amount_untaxed','amount_tax','state','commitment_date','note','user_id','invoice_status']"
}
response = requests.get(
f"{self.base_url}/restapi/1.0/object/sale.order/{order_id}",
headers=self.headers,
params=params
)
response.raise_for_status()
orders = response.json().get('sale.order', [])
return orders[0] if orders else None
def get_order_lines(self, order_id: int) -> list:
"""Get order lines."""
params = {
'domain': str([('order_id', '=', order_id)]),
'fields': "['product_id','name','product_uom_qty','price_unit','price_subtotal','qty_delivered']"
}
response = requests.get(
f"{self.base_url}/restapi/1.0/object/sale.order.line",
headers=self.headers,
params=params
)
response.raise_for_status()
return response.json().get('sale.order.line', [])
def get_sales_by_period(self, start_date: str, end_date: str) -> dict:
"""Get sales stats for a date range."""
domain = [
('state', '=', 'sale'),
('date_order', '>=', f'{start_date} 00:00:00'),
('date_order', '<=', f'{end_date} 23:59:59')
]
params = {
'domain': str(domain),
'fields': "['amount_total']",
'limit': 5000
}
response = requests.get(
f"{self.base_url}/restapi/1.0/object/sale.order",
headers=self.headers,
params=params
)
response.raise_for_status()
orders = response.json().get('sale.order', [])
return {
'count': len(orders),
'total_amount': sum(o.get('amount_total', 0) for o in orders)
}
# Usage
client = SalesOrderClient('https://your-odoo.com', 'your_api_key')
# Create quotation with lines
order = client.create_quotation(
partner_id=123,
commitment_date='2025-12-20 10:00:00'
)
print(f"Created order {order['name']}")
# Add products
client.add_line(order['id'], product_id=789, qty=2, price=150.00)
client.add_line(order['id'], product_id=790, qty=1, price=200.00)
# Get this month's sales
stats = client.get_sales_by_period('2025-12-01', '2025-12-31')
print(f"This month: {stats['count']} orders, ${stats['total_amount']:,.2f}")
Dashboard Stats Example
from datetime import datetime, timedelta
def get_sales_dashboard(client: SalesOrderClient) -> dict:
"""Get sales dashboard statistics."""
today = datetime.now().date()
# This week (Monday to today)
week_start = today - timedelta(days=today.weekday())
this_week = client.get_sales_by_period(
str(week_start), str(today)
)
# This month
month_start = today.replace(day=1)
this_month = client.get_sales_by_period(
str(month_start), str(today)
)
# Last month
last_month_end = month_start - timedelta(days=1)
last_month_start = last_month_end.replace(day=1)
last_month = client.get_sales_by_period(
str(last_month_start), str(last_month_end)
)
return {
'this_week': this_week,
'this_month': this_month,
'last_month': last_month
}
Notes
- Order lines require
order_id and product_id
- Price is auto-calculated from product if
price_unit not provided
- Use
commitment_date for delivery scheduling
- For complex workflows (confirm, invoice, deliver), consider using JSON-RPC to call Odoo methods directly