Errors and limits
SPEKTRA Edge reports failures as gRPC status codes. Over REST they arrive as the corresponding HTTP status, so the same small set of codes covers both.
Reading a failure
| gRPC | HTTP | Meaning | Retry? |
|---|---|---|---|
INVALID_ARGUMENT |
400 | Malformed request — a bad filter, an ill-formed name, a value that fails validation. | No. Fix the request. |
UNAUTHENTICATED |
401 | No usable credential. | No. |
PERMISSION_DENIED |
403 | Authenticated, but not allowed. | No, unless the grant is new — see below. |
NOT_FOUND |
404 | No resource at that name. | No. |
ALREADY_EXISTS |
409 | An identifier is taken. | No. |
ABORTED |
409 | A compare-and-swap precondition failed. | Yes, after re-reading. |
FAILED_PRECONDITION |
400 | The resource is in the wrong state for this call. | No, not without changing something. |
RESOURCE_EXHAUSTED |
429 | A project limit is reached. | No. See Limits. |
UNIMPLEMENTED |
501 | The operation, or that part of a filter, is not supported. | No. |
UNAVAILABLE |
503 | Transient — a restart, a rebalance, a network blip. | Yes. |
DEADLINE_EXCEEDED |
504 | The call outran its deadline. | Yes, with care. |
INTERNAL |
500 | A server-side fault. | Yes, once or twice with backoff. |
Three of these mean something more specific than their name suggests.
NOT_FOUND is usually a name problem. Before concluding a resource is
missing, check the name — a dropped regions/{region} segment is by far the
most common cause. See Resource names.
UNIMPLEMENTED from a read is usually the filter. The filter grammar
accepts OR, NOT, NOT IN, LIKE, and IS NAN, but the query engine does
not execute them. Such a filter parses and then fails here. See
Reading resources.
PERMISSION_DENIED right after granting access usually means “not yet”.
Role bindings are applied by a platform controller, not at write time,
so allow a minute or two before treating a denial as real. See
Accounts & access.
Updates
Both of the following destroy data silently, returning a perfectly successful response.
Omitting the update mask replaces the whole resource
updateMask lists the fields to change. Without it, the entire resource is
replaced by the body you sent — so a read-modify-write that posts back a
partial object clears every field it omitted.
PUT /v1/projects/{project}/regions/{region}/devices/{device}
{"name": "...", "displayName": "Till 4"}
That request clears every field absent from the body. Naming the field you mean to change confines the write to it:
PUT /v1/projects/{project}/regions/{region}/devices/{device}?updateMask=displayName
{"name": "...", "displayName": "Till 4"}
Always send an update mask. Treat a mask-less update as a deliberate, reviewed decision.
Arrays and maps are replaced, not merged
An update mask covering a repeated field or a map replaces its whole contents. There is no “add one element” update. To add a label, read the map, add your entry locally, and send the complete map back — otherwise you delete every label you did not mention.
This is the mistake that most often shows up as “something removed all our labels”, usually from two jobs updating the same resource.
Making concurrent updates safe
For read-modify-write on anything contended, use compare-and-swap rather
than hoping. Supply the state you believe the resource is in, plus a mask of
the fields to verify; the server compares before writing and returns ABORTED
if reality has moved on.
On ABORTED, re-read, re-apply your change, and try again. This is a correct
retry loop, unlike blindly retrying a plain update, which would overwrite
whatever the other writer did.
Upserts
allowMissing creates the resource if the name does not exist. Note that on
the create path the update mask is ignored — the whole body is used. Send
a complete resource when using it.
Trimming responses
An update response returns the resource. On a busy loop you can shrink it: suppress the body entirely, return only the fields actually changed, or supply a mask of the fields you want back. Worth doing for high-frequency writers.
Limits
RESOURCE_EXHAUSTED means a project limit has been reached — not that you are
being rate-limited for calling too often. Retrying will not help; something has
to change.
Limits are per resource type and per region, so a project spanning two regions has a separate allowance in each. Read your current usage and ceiling from the limits service, or in the dashboard under Settings → Limits, and request an increase there. Increases are approved, not granted instantly, so capacity is something to plan ahead of time.
For automation the practical consequence is to check headroom before a bulk create: a partially completed bulk operation is more work to reconcile than one that never started. See Understanding your limits.
Retrying well
Retry only the codes marked retryable above, and never a call whose effect you cannot reason about repeating.
- Back off exponentially, with jitter. A fleet of clients retrying in step after an outage is its own second outage.
- Cap total attempts, and surface a failure rather than retrying forever.
- Make creates idempotent by supplying the identifier yourself. A create
with a name you chose returns
ALREADY_EXISTSon a duplicate attempt, which you can treat as success. A create with a server-generated name that times out leaves you unable to tell whether it succeeded — and may create a second resource when retried. - Be careful with
DEADLINE_EXCEEDEDon writes. The deadline expired on your side; the server may well have applied the change. Re-read before retrying, or use compare-and-swap. - Reconnect streams as a matter of course. For watch streams, dropped connections are routine, not exceptional.
Things that are not immediate
Several platform behaviors are applied by controllers after the write returns, which is correct but surprising if you assume read-after-write everywhere.
| Change | Becomes true |
|---|---|
| Role binding created or removed | Within a minute or two |
| Device configuration change | Next time the device polls |
| Application deployment | When the device pulls and starts it |
| Limit increase request | When approved |
For anything in this table, poll for the observed state or watch for it. Do not assert it immediately after the write.
Next steps
- Reading resources — avoiding
UNIMPLEMENTEDfrom filters. - Understanding your limits — how limits are set and raised.