Skip to main content

Errors

Beta software

connect-python is in beta. 1.0 will include a new Protobuf implementation built from scratch by Buf, which may introduce breaking changes. Join us on Slack if you have questions or feedback.

Similar to the familiar "404 Not Found" and "500 Internal Server Error" status codes in HTTP, Connect uses a set of 16 error codes. These error codes are designed to work consistently across Connect, gRPC, and gRPC-Web protocols.

Working with errors

Connect handlers raise errors using ConnectError:

from connectrpc.code import Code
from connectrpc.errors import ConnectError
from connectrpc.request import RequestContext

async def greet(self, request: GreetRequest, ctx: RequestContext) -> GreetResponse:
if not request.name:
raise ConnectError(Code.INVALID_ARGUMENT, "name is required")
return GreetResponse(greeting=f"Hello, {request.name}!")

Clients catch errors the same way:

from connectrpc.code import Code
from connectrpc.errors import ConnectError

async with GreetServiceClient("http://localhost:8000") as client:
try:
response = await client.greet(GreetRequest(name=""))
except ConnectError as e:
if e.code == Code.INVALID_ARGUMENT:
print(f"Invalid request: {e.message}")
else:
print(f"RPC failed: {e.code} - {e.message}")

Error codes

Connect uses a set of 16 error codes. The code property of a ConnectError holds one of these codes. All error codes are available through the Code enumeration:

from connectrpc.code import Code

code = Code.INVALID_ARGUMENT
code.value # "invalid_argument"

# Access by name
Code["INVALID_ARGUMENT"] # Code.INVALID_ARGUMENT

Error messages

The message property contains a descriptive error message. In most cases, the message is provided by the backend implementing the service:

try:
response = await client.greet(GreetRequest(name=""))
except ConnectError as e:
print(e.message) # "name is required"

Error details

Errors can include strongly-typed details using Protobuf messages:

from connectrpc.code import Code
from connectrpc.errors import ConnectError
from connectrpc.request import RequestContext
from google.protobuf.struct_pb2 import Struct, Value

async def create_user(self, request: CreateUserRequest, ctx: RequestContext) -> CreateUserResponse:
if not request.email:
error_detail = Struct(fields={
"field": Value(string_value="email"),
"issue": Value(string_value="Email is required")
})

raise ConnectError(
Code.INVALID_ARGUMENT,
"Invalid user request",
details=[error_detail]
)
# ... rest of implementation

Reading error details on the client

Error details are google.protobuf.Any messages that can be unpacked to their original types:

try:
response = await client.some_method(request)
except ConnectError as e:
for detail in e.details:
# Check the type before unpacking
if detail.Is(Struct.DESCRIPTOR):
unpacked = Struct()
detail.Unpack(unpacked)
print(f"Error detail: {unpacked}")

Standard error detail types

With googleapis-common-protos installed, you can use standard types like:

  • BadRequest: Field violations in a request
  • RetryInfo: When to retry
  • Help: Links to documentation
  • QuotaFailure: Quota violations
  • ErrorInfo: Structured error metadata

Example:

from google.rpc.error_details_pb2 import BadRequest

bad_request = BadRequest()
violation = bad_request.field_violations.add()
violation.field = "email"
violation.description = "Must be a valid email address"

raise ConnectError(
Code.INVALID_ARGUMENT,
"Invalid email format",
details=[bad_request]
)

HTTP representation

In the Connect protocol, errors are always JSON:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
"code": "invalid_argument",
"message": "name is required",
"details": [
{
"type": "google.protobuf.Struct",
"value": "base64-encoded-protobuf"
}
]
}

The details array contains error detail messages, where each entry has:

  • type: The fully-qualified Protobuf message type (e.g., google.protobuf.Struct)
  • value: The Protobuf message serialized in binary format and then base64-encoded

See also