> Source: https://builder-docs.ema.ai/api-reference/misc/grpc-web
> Title: Handling gRPC-Web Requests

# Handling gRPC-Web Requests

Some Ema APIs are not accessible via standard REST and must be called using gRPC-Web over HTTP. This guide provides step-by-step instructions for making gRPC-Web requests using `curl` and command-line tools.

## Prerequisites

-   `protoc` (Protocol Buffer compiler)
-   `python3`
-   `curl`
-   Access to the `.proto` files defining the service (e.g., `service/auth/v1/auth.proto`)

## Step 1: Encode the Request Message

Use `protoc` to encode your request message into protobuf binary format.

```bash
# Option A: Encode from inline text
cat <<EOF | protoc -I=. --encode=auth.v1.GenerateApiKeyRequest \
 service/auth/v1/auth.proto > request.bin
email: "user@example.com"
EOF

# Option B: Encode from an input file
protoc -I=. --encode=auth.v1.GenerateApiKeyRequest \
 service/auth/v1/auth.proto < input.txt > request.bin
```

Replace `auth.v1.GenerateApiKeyRequest` and the proto file path with the appropriate message type and file for your target endpoint.

## Step 2: Frame the Request

gRPC-Web requires a 5-byte frame header before the protobuf payload:

```
+---------------+-----------------------------------+
| 1 byte flag | 4 byte payload length (big endian)|
+---------------+-----------------------------------+
| <protobuf-encoded message bytes> |
+---------------------------------------------------+
```

-   **Flag byte**: `0x00` for uncompressed messages
-   **Length bytes**: 4-byte big-endian integer representing the payload size

Use this Python one-liner to add the frame header:

```bash
python3 -c 'import sys,struct; data=sys.stdin.buffer.read(); \
 sys.stdout.buffer.write(b"\x00"+struct.pack(">I",len(data))+data)' \
 < request.bin > framed_request.bin
```

**Example:** If the protobuf payload is 18 bytes, the frame header is `00 00 00 00 12` (where `0x12` = 18 in decimal).

## Step 3: Send the Request

Make the HTTP request with the required gRPC-Web headers:

```bash
curl -X POST https://your-instance.ema.co/<service-path> \
 -H "Authorization: Bearer <access_token>" \
 -H "Content-Type: application/grpc-web+proto" \
 -H "x-grpc-web: 1" \
 -H "x-user-agent: grpc-web-javascript/0.1" \
 --data-binary @framed_request.bin -v \
 --output response.bin
```

### Required Headers

Header

Value

Description

`Authorization`

`Bearer <token>`

Authentication token

`Content-Type`

`application/grpc-web+proto`

gRPC-Web content type

`x-grpc-web`

`1`

gRPC-Web protocol indicator

### Optional Headers

Header

Value

Description

`x-user-agent`

`grpc-web-javascript/0.1`

Client user agent identifier

## Step 4: Strip the Response Frame

The response contains both message frames and trailer frames. Use this Python script to extract the protobuf message:

```python
#!/usr/bin/env python3
import struct
import sys

"""
Usage:
 python strip_grpc_web.py response.bin > clean_payload.bin

- response.bin: full gRPC-Web response captured from curl (--output file)
- clean_payload.bin: the extracted raw protobuf message body
"""

def extract_messages(filename: str):
 with open(filename, "rb") as f:
 data = f.read()

 idx = 0
 messages = []
 while idx < len(data):
 if idx + 5 > len(data):
 raise ValueError("Truncated frame header")

 # Read gRPC-Web frame header
 flag = data[idx]
 length = struct.unpack(">I", data[idx + 1 : idx + 5])[0]
 idx += 5

 if idx + length > len(data):
 raise ValueError("Truncated frame payload")

 payload = data[idx : idx + length]
 idx += length

 # Flag 0x80 = trailers frame (not protobuf)
 if flag == 0x80:
 try:
 print(payload.decode("utf-8"), file=sys.stderr)
 except Exception:
 print(f"[Trailers: {payload!r}]", file=sys.stderr)
 continue
 elif flag == 0x00:
 messages.append(payload)
 else:
 print(f"Unknown frame flag {flag:#x} (skipping)", file=sys.stderr)

 return messages

def main():
 if len(sys.argv) != 2:
 print("Usage: python strip_grpc_web.py response.bin > clean_payload.bin")
 sys.exit(1)

 messages = extract_messages(sys.argv[1])

 if not messages:
 print("No proto messages found!", file=sys.stderr)
 sys.exit(2)

 # Output the first proto message to stdout
 sys.stdout.buffer.write(messages[0])

if __name__ == "__main__":
 main()
```

Save this script as `strip_grpc_web.py` and run:

```bash
python3 strip_grpc_web.py response.bin > clean_payload.bin
```

The trailer frames (printed to stderr) contain gRPC status information such as `grpc-status` and `grpc-message`.

## Step 5: Decode the Response

Decode the clean protobuf payload:

```bash
protoc -I=. --decode=auth.v1.GenerateApiKeyResponse \
 service/auth/v1/auth.proto < clean_payload.bin
```

Replace the message type and proto file path with the appropriate response type for your endpoint.

## Frame Format Reference

Flag Value

Meaning

`0x00`

Uncompressed message frame

`0x01`

Compressed message frame

`0x80`

Trailers frame (gRPC status)

## Complete Example

Generating an API key end-to-end:

```bash
# 1. Encode
cat <<EOF | protoc -I=. --encode=auth.v1.GenerateApiKeyRequest \
 service/auth/v1/auth.proto > request.bin
email: "admin@company.com"
EOF

# 2. Frame
python3 -c 'import sys,struct; data=sys.stdin.buffer.read(); \
 sys.stdout.buffer.write(b"\x00"+struct.pack(">I",len(data))+data)' \
 < request.bin > framed_request.bin

# 3. Send
curl -X POST https://your-instance.ema.co/auth.v1.AuthService/GenerateApiKey \
 -H "Authorization: Bearer <token>" \
 -H "Content-Type: application/grpc-web+proto" \
 -H "x-grpc-web: 1" \
 --data-binary @framed_request.bin \
 --output response.bin

# 4. Strip
python3 strip_grpc_web.py response.bin > clean_payload.bin

# 5. Decode
protoc -I=. --decode=auth.v1.GenerateApiKeyResponse \
 service/auth/v1/auth.proto < clean_payload.bin
```

## gRPC-Web Endpoints in Ema

The following services use gRPC-Web:

Service

Path Prefix

Authentication

`/auth.v1.AuthService/`

Tenant Management

`/tenant.v1.TenantService/`

Action Manager

`/workflows.v1.ActionManager/`

Workflow Manager

`/workflows.v1.WorkflowManager/`

Dashboard Service

`/workflows.v1.DashboardsService/`

Debug Log Service

`/persona.v1.DebugLogService/`

## Related

-   [Authentication](/legacy-docs/api-reference/authentication) -- API key and token generation
-   [Access Token for Root Tenant API Key](/legacy-docs/api-reference/misc/root-tenant-token) -- First-time setup
-   [Workflow API](/legacy-docs/api-reference/workflows/workflow-api) -- Workflow gRPC-Web endpoints
-   [Dashboard RPC Calls](/legacy-docs/api-reference/dashboard/rpc-calls) -- Dashboard gRPC-Web endpoints
