Errors and limits

What the platform returns when something is wrong, and which failures to retry.

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_EXISTS on 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_EXCEEDED on 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