Skip to content

Helpdesk Ticket API

This guide covers how to manage helpdesk tickets via the REST API using Odoo's XML-RPC/JSON-RPC interface.

Overview

The REST API module provides generic CRUD endpoints for helpdesk.ticket model:

Operation HTTP Method Endpoint
Create POST /restapi/1.0/object/helpdesk.ticket
List/Search GET /restapi/1.0/object/helpdesk.ticket
Read GET /restapi/1.0/object/helpdesk.ticket/{id}
Update PUT /restapi/1.0/object/helpdesk.ticket/{id}
Delete DELETE /restapi/1.0/object/helpdesk.ticket/{id}

Authentication

All API requests require authentication. Supported methods:

# Header format
X-API-Key: your_api_key_here
# OR
Authorization: ApiKey your_api_key_here

Bearer Token (OAuth2)

Authorization: Bearer your_access_token

Generate API Key

  1. Go to Settings > Technical > REST API > Authentications
  2. Create new authentication record
  3. Click "Generate API Key"
  4. Copy and store the key securely

Ticket Fields Reference

Core Fields

Field Type Required Description
name String Yes Ticket subject
description HTML No Detailed description
team_id Integer No Helpdesk team ID
partner_id Integer No Customer (res.partner) ID
partner_name String No Customer name
partner_email String No Customer email
partner_phone String No Customer phone
user_id Integer No Assigned user ID
stage_id Integer No Stage ID
ticket_type_id Integer No Ticket type ID
tag_ids List[Int] No Tag IDs
priority String No Priority: '0', '1', '2', '3'
active Boolean No Active status (default: true)

Priority Values

Value Label
0 All (Default)
1 Low Priority
2 High Priority
3 Urgent

Read-Only Fields

Field Type Description
create_date Datetime Creation timestamp
write_date Datetime Last update timestamp
assign_date Datetime First assignment date
close_date Datetime Ticket close date
assign_hours Integer Hours to first assignment
close_hours Integer Hours to close
sla_deadline Datetime SLA deadline
sla_fail Boolean SLA failed status

Important: Request Format

The REST API expects form data with a vals parameter, NOT a JSON body:

# Correct format - form data with vals parameter
curl -X POST "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket" \
  -H "X-API-Key: your_api_key" \
  -d "vals={\"name\": \"Test Ticket\", \"team_id\": 1}"

# Incorrect - JSON body will cause "type http vs json" error
curl -X POST "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket" \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "Test Ticket"}'

In Python:

import json

# Correct
requests.post(url, headers={'X-API-Key': api_key}, data={'vals': json.dumps(values)})

# Incorrect
requests.post(url, headers={'X-API-Key': api_key}, json=values)


API Examples

Create a Ticket

curl -X POST "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket" \
  -H "X-API-Key: your_api_key" \
  -d 'vals={"name": "Installation Issue - Blinds not fitting", "description": "<p>Customer reports blinds are 2cm too wide for window frame.</p>", "partner_id": 123, "priority": "2", "team_id": 1}'

Response (201 Created):

{
  "helpdesk.ticket": {
    "id": 456,
    "name": "Installation Issue - Blinds not fitting",
    "description": "<p>Customer reports blinds are 2cm too wide for window frame.</p>",
    "partner_id": [123, "John Smith"],
    "priority": "2",
    "team_id": [1, "Support"],
    "stage_id": [1, "New"],
    "user_id": [5, "Support Agent"],
    "create_date": "2025-12-13 10:30:00"
  }
}

Search Tickets

# Get all urgent tickets
curl -X GET "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "domain=[('priority','=','3')]" \
  --data-urlencode "fields=['name','partner_id','stage_id','create_date']" \
  --data-urlencode "limit=20" \
  --data-urlencode "order=create_date desc"

Response (200 OK):

{
  "helpdesk.ticket": [
    {
      "id": 456,
      "name": "Installation Issue - Blinds not fitting",
      "partner_id": [123, "John Smith"],
      "stage_id": [1, "New"],
      "create_date": "2025-12-13 10:30:00"
    },
    {
      "id": 455,
      "name": "Urgent: Motor malfunction",
      "partner_id": [124, "Jane Doe"],
      "stage_id": [2, "In Progress"],
      "create_date": "2025-12-12 15:00:00"
    }
  ]
}

Get Single Ticket

curl -X GET "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket/456" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "fields=['name','description','partner_id','stage_id','user_id','priority','sla_deadline']"

Update a Ticket

# Change stage and priority
curl -X PUT "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket/456" \
  -H "X-API-Key: your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "stage_id": 3,
    "priority": "3",
    "user_id": 10
  }'

Delete a Ticket

curl -X DELETE "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket/456" \
  -H "X-API-Key: your_api_key"

Search by Customer

# Find all tickets for a specific customer
curl -X GET "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "domain=[('partner_id','=',123)]" \
  --data-urlencode "fields=['name','stage_id','priority','create_date']" \
  --data-urlencode "order=create_date desc"

Search Open Tickets

# Find tickets not in closed stage
curl -X GET "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "domain=[('stage_id.is_close','=',False)]" \
  --data-urlencode "fields=['name','stage_id','user_id','sla_deadline']"

Helpdesk Team (helpdesk.team)

# List teams
curl -X GET "https://your-odoo.com/restapi/1.0/object/helpdesk.team" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "fields=['name','use_sla']"

Helpdesk Stage (helpdesk.stage)

# List stages for a team
curl -X GET "https://your-odoo.com/restapi/1.0/object/helpdesk.stage" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "domain=[('team_ids','in',[1])]" \
  --data-urlencode "fields=['name','sequence','is_close']"

Ticket Types (helpdesk.ticket.type)

curl -X GET "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket.type" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "fields=['name','sequence']"

Tags (helpdesk.tag)

curl -X GET "https://your-odoo.com/restapi/1.0/object/helpdesk.tag" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "fields=['name','color']"

Domain Filter Examples

Common Filters

# Urgent tickets
[('priority', '=', '3')]

# Open tickets (not closed)
[('stage_id.is_close', '=', False)]

# Assigned to specific user
[('user_id', '=', 10)]

# Unassigned tickets
[('user_id', '=', False)]

# Created today
[('create_date', '>=', '2025-12-13 00:00:00')]

# SLA failed
[('sla_fail', '=', True)]

# Customer email contains domain
[('partner_email', 'ilike', '@company.com')]

# Multiple conditions (AND)
[('priority', '=', '3'), ('stage_id.is_close', '=', False)]

# OR conditions
['|', ('priority', '=', '3'), ('priority', '=', '2')]

Error Handling

Common Error Responses

401 Unauthorized:

{
  "error": {
    "code": 401,
    "message": "Authentication required"
  }
}

404 Not Found:

{
  "error": {
    "code": 404,
    "message": "Record not found"
  }
}

400 Bad Request:

{
  "error": {
    "code": 400,
    "message": "Invalid domain format"
  }
}

Rate Limiting

The API does not enforce rate limiting by default. However, consider implementing client-side throttling for high-volume integrations to avoid overloading the server.

Creating Tickets with Partner & Address

When creating tickets from external forms (like the landing page), you often need to: 1. Find or create a partner with address 2. Create the ticket linked to the partner

Step 1: Look Up State ID

Convert state code (e.g., "TX") to Odoo state ID:

curl -X GET "https://your-odoo.com/restapi/1.0/object/res.country.state" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "domain=[('code','=','TX'),('country_id.code','=','US')]" \
  --data-urlencode "fields=['id','name']" \
  --data-urlencode "limit=1"

Response: {"res.country.state": {"id": 52, "name": "Texas"}}

Step 2: Find or Create Partner

# Search for existing partner by email
curl -X GET "https://your-odoo.com/restapi/1.0/object/res.partner" \
  -H "X-API-Key: your_api_key" \
  -G \
  --data-urlencode "domain=[('email','=','john@example.com')]" \
  --data-urlencode "fields=['id','name','phone','street','city']" \
  --data-urlencode "limit=1"

# If not found, create new partner with address
curl -X POST "https://your-odoo.com/restapi/1.0/object/res.partner" \
  -H "X-API-Key: your_api_key" \
  -d 'vals={"name": "John Smith", "email": "john@example.com", "phone": "512-555-1234", "street": "123 Main St", "city": "Austin", "state_id": 52, "zip": "78701", "country_id": 233}'

Step 3: Create Ticket with Partner

curl -X POST "https://your-odoo.com/restapi/1.0/object/helpdesk.ticket" \
  -H "X-API-Key: your_api_key" \
  -d 'vals={"name": "Issue with blinds", "partner_id": 60, "partner_name": "John Smith", "partner_email": "john@example.com", "partner_phone": "512-555-1234", "description": "<p>Blinds not responding to remote</p>", "team_id": 1}'

Complete Flow (Python)

import json
import requests

def create_ticket_with_partner(api_url, api_key, form_data):
    """Create a helpdesk ticket with partner from form submission."""
    headers = {'X-API-Key': api_key}

    # 1. Look up state ID
    state_id = None
    if form_data.get('state_code'):
        response = requests.get(
            f"{api_url}/restapi/1.0/object/res.country.state",
            headers=headers,
            params={
                'domain': json.dumps([
                    ('code', '=', form_data['state_code']),
                    ('country_id.code', '=', 'US')
                ]),
                'fields': json.dumps(['id']),
                'limit': 1
            }
        )
        data = response.json().get('res.country.state', {})
        state_id = data.get('id') if isinstance(data, dict) else data[0].get('id') if data else None

    # 2. Find or create partner
    response = requests.get(
        f"{api_url}/restapi/1.0/object/res.partner",
        headers=headers,
        params={
            'domain': json.dumps([('email', '=', form_data['email'])]),
            'fields': json.dumps(['id']),
            'limit': 1
        }
    )
    partner_data = response.json().get('res.partner', {})

    if partner_data and partner_data.get('id'):
        partner_id = partner_data['id']
    else:
        # Create new partner
        partner_vals = {
            'name': form_data['name'],
            'email': form_data['email'],
            'phone': form_data.get('phone', ''),
            'street': form_data.get('street', ''),
            'city': form_data.get('city', ''),
            'zip': form_data.get('zip', ''),
        }
        if state_id:
            partner_vals['state_id'] = state_id
            partner_vals['country_id'] = 233  # US

        response = requests.post(
            f"{api_url}/restapi/1.0/object/res.partner",
            headers=headers,
            data={'vals': json.dumps(partner_vals)}
        )
        partner_id = response.json().get('res.partner', {}).get('id')

    # 3. Create ticket
    ticket_vals = {
        'name': form_data['subject'],
        'partner_id': partner_id,
        'partner_name': form_data['name'],
        'partner_email': form_data['email'],
        'partner_phone': form_data.get('phone', ''),
        'description': form_data.get('description', ''),
        'team_id': 1,
    }

    response = requests.post(
        f"{api_url}/restapi/1.0/object/helpdesk.ticket",
        headers=headers,
        data={'vals': json.dumps(ticket_vals)}
    )
    return response.json().get('helpdesk.ticket', {})

Next Steps