Skip to content
This page View Markdown Open in ChatGPT Open in Claude

Response format

Every API of API Maker answers with the same JSON object : the envelope. So one piece of client code can read every answer.

In short

  • success and statusCode are always there. data holds the result.
  • A failed call has errors : one entry for each problem.
  • The first error decides the status code of the answer.
  • Headers can change the format (XML, YAML, plain text) and the case of the keys in data.

The envelope

A successful answer
{
    "success": true,
    "statusCode": 200,
    "data": [ { "customer_id": 1, "first_name": "Bob", "last_name": "Lin" } ],
    "totalCount": 5
}
  • data holds the result. totalCount comes only with getTotalCount=true.
  • A key with nothing to say is not sent : warnings comes only when there are some.
A refused request
{
    "success": false,
    "statusCode": 400,
    "errors": [
        { "type": "required", "field": "first_name", "message": "Please provide valid 'first_name' field with type 'string'.", "code": 400 }
    ]
}
  • errors lists every problem found. Each has a code and a message. A value the schema refuses also names its field and the type of the rule.
Key When What it holds
success always true when the call worked.
statusCode always The HTTP status of the answer, also in the body.
data on success The result : a list for get all and the queries, one row for get by id, whatever a custom API returns. null when there is nothing.
totalCount with getTotalCount=true How many rows match the filter, before skip and limit.
errors on failure One entry for each problem. See An error entry.
warnings when your code adds some Problems that did not stop the call. A hook or a custom API adds them to g.res.warnings, in the shape of an error.
meta with x-am-meta: true, in the admin panel only How the call ran : executionTime, executionTimeMS, apiAccessGroups (the groups that allowed it) and runBy (the server, process and sandbox that ran the code).
encryptedData with x-am-get-encrypted-data The answer, encrypted. get_only_encryption sets data to null.

What your code writes with g.logger is never part of the answer. It goes to the log of the request, in Log Explorer.

An error entry

A row of a save breaks a rule of the schema
{
    "type": "min",
    "field": "price_cents",
    "message": "Please provide minimum '0' for 'price_cents' field.",
    "code": 400
}
Key What it holds
code The status of this problem.
message What went wrong. With x-am-internationalization, the message comes in the language of the caller. See the messages.
type For a value the schema refuses : the rule. required, min, max, minLength, maxLength, emailNotValid, enumValidation, unique, invalidValue (a wrong type, an error thrown by a validatorFun…), schemaKeyNotFound (a key the schema does not have), virtualFieldUsedInFind.
field For a value the schema refuses : the column, with dots for nested fields (address.city).
dataIndex For a unique error in a save of many rows : the place of the row in the list you sent, from 0.
stack For an error of your code : the stack, one line in each entry.
apiCallSequence When API calls go round in a circle (a calls b, b calls a) : the APIs of the circle.

Status codes

The status of a failed answer is the code of its first error.

Code When
200 The call worked.
201 Save single or multiple and master save saved the rows.
400 The request is wrong : a value the schema refuses, an unknown instance, a bad query, a missing body.
401 No token, a token that is not valid any more, or an API closed to the outside (NO_ACCESS). Get a new token from the token API.
403 The token is good, but no group of the API user allows this API. Or the browser page is on an origin the account does not allow.
404 No API at this URL and method. Also update by id, replace by id and remove by id with throwErrorIfRecordNotFound=true, when no row has the id.
500 An error of your code or of the database. Also any error whose code is missing or is not one of the codes above.

A custom API can answer another status with g.res.statusCode.

The format of the answer

The answer is JSON. These headers change it :

Header Effect
x-am-content-type-response text/xml or text/yaml : the same envelope, in XML or YAML. text/plain or text/html : when data is a text, the answer is that text alone. Errors stay JSON.
x-am-response-case The case of the keys in data : camelCase, snakeCase, pascalCase… Any other value, like noChange, keeps them as they are. See the values.
x-am-response-object-type: make_flat Nested objects become one level, joined by _ : state_id_state_name.
x-no-compression: true Never compressed. Without it, an answer bigger than compressThreshold (51200 bytes by default) is compressed in a way the client accepts : gzip, brotli or deflate.

A custom API can also answer a file or a page of its own : see Content types.

Response headers

Header Meaning
x-am-data-source cache when the answer came from the cache, api when the API ran. Always api for the /stream APIs.
x-am-request-id The id of the request. Search for it in Log Explorer.
Content-Encoding gzip, br or deflate when the answer was compressed.

In your code

A call of g.sys.db, g.sys.system or g.sys.cache returns data, and throws the errors. Ask for the full response to get the envelope instead : the second argument true in TypeScript, full in the other languages. Then nothing is thrown.

1
2
3
const full = await g.sys.db.getAll({ instance: 'mongodb', database: 'shop', collection: 'products' }, true);
if (!full.success) g.logger.error(full.errors);
const rows = full.data;
1
2
3
4
full = await g.sys.db.full.get_all('mongodb', 'shop', 'products')
if not full.success:
    g.logger.error(full.errors)
rows = full.data
1
2
3
4
5
$full = $g->sys->db->full->getAll('mongodb', 'shop', 'products');
if (!$full->success) {
    $g->logger->error($full->errors);
}
$rows = $full->data;
1
2
3
var full = await g.Sys.Db.Full.GetAll(new GetAll("mongodb", "shop", "products"));
if (!full.Success) g.Logger.Error(full.Errors);
var rows = full.Data;
1
2
3
4
5
6
7
8
full, err := g.Sys.Db.Full.GetAll(am.GetAll{Instance: "mongodb", Database: "shop", Collection: "products"})
if err != nil {
    return nil, err
}
if !full.Success {
    g.Logger.Error(full.Errors)
}
rows := full.Data
1
2
3
4
5
let full = g.sys.db.full.get_all(GetAll::new("mongodb", "shop", "products"))?;
if !full.success {
    g.logger.error(json!(full.errors));
}
let rows = full.data;
1
2
3
ApiResponse<List<JsonObject>> full = g.sys.db.full.getAll(new GetAll("mongodb", "shop", "products"));
if (!full.isSuccess()) g.logger.error(full.getErrors());
List<JsonObject> rows = full.getData();
1
2
3
val full = g.sys.db.full.getAll(GetAll("mongodb", "shop", "products"))
if (!full.isSuccess) g.logger.error(full.errors)
val rows = full.data