Field Service (FSM) Jobs API
Manage field service orders (installation/service jobs) via the REST API.
Overview
| Operation |
Method |
Endpoint |
| Create |
POST |
/restapi/1.0/object/fsm.order |
| List/Search |
GET |
/restapi/1.0/object/fsm.order |
| Read |
GET |
/restapi/1.0/object/fsm.order/{id} |
| Update |
PUT |
/restapi/1.0/object/fsm.order/{id} |
| Delete |
DELETE |
/restapi/1.0/object/fsm.order/{id} |
Authentication
X-API-Key: your_api_key_here
FSM Order Fields
Core Fields
| Field |
Type |
Required |
Description |
name |
String |
Auto |
Order reference (FSM00001) |
partner_id |
Integer |
Yes |
Customer ID |
location_id |
Integer |
No |
Service location ID |
sale_order_id |
Integer |
No |
Linked sales order |
scheduled_date_start |
Datetime |
No |
Scheduled start |
scheduled_date_end |
Datetime |
No |
Scheduled end |
date_start |
Datetime |
No |
Actual start |
date_end |
Datetime |
No |
Actual end |
stage_id |
Integer |
No |
Workflow stage |
person_id |
Integer |
No |
Assigned technician |
team_id |
Integer |
No |
Service team |
priority |
String |
No |
Priority level |
description |
Text |
No |
Job description |
Job Type Fields
| Field |
Type |
Description |
type |
String |
Job type (custom field) |
fsm_type |
String |
FSM type identifier |
Address Fields (from Location)
| Field |
Type |
Description |
street |
String |
Street address |
city |
String |
City |
state_id |
Integer |
State ID |
zip |
String |
ZIP code |
Computed Fields
| Field |
Type |
Description |
duration |
Float |
Planned duration (hours) |
effective_duration |
Float |
Actual duration (hours) |
FSM Stages (JDX Workflow)
| Stage |
Description |
| New |
New job created |
| Scheduled |
Job scheduled |
| In Progress |
Work started |
| Completed |
Work finished |
| Cancelled |
Job cancelled |
API Examples
Create a Job
curl -X POST "https://your-odoo.com/restapi/1.0/object/fsm.order" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"partner_id": 123,
"sale_order_id": 456,
"scheduled_date_start": "2025-12-15 09:00:00",
"scheduled_date_end": "2025-12-15 12:00:00",
"person_id": 10,
"description": "Install motorized blinds - living room and bedroom"
}'
Get Jobs by Stage
# Get all jobs in "Scheduled" stage
curl -X GET "https://your-odoo.com/restapi/1.0/object/fsm.order" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('stage_id','=',2)]" \
--data-urlencode "fields=['name','partner_id','scheduled_date_start','person_id','location_id']" \
--data-urlencode "limit=50" \
--data-urlencode "order=scheduled_date_start"
Get Today's Jobs
curl -X GET "https://your-odoo.com/restapi/1.0/object/fsm.order" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('scheduled_date_start','>=','2025-12-13 00:00:00'),('scheduled_date_start','<=','2025-12-13 23:59:59')]" \
--data-urlencode "fields=['name','partner_id','scheduled_date_start','stage_id','person_id']" \
--data-urlencode "order=scheduled_date_start"
Get Jobs by Technician
curl -X GET "https://your-odoo.com/restapi/1.0/object/fsm.order" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('person_id','=',10),('stage_id','not in',[5])]" \
--data-urlencode "fields=['name','partner_id','scheduled_date_start','stage_id','location_id']" \
--data-urlencode "order=scheduled_date_start"
Get Job Detail
curl -X GET "https://your-odoo.com/restapi/1.0/object/fsm.order/789" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "fields=['name','partner_id','location_id','sale_order_id','scheduled_date_start','scheduled_date_end','date_start','date_end','stage_id','person_id','description','duration']"
Update Job Stage
# Move job to "In Progress"
curl -X PUT "https://your-odoo.com/restapi/1.0/object/fsm.order/789" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"stage_id": 3,
"date_start": "2025-12-13 09:15:00"
}'
Complete a Job
curl -X PUT "https://your-odoo.com/restapi/1.0/object/fsm.order/789" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"stage_id": 4,
"date_end": "2025-12-13 11:30:00"
}'
Reschedule Job
curl -X PUT "https://your-odoo.com/restapi/1.0/object/fsm.order/789" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"scheduled_date_start": "2025-12-16 14:00:00",
"scheduled_date_end": "2025-12-16 17:00:00"
}'
Reassign Technician
curl -X PUT "https://your-odoo.com/restapi/1.0/object/fsm.order/789" \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"person_id": 15
}'
FSM Stages
curl -X GET "https://your-odoo.com/restapi/1.0/object/fsm.stage" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "fields=['name','sequence','is_closed']" \
--data-urlencode "order=sequence"
Service Locations
curl -X GET "https://your-odoo.com/restapi/1.0/object/fsm.location" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "domain=[('partner_id','=',123)]" \
--data-urlencode "fields=['name','street','city','state_id','zip']"
Technicians (FSM Persons)
curl -X GET "https://your-odoo.com/restapi/1.0/object/fsm.person" \
-H "X-API-Key: your_api_key" \
-G \
--data-urlencode "fields=['name','partner_id','user_id']"
Linked Sale Order
# Get sale order linked to FSM job
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','amount_total','order_line']"
Domain Filter Examples
# Active jobs (not cancelled/completed)
[('stage_id', 'not in', [4, 5])]
# Today's jobs
[('scheduled_date_start', '>=', '2025-12-13 00:00:00'),
('scheduled_date_start', '<=', '2025-12-13 23:59:59')]
# This week's jobs
[('scheduled_date_start', '>=', '2025-12-09'),
('scheduled_date_start', '<=', '2025-12-15')]
# By technician
[('person_id', '=', 10)]
# Unassigned jobs
[('person_id', '=', False)]
# By customer
[('partner_id', '=', 123)]
# Installation jobs (custom type)
[('type', '=', 'install')]
# Service jobs (custom type)
[('type', '=', 'service')]
# Overdue jobs
[('scheduled_date_start', '<', '2025-12-13'),
('stage_id', 'not in', [4, 5])]
# Linked to specific sale order
[('sale_order_id', '=', 456)]
Python Client Example
import requests
from datetime import datetime, timedelta
class FSMJobClient:
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_job(
self,
partner_id: int,
scheduled_start: str,
scheduled_end: str = None,
sale_order_id: int = None,
person_id: int = None,
description: str = None
) -> dict:
"""Create a new FSM job."""
data = {
'partner_id': partner_id,
'scheduled_date_start': scheduled_start
}
if scheduled_end:
data['scheduled_date_end'] = scheduled_end
if sale_order_id:
data['sale_order_id'] = sale_order_id
if person_id:
data['person_id'] = person_id
if description:
data['description'] = description
response = requests.post(
f"{self.base_url}/restapi/1.0/object/fsm.order",
headers=self.headers,
json=data
)
response.raise_for_status()
return response.json().get('fsm.order', {})
def get_jobs_by_date(self, date: str) -> list:
"""Get all jobs for a specific date."""
domain = [
('scheduled_date_start', '>=', f'{date} 00:00:00'),
('scheduled_date_start', '<=', f'{date} 23:59:59')
]
params = {
'domain': str(domain),
'fields': "['name','partner_id','scheduled_date_start','stage_id','person_id','location_id']",
'order': 'scheduled_date_start'
}
response = requests.get(
f"{self.base_url}/restapi/1.0/object/fsm.order",
headers=self.headers,
params=params
)
response.raise_for_status()
return response.json().get('fsm.order', [])
def get_technician_jobs(self, person_id: int, include_completed: bool = False) -> list:
"""Get jobs assigned to a technician."""
domain = [('person_id', '=', person_id)]
if not include_completed:
domain.append(('stage_id', 'not in', [4, 5]))
params = {
'domain': str(domain),
'fields': "['name','partner_id','scheduled_date_start','stage_id','location_id']",
'order': 'scheduled_date_start'
}
response = requests.get(
f"{self.base_url}/restapi/1.0/object/fsm.order",
headers=self.headers,
params=params
)
response.raise_for_status()
return response.json().get('fsm.order', [])
def update_stage(self, job_id: int, stage_id: int, **kwargs) -> dict:
"""Update job stage with optional timestamp."""
data = {'stage_id': stage_id, **kwargs}
response = requests.put(
f"{self.base_url}/restapi/1.0/object/fsm.order/{job_id}",
headers=self.headers,
json=data
)
response.raise_for_status()
return response.json().get('fsm.order', {})
def start_job(self, job_id: int) -> dict:
"""Start a job (set to In Progress)."""
return self.update_stage(
job_id,
stage_id=3,
date_start=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
)
def complete_job(self, job_id: int) -> dict:
"""Complete a job."""
return self.update_stage(
job_id,
stage_id=4,
date_end=datetime.now().strftime('%Y-%m-%d %H:%M:%S')
)
def reschedule_job(self, job_id: int, new_start: str, new_end: str = None) -> dict:
"""Reschedule a job to a new date/time."""
data = {'scheduled_date_start': new_start}
if new_end:
data['scheduled_date_end'] = new_end
response = requests.put(
f"{self.base_url}/restapi/1.0/object/fsm.order/{job_id}",
headers=self.headers,
json=data
)
response.raise_for_status()
return response.json().get('fsm.order', {})
def get_stages(self) -> list:
"""Get all FSM stages."""
params = {
'fields': "['name','sequence','is_closed']",
'order': 'sequence'
}
response = requests.get(
f"{self.base_url}/restapi/1.0/object/fsm.stage",
headers=self.headers,
params=params
)
response.raise_for_status()
return response.json().get('fsm.stage', [])
# Usage
client = FSMJobClient('https://your-odoo.com', 'your_api_key')
# Create installation job
job = client.create_job(
partner_id=123,
scheduled_start='2025-12-15 09:00:00',
scheduled_end='2025-12-15 12:00:00',
sale_order_id=456,
person_id=10,
description='Install motorized blinds'
)
print(f"Created job {job['name']}")
# Get today's schedule
today = datetime.now().strftime('%Y-%m-%d')
jobs = client.get_jobs_by_date(today)
print(f"Today: {len(jobs)} jobs scheduled")
# Start a job
client.start_job(job['id'])
# Complete the job
client.complete_job(job['id'])
Dashboard Stats Example
def get_fsm_dashboard(client: FSMJobClient) -> dict:
"""Get FSM dashboard statistics."""
today = datetime.now().strftime('%Y-%m-%d')
# Count by stage
stages = client.get_stages()
stage_counts = {}
for stage in stages:
params = {
'domain': str([('stage_id', '=', stage['id'])]),
'fields': "['id']"
}
response = requests.get(
f"{client.base_url}/restapi/1.0/object/fsm.order",
headers=client.headers,
params=params
)
jobs = response.json().get('fsm.order', [])
stage_counts[stage['name']] = len(jobs)
# Today's jobs
today_jobs = client.get_jobs_by_date(today)
return {
'stage_counts': stage_counts,
'today_count': len(today_jobs),
'today_jobs': today_jobs
}
Notes
- Jobs are typically created from sale orders when using MTO (Make to Order) workflow
scheduled_date_start and scheduled_date_end define planned schedule
date_start and date_end record actual work times
- Use
person_id to assign technicians
location_id links to customer's service address (fsm.location)
- Stage changes trigger workflow automation (activities, notifications)