# Master Save API (schema)

> Save or update a tree of related objects in one call with the schema master save API of API Maker - each object is inserted or updated by its primary key, nested objects go to their own tables, arrays fill one-to-many relations, and a failure reverts everything.

Source: https://docs.apimaker.dev/v1/docs/apis-all/schema-apis/auto-generated-schema-based-master-save-api.html

One call which saves or updates a whole object graph : each object is inserted when it has no primary key and updated when it has one, at every level of nesting, across tables and even databases.

| | |
|---|---|
| Method | POST |
| URL | `/api/schema/admin/mysql8/inventory/customers/master-save` |
| Body | one object, or an array of objects, with nested related objects |
| Query params | [select](https://docs.apimaker.dev/v1/docs/apis-all/query-params/select.html), [deep](https://docs.apimaker.dev/v1/docs/apis-all/query-params/deep.html) : applied to the answer |
| Answer | `201` and `data` : the saved object(s), with the ids of the nested rows |
| Databases | MongoDB, MySQL, MariaDB, PostgreSQL, SQL Server, Oracle, TiDB, Percona |
| Cached | no (a write ; it resets the cache of the table) |
| From code | [`g.sys.db.masterSave`](https://docs.apimaker.dev/v1/examples/sys/db/masterSave.html) |
| API id | `SCHEMA_MASTER_SAVE` (groups, settings, hooks, WebSocket subscriptions) |
| The other family | [Master save as a generated API](https://docs.apimaker.dev/v1/docs/apis-all/generated-apis/auto-generated-master-save-api.html) |

> **Generated (`/api/gen`) or schema (`/api/schema`) ?**
>
> Both families offer the same operations on the same URL pattern. The **schema APIs** read the [schema](https://docs.apimaker.dev/v1/docs/schema/schema.html) of the table : values are converted to their types, validated, encrypted, defaulted and numbered before they reach the database. The **generated APIs** are schemaless : they pass your data as it is, so a number sent as `"22"` stays a string. Use the schema APIs for the tables your application writes to, and the generated APIs for quick reads and for tables without a schema.

## The URL

`/api/schema/admin/mysql8/inventory/customers/master-save` : replace `admin` with the user path of your account, `mysql8` with your instance, `inventory` with the database and `customers` with the table. The examples use this table :

| customer_id | first_name | last_name | last_update           | pincode | isActive |
|-------------|------------|-----------|-----------------------|---------|----------|
| 1           | Bob        | lin       | 2022-11-14 04: 34: 58 | 382345  | 1        |
| 2           | Alice      | Page      | 2022-10-15 02: 10: 40 | 382346  | 1        |
| 3           | Mallory    | Brown     | 2022-09-13 03: 44: 05 | 382347  | 1        |
| 4           | Eve        | Mathly    | 2022-11-12 01: 59: 33 | 382348  | 1        |
| 5           | Eve        | Page      | 2022-11-12 01: 59: 33 | 382349  | 1        |

## Save or update

```text
POST /api/schema/admin/mysql8/inventory/customers/master-save
```

**Body : no primary key, inserted**

```json
{ "first_name": "Bob", "last_name": "Lin" }
```

**Body : a primary key which exists, updated with these fields**

```json
{ "customer_id": 27, "pincode": 382330 }
```

- The rule is the same at every level : **primary key present, the row is read and updated ; absent, the row is inserted.** An update sends only the fields you give.
- An array in the body saves or updates each object and answers an array.

## A tree of objects

With relations in the schemas (`collection`, `column`, `database`, `instance` on a field), a nested object is saved or updated in its own table first and its id is written in the parent :

**Body : three tables, one call**

```json
{
    "city_name": "Wembley",
    "state_id": {
        "state_name": "London",
        "country_id": { "country_name": "UK" }
    }
}
```

**Answer**

```json
{
    "success": true,
    "statusCode": 201,
    "data": { "id": 107, "city_name": "Wembley", "state_id": 205 }
}
```

- The country is saved first, its id goes into the state, the state is saved, its id goes into the city. The tables can be in different databases and instances : the relation says where.
- `deep` on the call gives the nested rows back populated : `?deep=[{s_key:'state_id',deep:[{s_key:'country_id'}]}]`.

## One to many with virtual fields

A [virtual field](https://docs.apimaker.dev/v1/docs/schema/schema.html) (`isVirtualField` with `s_columnVirtualLinker` and `t_columnVirtualLinker`) takes an array : each item is saved or updated in the target table with the key of the parent.

**Body : an order and its items**

```json
{
    "order_no": 1001,
    "customer_id": 1,
    "items": [
        { "product_id": "66fa1c9a9b1e4a0012a3c002", "qty": 2 },
        { "id": 55, "qty": 3 }
    ]
}
```

- The first item is inserted, the second (it has its primary key) is updated.

## When something fails

- Every row written by the call is reverted when a later one fails : a wrong field in the country of the example above leaves no city and no state behind.
- The errors name the object and the field, with `dataIndex` for arrays.

## Optimistic concurrency control

- A table whose schema has a version field (`isConcurrencyControlField`) requires the version in every update of master save, and refuses a stale one with `400`. See [Optimistic concurrency control](https://docs.apimaker.dev/v1/docs/features/optimistic-concurrency-control.html).

## From your code

[`g.sys.db.masterSave`](https://docs.apimaker.dev/v1/examples/sys/db/masterSave.html) makes the same call from custom APIs, hooks, events, schedulers and test cases, with the same headers and params. Pass `true` as the second argument to get the whole [response envelope](https://docs.apimaker.dev/v1/docs/apis-all/response-format.html) instead of `data`.

## Headers

Every call takes the [request headers](https://docs.apimaker.dev/v1/docs/apis-all/header/requestHeader.html) : the tokens, `x-am-response-case`, `x-am-content-type-response` (JSON, XML, YAML…), `x-am-response-object-type: make_flat`, `x-am-cache-control`, `x-am-get-encrypted-data`, `x-am-internationalization`, `x-am-tenant-username`, `x-am-meta`.

## Errors

| Code | When |
|---|---|
| `401` | The token in `x-am-authorization` is missing or invalid. |
| `403` | No group of the API user grants this API of this table, or a field of the request. |
| `404` | The instance, database or table of the URL does not exist, or the table has no schema (use `/api/gen`). |
| `400` | The query or the body is wrong : the [error messages](https://docs.apimaker.dev/v1/docs/apis-all/error-codes.html) say which key and why. |

## Related

- [All APIs at a glance](https://docs.apimaker.dev/v1/docs/apis-all/overview.html) · [Query params](https://docs.apimaker.dev/v1/docs/apis-all/query-params/query-params.html) · [Response format](https://docs.apimaker.dev/v1/docs/apis-all/response-format.html) · [Pre hooks](https://docs.apimaker.dev/v1/docs/apis-all/hooks/preHook-api.html) and [post hooks](https://docs.apimaker.dev/v1/docs/apis-all/hooks/postHook-api.html) · [Automatic caching](https://docs.apimaker.dev/v1/docs/features/automatic-caching.html)
- [Table schema](https://docs.apimaker.dev/v1/docs/schema/schema.html) : what the conversions and validations do to every write.
