Skip to content

gRPC & JSON Transcoding

Every Invora API is defined once as a gRPC service and exposed over two interchangeable protocols:

  • REST / JSON — ordinary HTTPS requests with JSON bodies. This is what every guide uses, and what you get by calling the gateway with curl.
  • gRPC — the canonical binary protocol (HTTP/2) for high-throughput, server-to-server integrations, with generated typed clients.

Both speak to the same backend; the JSON surface is produced by gRPC-JSON transcoding (google.api.http annotations on each RPC). This page is the canonical reference for how the gRPC contract maps to the JSON you send and receive — field names, scalar types, dates, decimals, and enums.

Base URLs

Environment REST / gRPC gateway Auth issuer
Production https://gateway.invora.app https://auth.invora.app
Staging https://stg-gateway.invora.app https://stg-auth.invora.app

gRPC uses the same host on port 443 over TLS (HTTP/2). Every example below uses the production host; swap it for stg-gateway.invora.app to run against Staging.

Field names are camelCase

Proto fields are snake_case; the JSON surface is camelCase. The transcoder converts automatically — always send and read camelCase over JSON.

Proto (snake_case) JSON (camelCase)
freeze_immediately freezeImmediately
concurrency_stamp concurrencyStamp
currency_code currencyCode
payment_means_code paymentMeansCode
source_document_key sourceDocumentKey

Scalar & well-known types

The transcoder follows the standard proto3 JSON mapping. The cases worth memorizing:

Proto type JSON representation Example
string, bool as-is "INV-001", true
int32, enum value see below
int64 / uint64 string (avoids 53-bit precision loss) "43200"
google.protobuf.Timestamp RFC 3339 / ISO-8601 string, UTC Z "2026-05-31T10:00:00Z"
google.type.Date object with year / month / day {"year": 2026, "month": 5, "day": 31}
google.type.TimeOfDay object with hours / minutes / seconds {"hours": 14, "minutes": 30}
google.protobuf.StringValue (wrapper) the bare value, not {"value": …} "Acme" (or null when unset)
google.protobuf.Int32Value (wrapper) the bare number 42 (or null)

Wrapper types unwrap

Optional scalars modelled with the google.protobuf.*Value wrappers serialize to the bare value, not an object. Send "name": "Acme", not "name": {"value": "Acme"}. They accept null for "unset".

Dates are objects, not strings

google.type.Date and google.type.TimeOfDay are ordinary messages, so they serialize as JSON objects ({"year": …} / {"hours": …}). Only date-time values (google.protobuf.Timestamp) serialize as an ISO-8601 string. When a timezone is omitted, UTC is assumed; timestamps always end with Z.

Decimal values are {units, nanos}

Every monetary amount, quantity, and rate is an exact decimal — a JSON object with an integer units part and a fractional nanos part (billionths, 10⁻⁹), so there is never any floating-point rounding error:

value = units + nanos / 1,000,000,000
Decimal JSON
100 {"units": 100, "nanos": 0}
100.50 {"units": 100, "nanos": 500000000}
0.6789 {"units": 0, "nanos": 678900000}
15 (a 15% rate) {"units": 15, "nanos": 0}

units and nanos are both numbers (not strings); nanos is in the range 0999999999 and must share the same sign as units. Never send a floating-point amount.

Enums are full string identifiers

Enum-valued fields take the full, prefixed string identifier — never an integer and never the bare suffix:

{ "editState": "EDIT_STATE_FROZEN", "amountCurrency": "CURRENCY_ENUM_SAR" }

Use EDIT_STATE_FROZEN, not FROZEN. Other examples you will see: PLAN_INTERVAL_MONTHLY, EVENT_TYPE_INVOICE_CREATED, SORT_DIRECTION_DESCENDING, CONNECTED_BUSINESS_STATUS_ACTIVE. The accepted values for each field are listed on that field in the API reference.

Optional & repeated fields

Optional fields are omitted when unset (or sent as null). Empty repeated fields are returned as empty arrays (e.g. "invoiceAllowances": []) even when you did not send them.

Calling over REST / JSON

curl -X POST https://gateway.invora.app/api/v1/simple/invoices/list \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pagination": {"limit": "10"}}'
Response
{
  "items": [],
  "totalCount": "0"
}

Calling over gRPC

The same services are reachable as native gRPC on gateway.invora.app:443 (TLS, HTTP/2).

Proto modules (Buf Schema Registry)

The contract is published as seven modules on the Buf Schema Registry:

Module BSR reference Contents
Common buf.build/invora/common Shared document model and value types
Simple buf.build/invora/simple The ergonomic invoice / credit-note / debit-note surface (invora.simple.*.v1)
Invoicing buf.build/invora/invoicing Full UBL Documents API, parties, branches, settings, regulations
Billing buf.build/invora/billing Plans, subscriptions, usage, wallets, analytics
Identity buf.build/invora/identity Registration and connected businesses
Admin buf.build/invora/admin Platform-administration services
UBL 2.1 buf.build/invora/ubl-2-1 OASIS UBL 2.1 component definitions

Code generation

Generate a typed client in any language straight from the registry:

buf generate buf.build/invora/invoicing
buf.gen.yaml
plugins:
  - remote: buf.build/protocolbuffers/csharp
    out: gen
  - remote: buf.build/grpc/csharp
    out: gen

Metadata (headers)

gRPC uses metadata in place of HTTP headers:

Metadata key Value
authorization Bearer <access_token>
x-zitadel-orgid Target organization ID, for multi-org callers (see Authentication)

Server reflection

Reflection is enabled on every environment, so grpcurl and Evans work without local protos:

grpcurl -H "Authorization: Bearer $TOKEN" \
  gateway.invora.app:443 list

grpcurl -H "Authorization: Bearer $TOKEN" \
  gateway.invora.app:443 \
  invora.simple.invoices.v1.SimpleInvoiceService/List

API versioning

The version lives in both the proto package and the URL path — invora.simple.invoices.v1/api/v1/..., invora.documents.v2/api/v2/.... Breaking changes get a new major version; non-breaking additions (new fields, new RPCs) are made in place.