Skip to content

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