--- myst: html_meta: "title": "External API using JSON-RPC" "description": "Complete guide to interacting with OpenSPP Registry via JSON-RPC API including authentication, CRUD operations, and Python examples." "keywords": "OpenSPP, JSON-RPC, external API, registry API, CRUD operations, authentication, Python integration" --- # External API using JSON-RPC This guide explains how to connect to and interact with OpenSPP Registry using the JSON-RPC API, with practical Python examples for adding individuals, groups, and memberships. ## Prerequisites - OpenSPP server running and accessible. - User credentials or API key with appropriate permissions. - Python 3.x and the `requests` library installed (`pip install requests`). ## Process ### Understanding JSON-RPC in OpenSPP OpenSPP exposes much of its data and functionality via JSON-RPC endpoints. You can use these endpoints to authenticate, read, create, update, and delete records from external applications. **Endpoint:** - `/jsonrpc` — All JSON-RPC calls (authentication and model methods) ### JSON-RPC payload structure and methods All interactions with the OpenSPP JSON-RPC API use a standard payload structure. Each request is a JSON object with the following keys: - `jsonrpc`: The JSON-RPC protocol version (always `"2.0"`). - `method`: The JSON-RPC method (always `"call"` for OpenSPP). - `params`: The parameters for the call, including: - `service`: The Odoo service to use (`"common"` for authentication, `"object"` for model methods). - `method`: The method to call on the service (e.g., `"authenticate"`, `"execute_kw"`). - `args`: A list of arguments for the method. - `id`: A unique identifier for the request (integer or string). **Example payload:** ```python payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "res.partner", "search_read", [[["is_group", "=", False]]], # Domain filter {"fields": ["name", "reg_id"], "limit": 5} # Options ] }, "id": 10, } ``` **Common JSON-RPC methods:** - `authenticate`: Used for logging in and obtaining a user ID. - `execute_kw`: Used for calling model methods (such as `create`, `write`, `unlink`, `search_read`). --- ### Common model methods and args When using the `"object"` service with the `"execute_kw"` method, you can call various model methods. Here are the most common: **`create`** Creates a new record. **Args:** 1. Model name (e.g., `"res.partner"`) 2. Method name (`"create"`) 3. List of dictionaries with field values **Example:** ```python ["res.partner", "create", [{"name": "Jane Doe", "reg_id": "IND654321"}]] ``` **Result:** Returns the ID of the newly created record. **`write`** Updates existing records. **Args:** 1. Model name 2. Method name (`"write"`) 3. List of record IDs to update 4. Dictionary of fields to update **Example:** ```python ["res.partner", "write", [[individual_id], {"birthdate": "1991-02-02"}]] ``` **Result:** Returns a dictionary indicating the result of the update. **`unlink`** Deletes records. **Args:** 1. Model name 2. Method name (`"unlink"`) 3. List of record IDs to delete **Example:** ```python ["res.partner", "unlink", [individual_id]] ``` **Result:** Returns a dictionary indicating the result of the deletion. **`search_read`** Searches for records and reads their fields. **Args:** 1. Model name 2. Method name (`"search_read"`) 3. Domain filter (list of conditions) 4. Options dictionary (fields, limit, offset, etc.) **Example:** ```python ["res.partner", "search_read", [[["is_group", "=", False]]], {"fields": ["name", "reg_id"], "limit": 5}] ``` **Domain filters:** A domain is a list of conditions, each as a list: `[field, operator, value]`. Example: `[["name", "=", "John Doe"]]` **Options:** - `fields`: List of fields to return. - `limit`: Maximum number of records. - `offset`: Skip the first N records. **Result** Returns a dictionary of the search results. --- ### Authentication You must authenticate before accessing most data. Use your password or an API key (recommended). ```python import requests url = "http://localhost:8069/jsonrpc" db = "my_database" username = "admin" password = "your_password_or_api_key" payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "common", "method": "authenticate", "args": [db, username, password, {}] }, "id": 1, } response = requests.post(url, json=payload).json() uid = response.get("result") if not uid: raise Exception("Authentication failed") print("Authenticated UID:", uid) ``` ### Working with individuals **Gather the fields** For this example we are going to use these fields: - `name` (string, required) - `given_name` (string, required) - `family_name` (string, required) - `gender` (many2one, required) - `birthdate` (date, required) - `is_registrant` (boolean, default=True) - `is_group` (boolean, default=False) **Example: Create an individual** ```python # First, get a gender_id (e.g., for "Male") payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "gender.type", "search_read", [[["code", "=", "Male"]]], {"fields": ["id"], "limit": 1} ] }, "id": 2, } response = requests.post(url, json=payload).json() gender_id = response["result"][0]["id"] # Now, create the individual payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "res.partner", "create", [{ "name": "John Doe", "given_name": "John", "family_name": "Doe", "gender": gender_id, "birthdate": "1990-01-01", "is_registrant": True, "is_group": False, }] ] }, "id": 3, } response = requests.post(url, json=payload).json() individual_id = response["result"] print("Created Individual ID:", individual_id) # Update the individual's birthdate payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "res.partner", "write", [[individual_id], # List of IDs to update {"birthdate": "1991-02-02"}] # Fields to update ] }, "id": 8, } response = requests.post(url, json=payload).json() updated = response["result"] print("Result:", updated) ``` ### Working with groups **Gather the fields** For this example we are going to use these fields: - `name` (string, required) - `kind` (many2one, required) - `is_registrant` (boolean, default=True) - `is_group` (boolean, default=True) **Example: Create a group** ```python # Get a group kind (e.g., "Household") payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "g2p.group.kind", "search_read", [[["name", "=", "Household"]]], {"fields": ["id"], "limit": 1} ] }, "id": 4, } response = requests.post(url, json=payload).json() kind_id = response["result"][0]["id"] # Create the group payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "res.partner", "create", [{ "name": "Doe Family", "kind": kind_id "is_registrant": True, "is_group": True, }] ] }, "id": 5, } response = requests.post(url, json=payload).json() group_id = response["result"] print("Created Group ID:", group_id) # Update the group's name payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "res.partner", "write", [[group_id], # List of IDs to update {"name": "Doe Family Updated"}] # Fields to update ] }, "id": 10, } response = requests.post(url, json=payload).json() updated = response["result"] print("Result:", updated) ``` ### Working with memberships **Gather the fields** From your `g2p_registry_membership` module, the main model is likely `g2p_registry_membership.group_membership`. Required fields typically include: - `individuak` (many2one, required) - `group` (many2one, required) - `kind` (many2many, required; e.g., "Head", "Member") - `start_date` (date, required) - Any other required fields as defined in your model **Example: Add an individual to a group** ```python # Get a membership kind (e.g., "Head") payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "g2p.group.membership.kind", "search_read", [[["name", "=", "Head"]]], {"fields": ["id"], "limit": 1} ] }, "id": 6, } response = requests.post(url, json=payload).json() kind_id = response["result"][0]["id"] # Create the membership payload = { "jsonrpc": "2.0", "method": "call", "params": { "service": "object", "method": "execute_kw", "args": [ db, uid, password, "g2p.group.membership", "create", [{ "individual": individual_id, "group": group_id, "kind": [[4, kind_id]], "start_date": "2025-01-01" }] ] }, "id": 7, } response = requests.post(url, json=payload).json() membership_id = response["result"] print("Created Membership ID:", membership_id) ``` ### Security: Using API keys - **API keys** are recommended over passwords for scripts and integrations. - Generate API keys in your OpenSPP user preferences under **Account Security**. - Use the API key in place of your password in all JSON-RPC calls. ## Best practices - **Use API Keys**: Safer than passwords; revoke if compromised. - **Limit Permissions**: Create dedicated users for API access with only necessary rights. - **Validate Responses**: Always check for errors or unexpected results. - **Secure Connections**: Use HTTPS for all API traffic. - **Log and Monitor**: Track API usage for auditing and troubleshooting. ## References - [Odoo 17 Developer Documentation](https://www.odoo.com/documentation/17.0/developer/) - [JSON-RPC Specification](https://www.jsonrpc.org/specification)