All posts
Developer9 min read

Making External HTTP API Calls from Odoo Models

Call third-party REST APIs directly from Odoo Python models using the requests library - handle authentication headers, JSON parsing, error handling, timeout configuration, and safe retry patterns without blocking the main thread.

Making External HTTP API Calls from Odoo Models#

Why Call External APIs from Odoo?#

Odoo models sometimes need to push or pull data from external systems: shipping carriers, payment gateways, ERP connectors, weather APIs, or custom SaaS backends. Odoo ships with the requests library available in its Python environment.

Basic HTTP GET#

python
import requests
from odoo import models

class ShipmentOrder(models.Model):
    _name = 'shipment.order'

    def fetch_tracking_status(self):
        api_key = self.env['ir.config_parameter'].sudo().get_param(
            'my_module.carrier_api_key'
        )
        url = f"https://api.carrier.com/track/{self.tracking_number}"
        response = requests.get(
            url,
            headers={'Authorization': f'Bearer {api_key}'},
            timeout=10,
        )
        response.raise_for_status()   # raises HTTPError for 4xx/5xx
        data = response.json()
        self.tracking_status = data.get('status', 'unknown')

Basic HTTP POST with JSON Body#

python
def create_remote_invoice(self):
    payload = {
        'partner_id': self.partner_id.name,
        'amount': float(self.amount_total),
        'currency': self.currency_id.name,
    }
    response = requests.post(
        'https://api.billing.com/invoices',
        json=payload,
        headers={'X-Api-Key': self._get_api_key()},
        timeout=15,
    )
    response.raise_for_status()
    return response.json()

Storing API Keys Securely#

Never hardcode API keys in Python. Use ir.config_parameter:

python
# Read key
key = self.env['ir.config_parameter'].sudo().get_param('my_module.api_key')

# Write key (e.g. from a wizard)
self.env['ir.config_parameter'].sudo().set_param('my_module.api_key', value)

Expose the setting in Settings → Technical → Parameters → System Parameters, or add a settings form section:

xml
<record id="action_my_module_settings" model="ir.actions.act_window">
    ...
</record>

Error Handling#

python
from requests.exceptions import RequestException, Timeout, HTTPError
from odoo.exceptions import UserError

def sync_with_external(self):
    try:
        response = requests.post(
            self._endpoint_url(),
            json=self._build_payload(),
            timeout=20,
        )
        response.raise_for_status()
    except Timeout:
        raise UserError("External API timed out. Try again later.")
    except HTTPError as e:
        raise UserError(f"API error {e.response.status_code}: {e.response.text[:200]}")
    except RequestException as e:
        raise UserError(f"Network error: {e}")

Avoiding Blocking the Main Thread#

Long-running API calls block the Odoo worker process and slow down all users sharing that worker. Use these strategies:

1. Async via ir.cron (polling pattern):

Schedule a cron job that processes a queue table instead of calling the API in the user's request:

python
class ApiCallQueue(models.Model):
    _name = 'api.call.queue'
    _description = 'Async API Call Queue'

    state = fields.Selection([('pending', 'Pending'), ('done', 'Done'), ('error', 'Error')])
    payload = fields.Json()
    response = fields.Json()
    error_message = fields.Char()

    def action_process_queue(self):
        pending = self.search([('state', '=', 'pending')], limit=50)
        for rec in pending:
            try:
                resp = requests.post(rec._endpoint(), json=rec.payload, timeout=15)
                resp.raise_for_status()
                rec.write({'state': 'done', 'response': resp.json()})
            except Exception as e:
                rec.write({'state': 'error', 'error_message': str(e)})

2. Webhook receiver (push pattern):

Register a controller to receive API callbacks instead of polling:

python
from odoo import http
from odoo.http import request

class WebhookController(http.Controller):
    @http.route('/webhook/carrier/tracking', type='json', auth='none', csrf=False)
    def carrier_tracking_webhook(self, **kwargs):
        data = request.get_json_data()
        token = request.httprequest.headers.get('X-Webhook-Token', '')
        expected = request.env['ir.config_parameter'].sudo().get_param(
            'my_module.webhook_secret'
        )
        if token != expected:
            return {'status': 'unauthorized'}
        # Process the webhook payload
        tracking_no = data.get('tracking_number')
        order = request.env['shipment.order'].sudo().search(
            [('tracking_number', '=', tracking_no)], limit=1
        )
        if order:
            order.tracking_status = data.get('status')
        return {'status': 'ok'}

Session Reuse for Performance#

If you make many calls to the same host, reuse a requests.Session:

python
class ExternalSync(models.AbstractModel):
    _name = 'external.sync.mixin'

    def _get_session(self):
        session = requests.Session()
        session.headers.update({'Authorization': f'Bearer {self._get_api_key()}'})
        session.mount('https://', requests.adapters.HTTPAdapter(max_retries=2))
        return session

    def bulk_sync(self, records):
        with self._get_session() as session:
            for rec in records:
                resp = session.post(self._endpoint(), json=rec._payload(), timeout=10)
                resp.raise_for_status()

Common Mistakes#

  • No timeout - requests.get(url) with no timeout can hang indefinitely, consuming a worker slot
  • Storing API keys in Python - use ir.config_parameter so keys are configurable without code changes
  • Calling external APIs on record write - triggers block the save transaction; use a queue or cron instead
  • Ignoring HTTP errors - always call response.raise_for_status() and catch HTTPError
  • Logging full response bodies - responses may contain PII or secrets; log only status codes and sanitized metadata

Try ERPeek on your own Odoo module - ask questions, scaffold tests, and explore your codebase in plain language.

Get started free