> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stateset.com/llms.txt
> Use this file to discover all available pages before exploring further.

# gRPC

> The 17 gRPC services StateSet exposes — the shared vocabulary, connection settings, and the two places gRPC behaves differently from REST.

StateSet exposes 17 gRPC services alongside the REST API, covering the same commerce domains.
Reach for gRPC when you're calling service-to-service and want a typed contract and lower
per-call overhead than JSON over HTTP.

<Warning>
  gRPC is **not** a drop-in mirror of REST. Two differences will bite you if you assume otherwise:

  * **Pagination is page-based** (`page`, `per_page`), not cursor-based like the REST endpoints.
  * **Money is an integer in minor units** — `Money { currency: "USD", amount: 1099 }` is $10.99, not
    $1,099.

  Both are covered below.
</Warning>

## Connecting

The gRPC server listens on its own port — by default **the HTTP port plus one**, overridable with
`grpc_port`.

```bash theme={null}
grpcurl -plaintext localhost:8081 list
```

Gzip compression is enabled in both directions on every service, so a client that negotiates gzip
gets it for free.

| Setting | Purpose |
| - | - |
| `grpc_port` | Listen port. Defaults to `port + 1` |
| `grpc_concurrency_limit_per_connection` | Max in-flight requests per connection (floored at 1) |
| `grpc_timeout_secs` | Server-side request timeout. `0` disables |
| `grpc_tcp_keepalive_secs` | TCP keepalive. `0` disables |
| `grpc_http2_keepalive_interval_secs` | HTTP/2 ping interval. `0` disables |
| `grpc_http2_keepalive_timeout_secs` | How long to wait for a keepalive ack |

<Tip>
  Set the HTTP/2 keepalive interval if your clients sit behind a load balancer that reaps idle
  connections. Without it, a long-lived channel can be silently dropped and the next call fails rather
  than reconnecting cleanly.
</Tip>

Every request is assigned an `x-request-id` if it doesn't carry one, and that ID is propagated to
the response — the same correlation identifier the REST API uses. Log it.

## The shared vocabulary

Four messages in `stateset.common` are used across every service. Get these right once.

```protobuf theme={null}
package stateset.common;

message Money {
  string currency = 1;  // ISO 4217, e.g. "USD"
  int64  amount   = 2;  // smallest units — cents for USD
}

message Address {
  string street_line1 = 1;
  string street_line2 = 2;  // optional
  string city         = 3;
  string state        = 4;
  string country      = 5;  // ISO 3166-1 alpha-2, e.g. "US"
  string postal_code  = 6;
}

message PaginationRequest {
  int32 page     = 1;  // 1-based
  int32 per_page = 2;
}

message PaginationResponse {
  int32 total_items      = 1;
  int32 total_pages      = 2;
  int32 current_page     = 3;
  int32 items_per_page   = 4;
  bool  has_next_page    = 5;
  bool  has_previous_page = 6;
}
```

<Warning>
  `Money.amount` is an **`int64` in the currency's smallest unit**. Sending `10.99` is not possible —
  the field is an integer — and sending `1099` while thinking in dollars overcharges by 100×. Convert
  at the boundary and assert on the result.
</Warning>

`PaginationRequest.page` is **1-based**. Page `0` is not the first page.

## Services

| Service | Package | RPCs |
| - | - | - |
| `OrderService` | `stateset.order` | 4 |
| `InventoryService` | `stateset.inventory` | 6 |
| `ProductService` | `stateset.product` | 5 |
| `CustomerService` | `stateset.customer` | 4 |
| `PaymentService` | `stateset.payment` | 2 |
| `ASNService` | `stateset.asn` | 10 |
| `PurchaseOrderService` | `stateset.purchase_order` | 7 |
| `WarehouseService` | `stateset.warehouse` | 5 |
| `SupplierService` | `stateset.supplier` | 7 |
| `ReturnService` | `stateset.return_order` | 4 |
| `WarrantyService` | `stateset.warranty` | 4 |
| `ShipmentService` | `stateset.shipment` | 4 |
| `BOMService` | `stateset.billofmaterials` | 7 |
| `PackagingService` | `stateset.packaging` | 1 |
| `PickingService` | `stateset.picking` | 1 |
| `TransferService` | `stateset.transfer` | 1 |
| `WorkOrderService` | `stateset.work_order` | 7 |

<Note>
  Note the package name for returns: `stateset.return_order`, not `stateset.return` — `return` is a
  reserved word in several target languages, so the package is qualified. Generated client namespaces
  follow suit.
</Note>

## A service in full

`ReturnService` is representative — a create, a get, a status transition, and a filtered list:

```protobuf theme={null}
syntax = "proto3";
package stateset.return_order;

import "google/protobuf/timestamp.proto";
import "common.proto";

message Return {
  string   id          = 1;
  string   order_id    = 2;
  string   customer_id = 3;
  repeated ReturnItem items = 4;
  string   status      = 5;
  string   reason      = 6;
  google.protobuf.Timestamp created_at = 7;
  google.protobuf.Timestamp updated_at = 8;
}

message ReturnItem {
  string product_id = 1;
  int32  quantity   = 2;
  string reason     = 3;
}

message ListReturnsRequest {
  string customer_id = 1;
  string order_id    = 2;
  string status      = 3;
  google.protobuf.Timestamp start_date = 4;
  google.protobuf.Timestamp end_date   = 5;
  common.PaginationRequest  pagination = 6;
}

message ListReturnsResponse {
  repeated Return returns = 1;
  common.PaginationResponse pagination = 2;
}

service ReturnService {
  rpc CreateReturn       (CreateReturnRequest)       returns (CreateReturnResponse);
  rpc GetReturn          (GetReturnRequest)          returns (GetReturnResponse);
  rpc UpdateReturnStatus (UpdateReturnStatusRequest) returns (UpdateReturnStatusResponse);
  rpc ListReturns        (ListReturnsRequest)        returns (ListReturnsResponse);
}
```

The filter fields on a `List` request are **AND-ed**, and an empty string means "don't filter on
this" rather than "match empty".

## Calling a service

```bash theme={null}
grpcurl -plaintext \
  -d '{"return_id": "ret_1042"}' \
  localhost:8081 stateset.return_order.ReturnService/GetReturn
```

```bash theme={null}
# List with pagination — note page is 1-based
grpcurl -plaintext \
  -d '{"status": "pending", "pagination": {"page": 1, "per_page": 25}}' \
  localhost:8081 stateset.return_order.ReturnService/ListReturns
```

## Errors

Failures come back as gRPC statuses rather than the REST error envelope. The mapping to expect:

| Status | Means | Retry? |
| - | - | - |
| `INVALID_ARGUMENT` | Validation failure | No — deterministic |
| `NOT_FOUND` | No such record | No |
| `FAILED_PRECONDITION` | The operation isn't valid in the current state | No — change the state first |
| `UNAVAILABLE` | Upstream or transient failure | Yes, with backoff |
| `DEADLINE_EXCEEDED` | Timed out | Yes, but see below |
| `INTERNAL` | Unexpected server-side failure | Once, then escalate |

<Warning>
  A `DEADLINE_EXCEEDED` on a **write** does not tell you whether the write happened — the server may
  have committed and then failed to respond within the deadline. Treat writes as at-least-once and make
  them idempotent on your side, keyed on your own identifier.
</Warning>

## Related

* [Overview](/api-reference/overview) — the REST surface and its conventions
* [Sync Server gRPC flow](/stateset-sync-server-grpc-flow) — a production dispatch path over gRPC
* [Protocol Buffers](https://protobuf.dev/) — the IDL itself


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.