Skip to content

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

{
    "id": 1641234567890,
    "result": {
        // Response data
    },
    "error": null
}

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:

// Request
{
    "method": "set_company",
    "params": {
        "company_id": 2,
        "token": "abc123def456"
    }
}

Logout

End user session:

// Request  
{
    "method": "logout",
    "params": {
        "token": "abc123def456"
    }
}

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);

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);
    });
});

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');
    }
});
// 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_read instead of separate search + 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