Errors
When Andromeda refuses a request or fails to carry it out, the body is a small JSON problem document. Its type tells your code what went wrong, and its detail tells a person. If only part of a batch or an attribute update fails, you get 207 and a report of what succeeded and what didn't. Every type in the tables below has its own page, linked from its name.
What an error looks like
Here is a batch delete with a null among its items, and the answer it gets.
POST /ngsi-ld/v1/entityOperations/delete
Content-Type: application/json
[
"urn:ngsi-ld:Building:1",
null
]
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"type": "https://uri.etsi.org/ngsi-ld/errors/BadRequestData",
"title": "Bad request data",
"status": 400,
"detail": "batch item 1 is null",
"instance": "/ngsi-ld/v1/entityOperations/delete"
}
The document is plain application/json, not a problem-details media type, and it carries no @context. It has these members.
typenames the kind of error. The list is below.titleis a short fixed text for that type, the same every time.statusrepeats the HTTP status.detailis one sentence for a person, saying what is wrong, where, and what was expected, as far as the broker knows.instance, when present, is the request target you sent, path and query string included. Here it is the batch delete path. A malformedqonGET /ngsi-ld/v1/entities?type=Building&q=bad((carries"instance": "/ngsi-ld/v1/entities?type=Building&q=bad((".
That's all of them. Anything else that might help you goes into detail.
How detail is written
The detail names the member, parameter or value it is about, quoted the way you sent it, so you can find it in your request. Some families of message start with a prefix that says which part of the request they come from.
q expression:for theqlanguage, as inq expression: empty inputgeo:for geoqueries and geometriesprojection:forpick,omitandattrstype expression:for entity type selectionsinvalid query parameter <name>:for a query parameter whose value doesn't decode, as ininvalid query parameter limit: not a non-negative integerinvalid path parameter <name>:for a path segment, named after the parameter of the resource path in the specification (entityId,attrId,instanceId,type,registrationId,subscriptionId,snapshotId,entityMapId,contextId), as ininvalid path parameter entityId: invalid URI: not a valid URN, URL or IRI
A query or path parameter is named the way the specification spells it. The value you sent isn't repeated back, and the detail says which rule the value breaks instead. A parameter the operation doesn't define reads query parameter limit is not defined for this operation, and a name no operation defines reads the query names a parameter no NGSI-LD operation defines. Both are InvalidRequest.
A JSON number that is too long, for example, reads like this.
the JSON number at /big/value is refused: the number needs more than 131072 digits before the decimal point; Andromeda stores numbers of at most 131072 digits before and 16383 digits after it
It points at the number with a JSON Pointer, says what is wrong with it, and gives the limit. A detail never holds clause numbers or the broker's internal names, and HTTP header names in it are lowercase, such as content-length.
Don't parse detail in your code, because its wording can change. type and status won't.
Error types and statuses
The specification defines these types, and Andromeda answers each with the same status every time.
type (after https://uri.etsi.org/ngsi-ld/errors/) | Status | When |
|---|---|---|
InvalidRequest | 400 | the request can't be parsed |
BadRequestData | 400 | it parses, but holds a value that isn't allowed |
AlreadyExists | 409 | the resource you are creating exists |
Conflict | 409 | the request clashes with the broker's current state |
ResourceNotFound | 404 | the resource doesn't exist |
NonexistentTenant | 404 | the tenant doesn't exist |
OperationNotSupported | 422 | the operation isn't supported |
TooComplexQuery | 403 | the query is too complex to answer |
TooManyResults | 403 | the query would return too much |
NoMultiTenantSupport | 501 | named tenants are turned off |
InternalError | 500 | the broker failed |
LdContextNotAvailable | 504 | a remote @context couldn't be fetched |
Strictness says which rule raises which type.
Some refusals come from HTTP itself and have no body. They are 411 (no Content-Length), 415 (an unsupported Content-Type) and 431 (headers too large). A 406 does have a body, listing the representations the endpoint offers.
A few answers go beyond the list above. Its twelve types don't cover everything a broker needs to report. None of them fits an oversized body, a method the resource doesn't serve, a request that never finished arriving, a condition that should clear shortly, or the ways a sole context source can fail. Andromeda could force those cases into the nearest defined type, but your code branches on type, and a type that names an error that didn't happen is worse than a name the specification doesn't list. Reporting an oversized body as BadRequestData, or a source that never answered as InternalError, would send the client down the wrong path. Inventing the missing error names seems to be common practice among NGSI-LD brokers. By the letter of the specification it is a break, and a justified one. The status carries the meaning either way, type stays the precise word, and the alternative is a type that lies.
Andromeda's own types
Seven answers name a type the specification doesn't define.
type | Status | When |
|---|---|---|
MethodNotAllowed | 405 | the resource doesn't serve the method. The Allow header names the methods it does |
RequestTimeout | 408 | the body is still arriving when request_limits.timeout runs out. A late HTTP/1.1 request head gets a bare 408 instead, without this type |
RequestEntityTooLarge | 413 | the body is over one of the size limits |
LoopDetected | 508 | the sole source is registered to redirect back to this broker, or itself answers 508 because the request came round to a broker it already passed through |
GatewayTimeout | 504 | that source doesn't answer in time |
BadGateway | 502 | any other failure of that source |
ServiceUnavailable | 503 | a condition on the broker's side that should clear shortly |
Over HTTP/1.1 a 408 carries Connection: close, and the broker closes the connection. An HTTP/1.1 request head that is still arriving when server.request_head_timeout runs out also gets 408 with Connection: close, but with no body, since there is no request yet to answer with a problem document.
When the one source fails
Sometimes one context source holds all the data a request asks for, through an exclusive or redirect registration. When that source fails, the answer says how it failed.
- A source registered to redirect back to this broker, or one that itself answers
508, gives508LoopDetected. The data its registration covers is never stored here instead. - A source that doesn't answer in time gives
504GatewayTimeout. - A source that doesn't have the resource gives
404ResourceNotFound, the specification's own type. - Any other failure gives
502BadGateway.
503 with Retry-After
A 503 with Retry-After is temporary, and its type is ServiceUnavailable. Something on the broker's side should clear shortly, such as a full connection pool or a database that can't be reached. Send the same request again once the delay has passed. Store outages covers every 503. InternalError stays with 500, a failure retrying won't fix, so each type carries one status.
Partial success
A batch operation that fails for some entities and succeeds for others answers 207 with two lists.
{
"success": [
"urn:ngsi-ld:Building:1"
],
"errors": [
{
"entityId": "urn:ngsi-ld:Building:2",
"error": {
"type": "https://uri.etsi.org/ngsi-ld/errors/AlreadyExists",
"title": "Already exists",
"status": 409,
"detail": "..."
}
}
]
}
Each item in errors names the entity and carries a full problem document for it. When the failure came from a context source, the item also names the registration in registrationId.
Update Attributes and Append Attributes do the same per attribute. updated lists the names of the attributes that were written. notUpdated lists the rest, each with its attributeName and a reason, plus a registrationId when a context source refused it.
Some faults sink the whole batch instead, such as a body that isn't an array or an item without a URI id. Those get an ordinary 400, and Batch operations lists them.
Failures inside the broker
Some failures are the broker's own, such as a database fault, a broken internal invariant, or a fault while writing its own response. Changing your request won't fix them, so the answer doesn't go into detail. You get InternalError with a short summary such as internal registration error, and the real cause goes to the broker's log for its operator.
The broker never blames your request for one of its own failures.
Where Andromeda interprets the specification
The error types and their statuses come from the specification. It gives no status for Conflict, so Andromeda answers 409, the same as AlreadyExists.
The specification requires type, title and detail in the problem document, following RFC 7807, and its example also carries status and instance. RFC 9457 has since replaced RFC 7807 with the same members. The specification prefers application/json to the problem media type so that old clients and existing tools keep working. It doesn't say what instance identifies, so Andromeda uses the request target. The HTTP status is set from the type.
The HTTP binding errors 405, 406, 411, 413 and 415 are listed beside the type table. The specification gives 405 and 413 no error type, so MethodNotAllowed and RequestEntityTooLarge are Andromeda's own names for them. The binding doesn't list 408, which the HTTP standard gives to a client that doesn't send its whole request in time, so RequestTimeout is Andromeda's own as well. When the single source of an exclusive or redirect registration fails, the specification gives 508, 504, 404 or 502 without a type, so LoopDetected, GatewayTimeout and BadGateway are Andromeda's own too. The specification admits a 503 of the HTTP binding but binds InternalError to 500, a failure a client doesn't retry, so ServiceUnavailable is Andromeda's own name for a retryable 503.
The 207 bodies are the specification's BatchOperationResult, whose errors hold BatchEntityError items, and UpdateResult, whose notUpdated holds NotUpdatedDetails.