API Reference¶
The Netforce API uses JSON-RPC 2.0 over HTTP for all communication between the frontend and backend. This provides a clean, standardized interface for all operations.
JSON-RPC Overview¶
Request Format¶
{
"id": 1641234567890,
"method": "execute",
"params": [
"model.name",
"method_name",
[arg1, arg2, ...],
{"option1": "value1", "option2": "value2"},
{"user_id": 123, "token": "abc123", "company_id": 1}
]
}
Response Format¶
Error Response¶
{
"id": 1641234567890,
"result": null,
"error": {
"code": -32603,
"message": "Internal error",
"data": {
"type": "validation_error",
"details": "Field 'name' is required"
}
}
}
Authentication & Session Management¶
Login¶
Authenticate user and obtain session token:
// Request
{
"method": "login",
"params": {
"database": "mycompany_db",
"login": "admin",
"password": "password"
}
}
// Response
{
"result": {
"user_id": 1,
"token": "abc123def456",
"user_name": "Administrator",
"company_id": 1,
"companies": [
{"id": 1, "name": "My Company"}
]
}
}
Set Company Context¶
Switch active company for multi-company environments:
Logout¶
End user session:
Model Operations¶
Create Records¶
Create new records in the database:
// Single record
{
"method": "execute",
"params": [
"contact",
"create",
[{
"name": "John Doe",
"email": "john@example.com",
"is_customer": true
}],
{},
{"token": "abc123"}
]
}
// Multiple records
{
"method": "execute",
"params": [
"contact",
"create",
[
{"name": "Customer A", "is_customer": true},
{"name": "Customer B", "is_customer": true}
],
{},
{"token": "abc123"}
]
}
Read Records¶
Retrieve record data:
// Read specific records
{
"method": "execute",
"params": [
"contact",
"read",
[[1, 2, 3]], // Record IDs
["name", "email", "phone"], // Fields to fetch
{"token": "abc123"}
]
}
// Read with all fields
{
"method": "execute",
"params": [
"contact",
"read",
[[1, 2, 3]],
[], // Empty array = all fields
{"token": "abc123"}
]
}
Update Records¶
Update existing records:
{
"method": "execute",
"params": [
"contact",
"write",
[[1, 2, 3]], // Record IDs to update
{
"phone": "+1-555-0123",
"email": "newemail@example.com"
},
{"token": "abc123"}
]
}
Delete Records¶
Delete records from database:
{
"method": "execute",
"params": [
"contact",
"delete",
[[1, 2, 3]], // Record IDs to delete
{},
{"token": "abc123"}
]
}
Search Records¶
Find records matching criteria:
// Basic search
{
"method": "execute",
"params": [
"contact",
"search",
[
[["is_customer", "=", true]] // Search domain
],
{
"limit": 10,
"offset": 0,
"order": "name"
},
{"token": "abc123"}
]
}
// Complex search with multiple conditions
{
"method": "execute",
"params": [
"account.invoice",
"search",
[
[
["type", "=", "out"],
["state", "in", ["open", "paid"]],
["date_invoice", ">=", "2023-01-01"],
["amount_total", ">", 1000]
]
],
{"limit": 50, "order": "date_invoice desc"},
{"token": "abc123"}
]
}
Search and Read¶
Combine search and read operations:
{
"method": "execute",
"params": [
"contact",
"search_read",
[
[["is_customer", "=", true]] // Search domain
],
["name", "email", "phone"], // Fields to read
{
"limit": 10,
"offset": 0,
"order": "name"
},
{"token": "abc123"}
]
}
Search Domain Operators¶
Comparison Operators¶
| Operator | Description | Example |
|---|---|---|
= |
Equals | ["name", "=", "John"] |
!= |
Not equals | ["active", "!=", false] |
> |
Greater than | ["amount", ">", 100] |
>= |
Greater or equal | ["date", ">=", "2023-01-01"] |
< |
Less than | ["price", "<", 50] |
<= |
Less or equal | ["quantity", "<=", 10] |
String Operators¶
| Operator | Description | Example |
|---|---|---|
like |
SQL LIKE (case sensitive) | ["name", "like", "John%"] |
ilike |
SQL ILIKE (case insensitive) | ["name", "ilike", "%doe%"] |
=like |
Exact match with wildcards | ["code", "=like", "CUST_001"] |
=ilike |
Case insensitive exact match | ["email", "=ilike", "john@example.com"] |
Set Operators¶
| Operator | Description | Example |
|---|---|---|
in |
Value in list | ["state", "in", ["draft", "open"]] |
not in |
Value not in list | ["status", "not in", ["cancelled"]] |
Special Operators¶
| Operator | Description | Example |
|---|---|---|
child_of |
Hierarchical child | ["category_id", "child_of", 5] |
parent_of |
Hierarchical parent | ["category_id", "parent_of", 10] |
Logical Operators¶
Combine multiple conditions:
// AND (default)
[
["is_customer", "=", true],
["active", "=", true]
]
// OR
[
"|",
["email", "ilike", "%gmail%"],
["email", "ilike", "%yahoo%"]
]
// NOT
[
"!",
["state", "=", "cancelled"]
]
// Complex logic: (A AND B) OR (C AND D)
[
"|",
"&", ["is_customer", "=", true], ["active", "=", true],
"&", ["is_supplier", "=", true], ["approved", "=", true]
]
Field Information¶
Get Field Definitions¶
Retrieve model field information:
{
"method": "execute",
"params": [
"contact",
"fields_get",
[["name", "email", "phone"]], // Specific fields, or [] for all
{},
{"token": "abc123"}
]
}
// Response
{
"result": {
"name": {
"type": "char",
"string": "Name",
"required": true,
"size": 256
},
"email": {
"type": "char",
"string": "Email",
"size": 256
},
"phone": {
"type": "char",
"string": "Phone",
"size": 64
}
}
}
Get Selection Field Options¶
Get options for selection fields:
{
"method": "execute",
"params": [
"account.invoice",
"name_get_selection",
["state"], // Selection field name
{},
{"token": "abc123"}
]
}
// Response
{
"result": [
["draft", "Draft"],
["open", "Open"],
["paid", "Paid"],
["cancelled", "Cancelled"]
]
}
File Operations¶
Upload Files¶
Upload files to the system:
// First, upload file data
{
"method": "upload_file",
"params": {
"filename": "document.pdf",
"data": "base64_encoded_file_data",
"token": "abc123"
}
}
// Response contains file ID
{
"result": {
"file_id": "12345.pdf"
}
}
// Then create record with file reference
{
"method": "execute",
"params": [
"ir.attachment",
"create",
[{
"name": "Important Document",
"filename": "document.pdf",
"file": "12345.pdf",
"res_model": "contact",
"res_id": 1
}],
{},
{"token": "abc123"}
]
}
Download Files¶
Download files from the system:
{
"method": "download_file",
"params": {
"file_id": "12345.pdf",
"token": "abc123"
}
}
// Response
{
"result": {
"filename": "document.pdf",
"data": "base64_encoded_file_data",
"content_type": "application/pdf"
}
}
Batch Operations¶
Batch Multiple Operations¶
Execute multiple operations in a single request:
{
"method": "execute_batch",
"params": [
[
["contact", "create", [{"name": "Customer A"}]],
["contact", "create", [{"name": "Customer B"}]],
["product", "search", [[["active", "=", true]]]]
],
{"token": "abc123"}
]
}
// Response contains array of results
{
"result": [
1, // ID of created Customer A
2, // ID of created Customer B
[5, 6, 7, 8] // IDs of active products
]
}
Error Handling¶
Common Error Codes¶
| Code | Description |
|---|---|
-32700 |
Parse error |
-32600 |
Invalid request |
-32601 |
Method not found |
-32602 |
Invalid params |
-32603 |
Internal error |
1000 |
Authentication failed |
1001 |
Access denied |
1002 |
Validation error |
Error Response Examples¶
// Authentication error
{
"error": {
"code": 1000,
"message": "Authentication failed",
"data": {
"type": "auth_error"
}
}
}
// Validation error
{
"error": {
"code": 1002,
"message": "Validation failed",
"data": {
"type": "validation_error",
"field_errors": {
"email": "Invalid email format",
"name": "Name is required"
}
}
}
}
JavaScript Client Library¶
Basic Usage¶
import NetforceClient from './netforce-client.js';
const client = new NetforceClient('http://localhost:8080');
// Login
const loginResult = await client.login('admin', 'password');
console.log('User ID:', loginResult.user_id);
// Create record
const customerId = await client.create('contact', {
name: 'John Doe',
email: 'john@example.com'
});
// Search records
const customers = await client.searchRead('contact',
[['is_customer', '=', true]],
['name', 'email']
);
// Update record
await client.write('contact', [customerId], {
phone: '+1-555-0123'
});
Advanced Client Features¶
// Batch operations
const results = await client.batch([
['contact', 'create', [{'name': 'Customer A'}]],
['product', 'search', [[['active', '=', true]]]]
]);
// File upload
const fileId = await client.uploadFile('document.pdf', fileData);
// Get field info
const fields = await client.getFields('contact', ['name', 'email']);
// Execute custom method
const result = await client.execute('sale.order', 'custom_method',
[orderId], {param1: 'value1'}
);
Authentication¶
Login¶
rpc.execute("base.user", "authenticate", ["username", "password"], {}, (err, result) => {
if (err) {
console.error("Login failed:", err);
return;
}
// Store authentication data
utils.set_cookie("user_id", result.user_id);
utils.set_cookie("token", result.token);
utils.set_cookie("company_id", result.company_id);
});
Session Management¶
// Check if session is valid
rpc.execute("base.user", "check_session", [], {}, (err, result) => {
if (err || !result.valid) {
// Redirect to login
window.location = "/login";
}
});
// Logout
rpc.execute("base.user", "logout", [], {}, (err, result) => {
// Clear cookies and redirect
utils.clear_cookies();
window.location = "/login";
});
Core Model Methods¶
Create¶
Create new records.
// Single record
rpc.execute("account.invoice", "create", [{
"number": "INV001",
"date": "2023-01-15",
"customer_id": 123,
"lines": [
[0, 0, {"product_id": 1, "qty": 5, "price": 100.0}],
[0, 0, {"product_id": 2, "qty": 2, "price": 50.0}]
]
}], {}, callback);
// Multiple records
rpc.execute("product.product", "create", [
{"name": "Product A", "price": 100.0},
{"name": "Product B", "price": 200.0}
], {}, callback);
One2Many/Many2Many Operations¶
// One2Many operations in create/write
"lines": [
[0, 0, {values}], // Create new record
[1, id, {values}], // Update existing record
[2, id, false], // Delete record
[3, id, false], // Unlink record (don't delete)
[4, id, false], // Link existing record
[5, false, false], // Unlink all
[6, false, [ids]] // Replace with list of ids
]
Read¶
Retrieve records by ID.
// Basic read
rpc.execute("account.invoice", "read", [[1, 2, 3]], {
fields: ["number", "date", "customer_id", "total_amount"],
context: {}
}, (err, records) => {
records.forEach(record => {
console.log(record.number, record.total_amount);
});
});
// Read with related fields
rpc.execute("account.invoice", "read", [[1]], {
fields: ["number", "customer_id.name", "lines.product_id.name"]
}, callback);
Search¶
Find records matching criteria.
// Basic search
rpc.execute("account.invoice", "search", [
[["state", "=", "draft"], ["total_amount", ">", 1000]]
], {
limit: 50,
offset: 0,
order: "date desc"
}, (err, ids) => {
console.log("Found", ids.length, "invoices");
});
// Search with count
rpc.execute("account.invoice", "search", [
[["customer_id", "=", 123]]
], {
count: true
}, (err, count) => {
console.log("Customer has", count, "invoices");
});
Search Domain Syntax¶
// Basic operators
[["field", "=", "value"]] // Equals
[["field", "!=", "value"]] // Not equals
[["field", ">", 100]] // Greater than
[["field", ">=", 100]] // Greater or equal
[["field", "<", 100]] // Less than
[["field", "<=", 100]] // Less or equal
[["field", "in", [1, 2, 3]]] // In list
[["field", "not in", [1, 2, 3]]] // Not in list
[["field", "like", "%pattern%"]] // SQL LIKE
[["field", "ilike", "%pattern%"]] // Case-insensitive LIKE
// Logical operators
["and", condition1, condition2] // AND (default)
["or", condition1, condition2] // OR
["not", condition] // NOT
// Complex example
[
"or",
["and", ["state", "=", "draft"], ["amount", ">", 1000]],
["customer_id", "in", [1, 2, 3]]
]
Search and Read¶
Combined search and read operation.
rpc.execute("account.invoice", "search_read", [
[["state", "=", "draft"]], // domain
["number", "date", "customer_id", "total_amount"] // fields
], {
limit: 20,
order: "date desc"
}, (err, records) => {
records.forEach(record => {
console.log(record.number, record.total_amount);
});
});
Update¶
Update existing records.
// Update single record
rpc.execute("account.invoice", "write", [[123]], {
"state": "confirmed",
"date": "2023-01-20"
}, {}, callback);
// Update multiple records
rpc.execute("account.invoice", "write", [[1, 2, 3]], {
"state": "cancelled"
}, {}, callback);
// Update with One2Many changes
rpc.execute("account.invoice", "write", [[123]], {
"lines": [
[1, 456, {"qty": 10}], // Update existing line
[0, 0, {"product_id": 5, "qty": 2}], // Add new line
[2, 789, false] // Delete line
]
}, {}, callback);
Delete¶
Delete records.
// Delete records
rpc.execute("account.invoice", "delete", [[1, 2, 3]], {}, (err, result) => {
console.log("Deleted", result ? "successfully" : "failed");
});
Browse¶
Object-style record access (backend only - for understanding).
# Backend browse usage (for reference)
invoices = self.browse([1, 2, 3])
for inv in invoices:
print(inv.number, inv.customer_id.name, inv.total_amount)
for line in inv.lines:
print("-", line.product_id.name, line.qty, line.price)
Custom Model Methods¶
Calling Custom Methods¶
// Call custom method without parameters
rpc.execute("account.invoice", "confirm", [[123]], {}, callback);
// Call custom method with parameters
rpc.execute("account.invoice", "send_email", [[123]], {
"email_template": "invoice_template",
"recipients": ["customer@example.com"]
}, callback);
// Call static method (no record IDs)
rpc.execute("account.invoice", "get_next_number", [], {
"invoice_type": "customer"
}, callback);
Method Response Handling¶
rpc.execute("account.invoice", "calculate_taxes", [[123]], {}, (err, result) => {
if (err) {
console.error("Method failed:", err.message);
if (err.type === "validation_error") {
// Handle validation errors
alert("Validation error: " + err.details);
}
return;
}
// Handle successful response
console.log("Tax calculation result:", result);
});
Field Operations¶
Name Get¶
Get display names for records.
rpc.execute("contact", "name_get", [[1, 2, 3]], {}, (err, names) => {
names.forEach(([id, name]) => {
console.log("Contact", id, "is named", name);
});
});
Name Search¶
Search records by display name.
rpc.execute("product.product", "name_search", ["laptop"], {
limit: 10
}, (err, results) => {
results.forEach(([id, name]) => {
console.log("Found product:", id, name);
});
});
Default Get¶
Get default values for new records.
rpc.execute("account.invoice", "default_get", [
["customer_id", "date", "currency_id"]
], {
context: {"default_type": "out"}
}, (err, defaults) => {
console.log("Default values:", defaults);
// {"date": "2023-01-15", "currency_id": [1, "USD"]}
});
OnChange¶
Trigger field change handlers.
rpc.execute("account.invoice", "onchange", [{
"customer_id": 123,
"lines": []
}], {
"field": "customer_id",
"spec": {
"customer_id": {},
"payment_term_id": {},
"currency_id": {}
}
}, (err, result) => {
// Apply changes returned by onchange
Object.assign(record_data, result.value);
if (result.warning) {
alert(result.warning.message);
}
});
UI Metadata¶
Get Model Info¶
rpc.execute("ir.model", "get_model_info", ["account.invoice"], {}, (err, info) => {
console.log("Model info:", info);
// {
// "string": "Invoice",
// "fields": {...},
// "access": {"read": true, "write": true, ...}
// }
});
Get Field Info¶
rpc.execute("ir.model.fields", "get_field_info", [
"account.invoice", "customer_id"
], {}, (err, field_info) => {
console.log("Field info:", field_info);
// {
// "type": "many2one",
// "string": "Customer",
// "relation": "contact",
// "required": true
// }
});
Get Layouts¶
rpc.execute("ir.ui.view", "get_layout", ["account_invoice_form"], {}, (err, layout) => {
console.log("Form layout:", layout);
// Returns XML layout string
});
File Operations¶
Upload File¶
// File upload via form data
const formData = new FormData();
formData.append('file', file);
formData.append('model', 'account.invoice');
formData.append('field', 'attachment');
formData.append('id', record_id);
fetch('/file_upload', {
method: 'POST',
body: formData,
headers: {
'X-Database': get_database(),
'Authorization': 'Bearer ' + get_token()
}
}).then(response => response.json())
.then(result => {
console.log("File uploaded:", result.filename);
});
Download File¶
// Generate download URL
const download_url = `/file_download?model=account.invoice&field=attachment&id=${record_id}`;
window.open(download_url, '_blank');
Report Generation¶
Generate Report¶
rpc.execute("account.invoice", "render_report", [[123]], {
"template": "invoice_template",
"format": "pdf"
}, (err, result) => {
if (result.data) {
// result.data contains base64-encoded PDF
const blob = b64toBlob(result.data, 'application/pdf');
const url = URL.createObjectURL(blob);
window.open(url, '_blank');
}
});
Print Report¶
// Trigger print dialog
rpc.execute("report.invoice", "print_report", [[123]], {
"printer": "default"
}, callback);
Batch Operations¶
Batch Processing¶
// Process large datasets in batches
function process_all_invoices() {
let offset = 0;
const batch_size = 100;
function process_batch() {
rpc.execute("account.invoice", "search", [
[["state", "=", "draft"]]
], {
offset: offset,
limit: batch_size
}, (err, ids) => {
if (!ids.length) {
console.log("All invoices processed");
return;
}
// Process this batch
rpc.execute("account.invoice", "batch_confirm", [ids], {}, (err, result) => {
console.log("Processed batch:", offset, "-", offset + ids.length);
// Process next batch
offset += batch_size;
setTimeout(process_batch, 100); // Throttle requests
});
});
}
process_batch();
}
Bulk Operations¶
// Bulk update
rpc.execute("account.invoice", "write", [
[1, 2, 3, 4, 5] // Multiple IDs
], {
"state": "confirmed",
"confirmed_date": "2023-01-15"
}, {}, callback);
// Bulk delete
rpc.execute("account.invoice", "delete", [
[10, 11, 12, 13, 14]
], {}, callback);
Error Handling¶
Error Types¶
rpc.execute("model", "method", [], {}, (err, result) => {
if (err) {
switch (err.type) {
case "validation_error":
// Field validation failed
alert("Validation error: " + err.message);
break;
case "access_denied":
// Insufficient permissions
alert("Access denied: " + err.message);
break;
case "missing_required":
// Required field not provided
highlight_required_fields(err.fields);
break;
case "constraint_violation":
// Database constraint failed
alert("Constraint error: " + err.message);
break;
case "not_found":
// Record not found
alert("Record not found");
break;
default:
// Generic error
console.error("API error:", err);
alert("An error occurred: " + err.message);
}
}
});
Retry Logic¶
function rpc_with_retry(model, method, args, opts, callback, max_retries = 3) {
let attempts = 0;
function attempt() {
attempts++;
rpc.execute(model, method, args, opts, (err, result) => {
if (err && attempts < max_retries && is_retryable_error(err)) {
console.log(`Retry attempt ${attempts}/${max_retries}`);
setTimeout(attempt, 1000 * attempts); // Exponential backoff
} else {
callback(err, result);
}
});
}
attempt();
}
function is_retryable_error(err) {
return err.code === -32603 || // Internal error
err.message.includes("timeout") ||
err.message.includes("connection");
}
Advanced Usage¶
Context Usage¶
// Pass context for business logic
rpc.execute("sale.order", "create", [order_data], {
context: {
"default_warehouse_id": 1,
"skip_price_calculation": true,
"user_timezone": "America/New_York"
}
}, callback);
Transaction Handling¶
// All operations in single transaction (backend handles this)
rpc.execute("account.invoice", "process_payment", [[123]], {
"payment_amount": 1000.0,
"payment_method": "bank_transfer",
"bank_account_id": 5
}, (err, result) => {
// If this fails, all changes are rolled back automatically
});
Concurrent Requests¶
// Handle multiple concurrent requests
Promise.all([
new Promise(resolve => rpc.execute("model1", "method1", [], {}, resolve)),
new Promise(resolve => rpc.execute("model2", "method2", [], {}, resolve)),
new Promise(resolve => rpc.execute("model3", "method3", [], {}, resolve))
]).then(results => {
console.log("All requests completed:", results);
});
Best Practices¶
1. Error Handling¶
- Always handle errors gracefully
- Provide meaningful error messages to users
- Log errors for debugging
- Implement retry logic for transient failures
2. Performance¶
- Use
search_readinstead of separatesearch+read - Limit field lists to only what you need
- Use pagination for large datasets
- Batch operations when possible
3. Security¶
- Validate all user input before sending to API
- Use HTTPS in production
- Implement proper session management
- Handle authentication errors properly
4. User Experience¶
- Show loading indicators during API calls
- Provide progress feedback for long operations
- Cache frequently accessed data
- Handle offline scenarios gracefully
Next Steps¶
- Review Models to understand backend implementation
- Check Frontend for React component integration
- Explore Layouts for UI metadata
- Try the Quick Start Tutorial to practice API usage