skills/a6-plugin-grpc-transcode/SKILL.md
Skill for configuring the Apache APISIX grpc-transcode plugin via the a6 CLI. Covers converting RESTful HTTP requests to gRPC, proto file management, pb_option settings for data type conversion, error detail decoding, and common operational patterns.
npx skillsauth add moonming/a6 a6-plugin-grpc-transcodeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The grpc-transcode plugin converts HTTP/JSON requests into gRPC calls and
returns gRPC responses as JSON. Clients send standard HTTP requests; APISIX
transcodes them to gRPC using a pre-uploaded protobuf definition, forwards to
the gRPC upstream, and returns the response as JSON. The gRPC service needs
no modification.
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| proto_id | string/integer | Yes | — | ID of the proto resource (uploaded via a6 proto create). |
| service | string | Yes | — | Fully qualified gRPC service name (e.g., helloworld.Greeter). |
| method | string | Yes | — | gRPC method name (e.g., SayHello). |
| deadline | number | No | 0 | Deadline for the gRPC call in milliseconds. 0 = no deadline. |
| pb_option | array[string] | No | — | Protobuf serialization options (see table below). |
| show_status_in_body | boolean | No | false | Include parsed grpc-status-details-bin in the JSON response body on errors. |
| status_detail_type | string | No | — | Message type for the details field in gRPC error status. Required to decode error details. |
| Option | Description |
|--------|-------------|
| enum_as_name | Return enum fields as string names (e.g., "PENDING") |
| enum_as_value | Return enum fields as integer values (e.g., 1) |
| int64_as_number | Return int64 as JSON number (may lose precision in JavaScript) |
| int64_as_string | Return int64 as string (safe for JavaScript clients) |
| int64_as_hexstring | Return int64 as hexadecimal string |
| auto_default_values | Auto-populate default values for unset fields |
| no_default_values | Do not add default values for unset fields |
| use_default_values | Use proto-defined default values |
| use_default_metatable | Use metatable for default values |
| enable_hooks | Enable protobuf hooks |
| disable_hooks | Disable protobuf hooks |
Multiple options can be combined: ["int64_as_string", "enum_as_name"]
Given a proto file:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Upload it:
a6 proto create -f - <<'EOF'
{
"id": "1",
"content": "syntax = \"proto3\";\npackage helloworld;\nservice Greeter {\n rpc SayHello (HelloRequest) returns (HelloReply) {}\n}\nmessage HelloRequest {\n string name = 1;\n}\nmessage HelloReply {\n string message = 1;\n}"
}
EOF
a6 route create -f - <<'EOF'
{
"id": "grpc-hello",
"methods": ["GET", "POST"],
"uri": "/grpc/hello",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"grpc-server:50051": 1
}
}
}
EOF
Critical: The upstream scheme must be "grpc" (or "grpcs" for TLS).
# Pass parameters via query string
curl "http://localhost:9080/grpc/hello?name=world"
# Response: {"message":"Hello world"}
# Or via POST body
curl -X POST http://localhost:9080/grpc/hello \
-H "Content-Type: application/json" \
-d '{"name": "world"}'
# Response: {"message":"Hello world"}
When your proto has import statements, compile to a .pb file first:
protoc --include_imports --descriptor_set_out=service.pb proto/service.proto
Then upload the base64-encoded .pb:
a6 proto create -f - <<EOF
{
"id": "2",
"content": "$(base64 -i service.pb)"
}
EOF
{
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "order.OrderService",
"method": "GetOrder",
"pb_option": ["int64_as_string"]
}
}
}
Returns {"order_id": "9223372036854775807"} instead of {"order_id": 9223372036854775807} (which JavaScript would corrupt).
{
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "order.OrderService",
"method": "GetOrder",
"pb_option": ["enum_as_name", "int64_as_string"]
}
}
}
Returns {"status": "PENDING"} instead of {"status": 1}.
{
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello",
"show_status_in_body": true,
"status_detail_type": "helloworld.ErrorDetail"
}
}
}
Error response body:
{
"error": {
"code": 14,
"message": "Out of service",
"details": [
{
"type": "service",
"message": "The server is out of service",
"code": 1
}
]
}
}
{
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "slow.SlowService",
"method": "LongRunning",
"deadline": 5000
}
}
}
Request fails if the gRPC service doesn't respond within 5 seconds.
// Route 1: GET /api/users/:id → user.UserService/GetUser
{
"id": "grpc-get-user",
"uri": "/api/users/*",
"methods": ["GET"],
"plugins": {
"grpc-transcode": {
"proto_id": "3",
"service": "user.UserService",
"method": "GetUser"
}
},
"upstream": {"scheme": "grpc", "type": "roundrobin", "nodes": {"user-svc:50051": 1}}
}
// Route 2: POST /api/users → user.UserService/CreateUser
{
"id": "grpc-create-user",
"uri": "/api/users",
"methods": ["POST"],
"plugins": {
"grpc-transcode": {
"proto_id": "3",
"service": "user.UserService",
"method": "CreateUser"
}
},
"upstream": {"scheme": "grpc", "type": "roundrobin", "nodes": {"user-svc:50051": 1}}
}
| Symptom | Cause | Fix |
|---------|-------|-----|
| "can not find proto" | Proto ID doesn't exist | Verify with a6 proto get <id> |
| "method not found" | Service or method name mismatch | Use fully qualified name: package.Service, case-sensitive |
| Connection refused to upstream | Wrong scheme or port | Set upstream scheme to grpc, verify port is gRPC port |
| Import errors in proto | Proto has imports but raw content uploaded | Compile to .pb with protoc --include_imports |
| int64 values corrupted | JavaScript precision loss | Use pb_option: ["int64_as_string"] |
| Enum shows numbers instead of names | Default behavior | Use pb_option: ["enum_as_name"] |
| Error details not decoded | show_status_in_body not set | Set show_status_in_body: true and status_detail_type |
| gRPC call times out silently | No deadline set | Set deadline in milliseconds |
| 502 Bad Gateway | gRPC service not running or not reachable | Check gRPC service is up and port is accessible from APISIX |
version: "1"
protos:
- id: helloworld-proto
content: |
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
routes:
- id: grpc-hello
methods:
- GET
- POST
uri: /grpc/hello
plugins:
grpc-transcode:
proto_id: helloworld-proto
service: helloworld.Greeter
method: SayHello
pb_option:
- int64_as_string
- enum_as_name
upstream_id: grpc-backend
upstreams:
- id: grpc-backend
scheme: grpc
type: roundrobin
nodes:
"grpc-server:50051": 1
tools
Core skill for working with the a6 CLI — the Apache APISIX command-line tool. Provides project conventions, command patterns, architecture overview, and development workflow. Load this skill when working on a6 source code, adding new commands, writing tests, or modifying any a6 component.
tools
Recipe skill for implementing multi-tenant API gateway patterns using the a6 CLI. Covers tenant isolation via Consumer Groups, host/path/header-based routing, per-tenant rate limiting, context forwarding with proxy-rewrite, and declarative config sync workflows for multi-tenant management.
tools
Recipe skill for configuring mutual TLS (mTLS) using the a6 CLI. Covers SSL certificate management, upstream mTLS to backend services, client certificate verification, and end-to-end mTLS setup from client through APISIX to upstream.
tools
Recipe skill for configuring upstream health checks using the a6 CLI. Covers active health checks (HTTP probing), passive health checks (response analysis), combining both, configuring healthy/unhealthy thresholds, and monitoring upstream node status.