gRPC Streaming

gRPC is a high-performance, open-source universal RPC framework. Your application requests events from the Firehose gRPC channel using a server-streaming RPC call, and receives a continuous sequence of Protocol Buffer binary messages.

gRPC supports code generation in several languages, allowing you to start using the API with strongly typed clients without worrying about serialization details.

Regional Endpoints

Region Host Port
Rest of the World partners.ciscospaces.io 443
Europe partners.ciscospaces.eu 443
Singapore partners.ciscospaces.sg 443

All endpoints use TLS. gRPC operates over HTTP/2.

Authentication

Pass the API key as gRPC metadata:

Metadata Key Value
x-api-key Your Firehose API key

The API key must resolve to an activation with the gRPC endpoint type enabled.

Service Definition

service Firehose {
  rpc GetEvents(EventsStreamRequest) returns (stream bytes);
}

message EventsStreamRequest {
  int32 min_partition = 1;
  int32 max_partition = 2;
  int64 from_timestamp = 3;
  int32 replica_id = 4;
}

Parameters

Field Type Default Description
min_partition int32 1 Start of partition range (1–12).
max_partition int32 12 End of partition range (1–12).
from_timestamp int64 0 Epoch milliseconds to resume consuming from. 0 = latest only.
replica_id int32 0 On-prem replica identifier. Not applicable for cloud activations.

Response Format

Each streamed message is a raw byte array containing a serialized EventRecord Protocol Buffer message. Deserialize each response as EventRecord using the generated protobuf classes.

Proto Schema

Download the full Protocol Buffer schema that defines EventRecord and all event types:

spaces_firehose_spec_v2.0.proto

Use this file to generate client stubs and message classes in your language of choice:

# Python
protoc --python_out=. --grpc_python_out=. spaces_firehose_spec_v2.0.proto

# Java
protoc --java_out=. --grpc-java_out=. spaces_firehose_spec_v2.0.proto

# Go
protoc --go_out=. --go-grpc_out=. spaces_firehose_spec_v2.0.proto

Field Naming

Proto fields use snake_case (e.g., record_uid, event_type, device_location_update). This differs from the JSON format used by HTTP/WebSocket, which uses camelCase.

Keep-Alive

Same behaviour as HTTP — if no events are available for 15 seconds, the server streams an EventRecord with event_type = KEEP_ALIVE.

Error Handling

Errors are returned as gRPC status codes:

gRPC Status HTTP Equivalent Cause
UNAUTHENTICATED 401 API key missing or invalid
PERMISSION_DENIED 403 gRPC endpoint not enabled for activation
FAILED_PRECONDITION 400 Invalid replica_id for cloud activation
RESOURCE_EXHAUSTED 429 Rate limit exceeded
INTERNAL 500 Internal server error; connection closed

On RESOURCE_EXHAUSTED, wait 180 seconds before reconnecting (same as the HTTP Retry-After behaviour).

Client Examples

Python

import grpc
import firehose_pb2
import firehose_pb2_grpc

channel = grpc.secure_channel(
    'partners.ciscospaces.io:443',
    grpc.ssl_channel_credentials()
)
stub = firehose_pb2_grpc.FirehoseStub(channel)
metadata = [('x-api-key', '<api-key>')]

request = firehose_pb2.EventsStreamRequest(
    min_partition=1,
    max_partition=12
)

try:
    for event_bytes in stub.GetEvents(request, metadata=metadata):
        event = firehose_pb2.EventRecord()
        event.ParseFromString(event_bytes)
        if event.event_type == firehose_pb2.KEEP_ALIVE:
            continue
        print(f"{event.event_type}: {event.record_uid}")
except grpc.RpcError as e:
    print(f"gRPC error: {e.code()} - {e.details()}")

Java/Kotlin

val channel = ManagedChannelBuilder
    .forAddress("partners.ciscospaces.io", 443)
    .useTransportSecurity()
    .build()

val metadata = Metadata().apply {
    put(Metadata.Key.of("x-api-key", Metadata.ASCII_STRING_MARSHALLER), apiKey)
}

val stub = FirehoseGrpc.newBlockingStub(channel)
    .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata))

val request = EventsStreamRequest.newBuilder()
    .setMinPartition(1)
    .setMaxPartition(12)
    .build()

try {
    stub.getEvents(request).forEach { eventBytes ->
        val event = EventRecord.parseFrom(eventBytes)
        if (event.eventType != EventType.KEEP_ALIVE) {
            println("${event.eventType}: ${event.recordUid}")
        }
    }
} catch (e: StatusRuntimeException) {
    println("gRPC error: ${e.status} - ${e.message}")
}

Go

conn, err := grpc.Dial(
    "partners.ciscospaces.io:443",
    grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})),
)
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

client := pb.NewFirehoseClient(conn)
md := metadata.Pairs("x-api-key", apiKey)
ctx := metadata.NewOutgoingContext(context.Background(), md)

stream, err := client.GetEvents(ctx, &pb.EventsStreamRequest{
    MinPartition: 1,
    MaxPartition: 12,
})
if err != nil {
    log.Fatal(err)
}

for {
    eventBytes, err := stream.Recv()
    if err != nil {
        log.Printf("Stream ended: %v", err)
        break
    }
    event := &pb.EventRecord{}
    proto.Unmarshal(eventBytes, event)
    if event.EventType != pb.EventType_KEEP_ALIVE {
        log.Printf("%s: %s", event.EventType, event.RecordUid)
    }
}

See Also