gRPC API
Requires a license with the
grpcfeature. See pricing.
The gRPC API serves your content types and entries to gRPC clients on a port
of its own, with the same data, tenants, roles and data residency rules as the
REST APIs. A second port answers health checks and offers every method as a
plain JSON route, which is handy with curl. It adds no features over REST: it
carries the same calls over HTTP/2.
How it works
Section titled “How it works”| Listener | Variable | Default | Serves |
|---|---|---|---|
| gRPC | GRPC_ADDR | 127.0.0.1:3003 | SchemaService, ContentService, FlowService, gRPC health, and reflection outside production |
| Health and JSON | GRPC_HEALTH_ADDR | 127.0.0.1:3004 | /healthz, and every method as a JSON route under /api/ |
Both are separate from the Admin API (3001) and the Content API (3002).
Both default to loopback, so nothing outside the container reaches them until
you change the address. The gRPC listener is plaintext HTTP/2: put TLS in front
of it as you do for the Content API. The
Kubernetes deployment shows the container
ports and Service entries.
Try it
Section titled “Try it”You need a signed-in user's token in TOKEN. The
quickstart shows how to get one.
-
Open both listeners and publish the ports (the engine image exposes 3003 and 3004), then restart:
Terminal window GRPC_ADDR=0.0.0.0:3003GRPC_HEALTH_ADDR=0.0.0.0:3004 -
Check health, which needs no token:
Terminal window curl http://localhost:3004/healthz{"status": "SERVING", "services": {"": "SERVING", "lyeve.core.v1.ContentService": "SERVING", "lyeve.core.v1.SchemaService": "SERVING"}} -
List entries of a content type through the JSON route:
Terminal window curl http://localhost:3004/api/content/post \-H "Authorization: Bearer $TOKEN"{"items": [{"id": "c8a14e2b-7d3f-4b90-a1c6-5e8f0d2b9a74", "title": "Hello"}]} -
Call the gRPC listener itself with
grpcurl. Reflection letsgrpcurldiscover the services, and it is served only whenAPP_ENVis notproduction:Terminal window grpcurl -plaintext \-H "authorization: Bearer $TOKEN" \localhost:3003 lyeve.core.v1.SchemaService/ListSchemasThe answer lists your content types as
schemas, each withnameanddefinition. -
Open Delivery > gRPC in the admin console as a super admin. It shows the listeners, their services and health, and runs one JSON route for you.
Call the services
Section titled “Call the services”| Service | Methods | Role |
|---|---|---|
lyeve.core.v1.SchemaService | ListSchemas, GetSchema | viewer, editor or admin |
lyeve.core.v1.ContentService | List, Get | viewer, editor or admin |
lyeve.core.v1.ContentService | Create, Update, Delete | editor or admin |
lyeve.core.v1.FlowService | Run, List | any signed-in user, and the flow's own auth setting |
grpc.health.v1.Health | Check, Watch | none |
grpc.reflection.v1.ServerReflection | a token, and only outside production |
A super admin may call every method. The services are defined in
schema.proto, content.proto and flow.proto, package lyeve.core.v1.
Messages travel as JSON, so you can call the services without generated message
types: use the json content subtype, which sends
Content-Type: application/grpc+json. In Go, pass
grpc.CallContentSubtype("json"). The .proto files name some fields with a
_json suffix (definition_json, items_json, data_json, item_json). On
the wire the keys are definition, items, data and item, and their
values are JSON objects rather than encoded strings.
| Method | Request | Answer |
|---|---|---|
ContentService.List | schema, limit (default 50, 1 to 500), offset, filters (equality on known fields), locale | items |
ContentService.Get | schema, id, locale | item |
ContentService.Create | schema, data | item |
ContentService.Update | schema, id, data | item |
ContentService.Delete | schema, id | empty |
SchemaService.ListSchemas | none | schemas, each name and definition |
SchemaService.GetSchema | name | name and definition |
FlowService.Run | slug, input | run_id, status, headers, body |
FlowService.List | none | flows, each slug, name, description, auth and input_schema |
On content types that use soft delete, List and Get leave deleted entries
out, and Delete soft-deletes.
A content type's transports block decides what gRPC serves:
| Case | Answer |
|---|---|
The content type is off for gRPC | NOT_FOUND, the answer a missing content type gets, and ListSchemas leaves it out |
It is served, but not in this direction (r refuses writes, w refuses reads) | FAILED_PRECONDITION |
See Transports.
Authenticate
Section titled “Authenticate”Send a signed-in user's session token in the authorization metadata on gRPC,
or the Authorization header on the JSON routes. API keys are not accepted
here, and neither is a sign-in that still waits for its second factor. Only the
health service and /healthz answer without a token. The token's tenant_id
claim decides the tenant, never a header the client sends. See
Tenants.
Every call is rate limited, authenticated, scoped to its tenant and checked for data residency before it runs.
Use the JSON routes
Section titled “Use the JSON routes”The health and JSON listener offers the same calls for clients without gRPC:
JSON routes on GRPC_HEALTH_ADDR
| Method | Path | Same as |
|---|---|---|
GET | /healthz | Health summary. ?service=<name> checks one service. No token needed. |
GET | /api/schemas | SchemaService.ListSchemas |
GET | /api/schemas/{name} | SchemaService.GetSchema |
GET | /api/content/{schema} | ContentService.List. Takes locale. |
GET | /api/content/{schema}/{id} | ContentService.Get. Takes locale. |
POST | /api/content/{schema} | ContentService.Create. Answers 201. |
PUT | /api/content/{schema}/{id} | ContentService.Update |
DELETE | /api/content/{schema}/{id} | ContentService.Delete. Answers {"deleted": "<id>"}. |
GET | /api/flows | FlowService.List |
POST | /api/flows/{slug} | FlowService.Run. The body is the input. |
GET / redirects to /healthz.
On these routes the whole body is the entry, while the Content API on 3002
takes {"data": {...}}. Sending that envelope here creates an entry with one
field named data:
curl -X POST http://localhost:3004/api/content/post \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"title": "From gRPC JSON", "slug": "from-grpc-json"}'The answer is 201 with {"item": {...}}. A body is at most 1 MB, and one
that is not JSON answers 400 with {"error": "invalid JSON body"}.
Run flows
Section titled “Run flows”When flows run beside gRPC, FlowService.Run starts a published
flow of the caller's tenant whose HTTP trigger lists grpc in protocols,
with input as the trigger body:
grpcurl -plaintext \ -H "authorization: Bearer $TOKEN" \ -d '{"slug": "order-totals", "input": {"order": 7}}' \ localhost:3003 lyeve.core.v1.FlowService/RunThe answer carries the run id (empty when the flow's cache answered), the
status the flow set or 200, its headers and the body. The flow's auth
setting decides who may run it: public and auth flows answer, and admin
flows answer NOT_FOUND. Its rate limit and cache count gRPC callers apart
from REST and GraphQL callers.
| Code | Meaning |
|---|---|
NOT_FOUND | No published flow has that slug for this protocol and caller. |
PERMISSION_DENIED | The license does not carry flow-pro, which starting a flow over gRPC needs. |
RESOURCE_EXHAUSTED | The flow's rate limit refused the call. |
ABORTED | The call would nest flows too deep. |
UNAVAILABLE | The flow could not run. |
UNIMPLEMENTED | Flows are not available on this instance. The JSON routes answer 501. |
Admin routes
Section titled “Admin routes”Both need a super admin and live on the Admin API.
| Method | Path | Purpose |
|---|---|---|
GET | /api/admin/grpc/status | The listeners, their services, what health reports and the JSON routes. |
POST | /api/admin/grpc/invoke | Run one JSON route inside the engine with your own token and return its answer. |
curl -X POST http://localhost:3001/api/admin/grpc/invoke \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"route": "GET /api/content/{schema}", "params": {"schema": "post"}}'route is one of the JSON routes, written as in the table. params fills its
{name} parts and body is sent on a write. The answer carries the status,
duration_ms, headers and body.
Settings
Section titled “Settings”| Variable | What it does | Default |
|---|---|---|
GRPC_ADDR | Address of the gRPC listener | 127.0.0.1:3003 |
GRPC_HEALTH_ADDR | Address of the health and JSON listener | 127.0.0.1:3004 |
GRPC_RATE_LIMIT | Calls a second per gRPC method, shared by every caller. Only a positive number is taken, so the limit cannot be turned off. | 100 |
GRPC_RATE_BURST | Burst per gRPC method | 200 |
JWT_SECRET, JWT_SECRETS | The secrets tokens are checked against. Without one, every call except health is refused. | none |
APP_ENV | Outside production, server reflection is served. Nothing else changes. | production |
INSTANCE_REGION | With data residency, writes from a tenant assigned to another region are refused. | empty, so off |
A gRPC message is at most 32 MiB each way, and calls in flight get up to 10
seconds to finish on shutdown. If you set LYEVE_PLUGINS to choose which
features start, include grpc. See
Licensing and tiers.
Errors
Section titled “Errors”On the JSON routes, gRPC codes map to HTTP statuses.
| gRPC code | HTTP | Cause |
|---|---|---|
UNAUTHENTICATED | 401 | No token, a token that does not verify, or no JWT secret configured. |
PERMISSION_DENIED | 403 | The caller's role does not allow it, or FlowService without flow-pro. |
INVALID_ARGUMENT | 400 | A malformed request. |
NOT_FOUND | 404 | No such content type or entry. |
ALREADY_EXISTS | 409 | A conflicting entry. |
RESOURCE_EXHAUSTED | 429 | rate limit exceeded. Slow down or raise GRPC_RATE_LIMIT. |
FAILED_PRECONDITION | 421 | A write from a tenant that belongs in another region. The JSON answer is {"error": "write routed to wrong region", "target_region": "<slug>"}. |
FAILED_PRECONDITION | 500 | A content type served over gRPC in the other direction only. The JSON routes answer 500 with {"error": "Internal Server Error"} today, where gRPC itself names the reason. |
DEADLINE_EXCEEDED | 504 | The call ran out of time. |
UNAVAILABLE | 503 | The tenant's region could not be read, or the database did not answer. |
Related
Section titled “Related”- GraphQL API: the same content for frontends that pick their fields.
- API endpoints: the REST routes and the transports block.
- Validate tokens with JWKS: check LyEve tokens in your own services.
- Flows: the flows
FlowService.Runstarts.