Telepact bridges programs across any transport boundary. JSON in, JSON out — with optional binary efficiency, dynamic response shaping, and type‑safe code generation.
Telepact combines the best ideas from REST, gRPC, and GraphQL into one cohesive ecosystem — without their trade-offs.
API calls and SELECT-style field selection achieved entirely through JSON abstractions. No special parsers, no DSLs. Clients using only native JSON libraries are first-class citizens.
Binary protocols established through runtime handshakes — not build-time code generation. Opt into MessagePack efficiency without any mandatory toolchain setup.
API responses include "links" — functions with pre-filled arguments. HATEOAS-style navigation using pure JSON, over any transport. Discoverability built in.
No special client library needed. Just send JSON, get JSON
Start with the smallest possible Telepact API: one function, one request, one response, all expressed as plain JSON.
- fn.helloWorld: {}
->:
- Ok_:
msg: string
[
{},
{
"fn.helloWorld": {}
}
]
[
{},
{
"Ok_": {
"msg": "Hello world!"
}
}
]
Clients can ask for fewer fields with @select_, which keeps responses lean without introducing a separate query language.
- struct.User:
id: integer
name: string
email: string
bio: string
- fn.getUser:
id: integer
->:
- Ok_:
user: struct.User
[
{
"@select_": {
"->": {
"Ok_": [
"user"
]
},
"struct.User": [
"name",
"email"
]
}
},
{
"fn.getUser": {
"id": 42
}
}
]
[
{},
{
"Ok_": {
"user": {
"name": "Ada",
"email": "ada@example.com"
}
}
}
]
@select_ trims payloads at the protocol level. You get GraphQL-style field selection while staying inside JSON documents.
Responses can include pre-filled function calls, which act like links the client can follow next without building URLs or stitching arguments together.
- fn.getTicket:
id: string
->:
- Ok_:
title: string
- fn.createTicket:
title: string
->:
- Ok_:
id: string
view: fn.getTicket
[
{},
{
"fn.createTicket": {
"title": "Fix mobile layout"
}
}
]
[
{},
{
"Ok_": {
"id": "ticket-7",
"view": {
"fn.getTicket": {
"id": "ticket-7"
}
}
}
}
]
Clients can opt into efficient binary exchanges with any Telepact server, using a just-in-time negotiation handled cleanly by the Telepact runtime.
- struct.Event:
id: integer
title: string
status: string
- fn.listEvents:
limit: integer
->:
- Ok_:
events: [struct.Event]
[
{},
{
"fn.listEvents": {
"limit": 8
}
}
]
[{},{"Ok_":{"events":[{"id":964950937,"status":"eta","title":"ga
mma"},{"id":547909113,"status":"epsilon","title":"upsilon"},{"id
":1271036600,"status":"theta","title":"tau"},{"id":1233703683,"s
tatus":"sigma","title":"iota"},{"id":677484023,"status":"chi","t
itle":"xi"},{"id":1504773852,"status":"phi","title":"nu"},{"id":
801153710,"status":"tau","title":"omicron"},{"id":502003645,"sta
tus":"upsilon","title":"kappa"}]}}]
[
{
"@pac_": true,
"@time_": 5000,
"@bin_": [
1466586054
]
},
{
"fn.listEvents": {
"limit": 8
}
}
]
���@pac_å@bin_��WjSƁ·�·��··��···��9����eta�gamma�� �m��epsilon�u
psilon��K�z��theta�tau��I��·�sigma�iota��(a���chi�xi��Y�·ܣphi�nu
��/����tau�omicron��·����upsilon�kappa
Every API tool makes trade-offs. Telepact was designed so you don't have to choose.
| Capability | OpenAPI | JSON-RPC | gRPC | GraphQL | Telepact |
|---|---|---|---|---|---|
| No transport restrictions | ✗ | ~JSON-RPC is transport-agnostic in theory, but lack of support for metadata at the protocol level usually restrict JSON-RPC cases to transports that support headers, such as HTTP. | ✗ | ~The GraphQL protocol has limited ergonomics, so in practice most use cases are restricted to what the client and server can agree on with library support, usually HTTP. | ✓ |
| No required client libraries | ✓ | ✓ | ✗ | ✗ | ✓ |
| Type-safe generated code | ~OpenAPI code generation exists, but quality and type coverage vary across generators and target languages. | ✗ | ✓ | ✓ | ✓ |
| Human-readable wire format | ✓ | ✓ | ✗ | ~GraphQL documents are text, but the query language is brittle to work with directly, so most use cases rely on dedicated graphql libraries. | ✓ |
| Built-in binary serialization | ✗ | ✗ | ✓ | ✗ | ✓ |
| Dynamic response shaping | ✗ | ✗ | ✗ | ✓ | ✓ |
| Built-in API documentation | ~OpenAPI tooling exists to generate documentation, but this documentation has to served out-of-band from the API itself. | ✗ | ✗ | ✓ | ✓ |
| Built-in mocking | ✗ | ✗ | ✗ | ✗ | ✓ |
Define your schema once. Implement servers and clients in any supported language with full type safety.
# divide.telepact.yaml
- fn.divide:
x: integer
y: integer
->:
- Ok_:
result: number
- ErrorCannotDivideByZero: {}
from telepact import FunctionRouter, Message, Server, TelepactSchema
schema = TelepactSchema.from_directory('./api')
async def divide(function_name: str, request_message: Message) -> Message:
args = request_message.body[function_name]
x, y = args['x'], args['y']
if y == 0:
return Message({}, {'ErrorCannotDivideByZero': {}})
return Message({}, {'Ok_': {'result': x / y}})
options = Server.Options()
options.auth_required = False
function_router = FunctionRouter({'fn.divide': divide})
server = Server(schema, function_router, options)
# Plug into any framework — Starlette, Flask, FastAPI...
async def http_handler(request):
response = await server.process(await request.body())
return Response(content=response.bytes)
// No Telepact library required
const request = [
{},
{
"fn.divide": {
x: 10,
y: 2,
},
},
];
const response = await fetch("/api/telepact", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
});
const [headers, body] = await response.json();
if (body.Ok_) {
console.log(body.Ok_.result);
} else {
console.error(headers, body);
}
import io.github.telepact.Client;
import io.github.telepact.Message;
import io.github.telepact.Serializer;
BiFunction<Message, Serializer, Future<Message>> adapter = (message, serializer) ->
CompletableFuture.supplyAsync(() -> {
var requestBytes = serializer.serialize(message);
var responseBytes = transport.send(requestBytes);
return serializer.deserialize(responseBytes);
});
var client = new Client(adapter, new Client.Options());
var request = new Message(
Map.of(),
Map.of("fn.divide", Map.of("x", 10, "y", 2))
);
var response = client.request(request);
if ("Ok_".equals(response.getBodyTarget())) {
System.out.println(response.getBodyPayload().get("result"));
}
// First generate bindings from the schema:
// telepact codegen --schema-dir ./api --lang go --package calcclient --out ./gen
package main
import (
"context"
"log"
calcclient "example.com/project/gen"
telepact "github.com/telepact/telepact/lib/go"
)
func main() {
adapter := func(ctx context.Context, request telepact.Message, serializer *telepact.Serializer) (telepact.Message, error) {
requestBytes, err := serializer.Serialize(request)
if err != nil {
return telepact.Message{}, err
}
responseBytes, err := transport.Send(ctx, requestBytes)
if err != nil {
return telepact.Message{}, err
}
return serializer.Deserialize(responseBytes)
}
client, err := calcclient.NewClient(adapter, telepact.NewClientOptions())
if err != nil {
log.Fatal(err)
}
response, err := client.Divide(context.Background(), calcclient.FnDivideArgs{
X: 10,
Y: 2,
})
if err != nil {
log.Fatal(err)
}
log.Printf("result=%v", response.Ok.Result)
}
First-class support for four languages. Same API, same schema, fully cross-compatible.
npm i telepact
pip install --pre telepact
go get github.com/
telepact/telepact/lib/go
io.github.telepact:telepact
CLI workflows, an interactive console, mock servers, and more — batteries included.
Fetch schemas, generate type-safe bindings, compare compatibility, and run mock or demo servers from one command line tool.
uv tool install --prerelease=allow telepact-cli
Interactive browser-based API explorer. Build requests, browse documentation, and test against live servers.
npx telepact-console -p 8080
Spin up a fully-functional mock server from your schema. Perfect for frontend development and testing.
telepact mock --dir ./api
Read the documentation to get started!