REST API authentication fails when posting journal entries through external integration

We’re integrating our expense management system with Odoo 14 to automatically post journal entries at month-end. The integration worked fine in our test environment, but production keeps returning 401 Unauthorized errors. I’ve verified the API key is correct and the user has Accounting/Adviser permissions. The strange part is that GET requests work fine - we can retrieve journal entries without issues. Only POST requests to /api/account.move fail. Here’s the error response we’re getting:

{
  "jsonrpc": "2.0",
  "id": null,
  "error": {
    "code": 401,
    "message": "Odoo Session Expired",
    "data": {
      "name": "odoo.exceptions.SessionExpiredException",
      "debug": "Traceback (most recent call last):\n  File /odoo/http.py, line 640, in _handle_exception\n    return super(JsonRequest, self)._handle_exception(exception)\n  File /odoo/http.py, line 684, in dispatch\n    result = self._call_function(**self.params) SessionExpiredException: Session expired"
    }
  }
}

Our month-end close is blocked because we can’t post these entries. Has anyone encountered this with external REST API integrations? What authentication method should we use for server-to-server communication?

I’ve seen this exact issue before. The problem is you’re probably mixing authentication methods. When you use /api/account.move endpoint, Odoo expects either session cookies OR API key authentication, not both. For external integrations, you need to use the JSON-RPC endpoint with proper authentication headers. Make sure you’re setting the Content-Type to application/json and including your API key in the request. Also verify that the user associated with the API key has ‘Technical Features’ enabled in addition to accounting permissions. The 401 error specifically indicates the authentication token isn’t being recognized for write operations. GET requests might work because they have different permission requirements. Check your Odoo logs for more details about what’s failing during the authentication process.

This is a common authentication pattern issue in Odoo. For external REST API integrations that need to POST data, you should abandon the /api endpoints and use the XML-RPC or JSON-RPC interfaces instead. These are designed for server-to-server communication and don’t rely on web sessions. The /api endpoints are really meant for interactive web applications that maintain session state. With XML-RPC, you authenticate once per request using the database name, username, and API key (not password). This is much more reliable for automated integrations. I’ve implemented dozens of these integrations and never had authentication issues with the RPC approach. The session expiration you’re seeing is because the /api endpoint expects browser-like behavior with cookie management.

Perfect! Switched to JSON-RPC authentication and it works flawlessly now. The key was understanding that /api endpoints are session-based while RPC methods are stateless. Month-end processing is back on track. Thanks for the detailed code example!

We had this exact scenario last quarter. Your authentication is failing because you’re using the wrong endpoint pattern for programmatic access. Here’s what you need to do:

  1. Stop using the /api/account.move endpoint - it’s session-based
  2. Use XML-RPC or JSON-RPC authentication instead
  3. For JSON-RPC, authenticate like this:
import json
import requests

url = "https://your-odoo.com/jsonrpc"
headers = {"Content-Type": "application/json"}

# Authenticate and get uid
auth_payload = {
    "jsonrpc": "2.0",
    "method": "call",
    "params": {
        "service": "common",
        "method": "authenticate",
        "args": ["database_name", "username", "api_key_here", {}]
    },
    "id": 1
}

auth_response = requests.post(url, json=auth_payload, headers=headers)
uid = auth_response.json()["result"]

# Now create journal entry
entry_payload = {
    "jsonrpc": "2.0",
    "method": "call",
    "params": {
        "service": "object",
        "method": "execute_kw",
        "args": [
            "database_name",
            uid,
            "api_key_here",
            "account.move",
            "create",
            [{
                "journal_id": 1,
                "date": "2025-03-31",
                "line_ids": [
                    (0, 0, {"account_id": 10, "debit": 1000}),
                    (0, 0, {"account_id": 20, "credit": 1000})
                ]
            }]
        ]
    },
    "id": 2
}

response = requests.post(url, json=entry_payload, headers=headers)

This approach doesn’t use sessions at all - each request is independently authenticated. Make sure the API key is generated from Settings > Users > API Keys (not the user password). The user needs ‘Show Full Accounting Features’ and ‘Technical Features’ enabled. We process 5000+ journal entries monthly through this pattern with zero authentication failures. The key difference is that JSON-RPC authenticate method returns a UID that you use for subsequent calls, rather than relying on session cookies that expire.

Double-check your nginx or Apache configuration if you’re behind a reverse proxy. Sometimes authentication headers get stripped at the proxy level and never reach Odoo.

Are you sending the db parameter in your requests? Odoo multi-tenant setups require explicit database specification even with valid credentials.

Check if you’re using session-based auth versus API key auth. Session tokens expire but API keys don’t. For server-to-server you should use database authentication with API keys in headers.