gRPC Integration
Vyntech Account exposes a gRPC API alongside REST and GraphQL. gRPC is ideal for service-to-service communication where low latency and strong typing matter.
Connection Details
grpc.id.vyntech.com.au443authorization key)Available Services
The following protobuf services are available on the gRPC endpoint:
AuthServiceLogin, Register, VerifyMfa, RefreshToken, LogoutUserServiceGetUser, ListUsers, UpdateUser, DeleteUserSessionServiceListSessions, RevokeSession, RevokeAllRoleServiceListRoles, CreateRole, UpdateRole, DeleteRoleTenantServiceGetTenant, UpdateTenant, GetSettings, UpdateSettingsProto Definition
Simplified service definitions and key message types. The full proto files are available at grpc.id.vyntech.com.au via server reflection.
syntax = "proto3";
package vyntech.account.v1;
service AuthService {
rpc Login(LoginRequest) returns (AuthResponse);
rpc Register(RegisterRequest) returns (AuthResponse);
rpc VerifyMfa(VerifyMfaRequest) returns (AuthResponse);
rpc RefreshToken(RefreshTokenRequest) returns (AuthResponse);
rpc Logout(LogoutRequest) returns (Empty);
}
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (stream User);
rpc UpdateUser(UpdateUserRequest) returns (User);
rpc DeleteUser(DeleteUserRequest) returns (Empty);
}
service SessionService {
rpc ListSessions(ListSessionsRequest) returns (stream Session);
rpc RevokeSession(RevokeSessionRequest) returns (Empty);
rpc RevokeAll(RevokeAllRequest) returns (RevokeAllResponse);
}
service RoleService {
rpc ListRoles(ListRolesRequest) returns (ListRolesResponse);
rpc CreateRole(CreateRoleRequest) returns (Role);
rpc UpdateRole(UpdateRoleRequest) returns (Role);
rpc DeleteRole(DeleteRoleRequest) returns (Empty);
}
service TenantService {
rpc GetTenant(GetTenantRequest) returns (Tenant);
rpc UpdateTenant(UpdateTenantRequest) returns (Tenant);
rpc GetSettings(GetSettingsRequest) returns (Settings);
rpc UpdateSettings(UpdateSettingsRequest) returns (Settings);
}
// Key message types
message LoginRequest {
string email = 1;
string password = 2;
string tenant_id = 3;
}
message AuthResponse {
string access_token = 1;
string refresh_token = 2;
User user = 3;
bool mfa_required = 4;
string mfa_token = 5;
}
message User {
string id = 1;
string email = 2;
string display_name = 3;
string tenant_id = 4;
bool email_verified = 5;
bool mfa_enabled = 6;
string status = 7;
repeated string roles = 8;
google.protobuf.Timestamp created_at = 9;
google.protobuf.Timestamp updated_at = 10;
}
message Session {
string id = 1;
string user_id = 2;
string ip_address = 3;
string user_agent = 4;
google.protobuf.Timestamp created_at = 5;
google.protobuf.Timestamp last_active_at = 6;
}Client Setup
Connect to the gRPC endpoint from your preferred language. All examples use TLS and attach the access token via call metadata.
Go
package main
import (
"context"
"crypto/tls"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
pb "github.com/vyntech/account-proto/gen/go/account/v1"
)
func main() {
creds := credentials.NewTLS(&tls.Config{})
conn, err := grpc.Dial(
"grpc.id.vyntech.com.au:443",
grpc.WithTransportCredentials(creds),
grpc.WithUnaryInterceptor(authInterceptor("your-access-token")),
)
if err != nil {
panic(err)
}
defer conn.Close()
client := pb.NewAuthServiceClient(conn)
resp, err := client.Login(context.Background(), &pb.LoginRequest{
Email: "user@example.com",
Password: "SecureP@ss2024!",
TenantId: "tnt_01H7ABCD9E8F4G2H1J3K5L7M",
})
// handle resp...
}
func authInterceptor(token string) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+token)
return invoker(ctx, method, req, reply, cc, opts...)
}
}Node.js
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const packageDef = protoLoader.loadSync("account/v1/account.proto", {
keepCase: true,
longs: String,
enums: String,
defaults: true,
});
const proto = grpc.loadPackageDefinition(packageDef).vyntech.account.v1;
// Create channel with TLS
const channelCreds = grpc.credentials.createSsl();
const client = new proto.AuthService("grpc.id.vyntech.com.au:443", channelCreds);
// Attach token via metadata on each call
const meta = new grpc.Metadata();
meta.add("authorization", "Bearer your-access-token");
client.Login(
{
email: "user@example.com",
password: "SecureP@ss2024!",
tenant_id: "tnt_01H7ABCD9E8F4G2H1J3K5L7M",
},
meta,
(err, response) => {
if (err) throw err;
console.log("Access token:", response.access_token);
}
);Python
import grpc
from account.v1 import account_pb2, account_pb2_grpc
# Create secure channel with TLS
channel_creds = grpc.ssl_channel_credentials()
call_creds = grpc.access_token_call_credentials("your-access-token")
composite_creds = grpc.composite_channel_credentials(channel_creds, call_creds)
channel = grpc.secure_channel("grpc.id.vyntech.com.au:443", composite_creds)
stub = account_pb2_grpc.AuthServiceStub(channel)
# Make RPC call
response = stub.Login(account_pb2.LoginRequest(
email="user@example.com",
password="SecureP@ss2024!",
tenant_id="tnt_01H7ABCD9E8F4G2H1J3K5L7M",
))
print(f"Access token: {response.access_token}")
print(f"User: {response.user.display_name}")Java
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import com.vyntech.account.v1.AuthServiceGrpc;
import com.vyntech.account.v1.Account.*;
public class AccountClient {
public static void main(String[] args) {
ManagedChannel channel = ManagedChannelBuilder
.forAddress("grpc.id.vyntech.com.au", 443)
.useTransportSecurity()
.build();
// Create metadata with bearer token
Metadata headers = new Metadata();
Metadata.Key<String> authKey =
Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
headers.put(authKey, "Bearer your-access-token");
AuthServiceGrpc.AuthServiceBlockingStub stub = AuthServiceGrpc
.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(headers));
AuthResponse response = stub.login(LoginRequest.newBuilder()
.setEmail("user@example.com")
.setPassword("SecureP@ss2024!")
.setTenantId("tnt_01H7ABCD9E8F4G2H1J3K5L7M")
.build());
System.out.println("Access token: " + response.getAccessToken());
channel.shutdown();
}
}Authentication
Pass the access token as gRPC metadata with key authorization and value Bearer <token>. The token is validated on every call — expired or invalid tokens return UNAUTHENTICATED.
Note: The Login, Register, and RefreshToken RPCs do not require authentication metadata — they produce tokens rather than consume them.
// Metadata sent with each authenticated RPC call: // // authorization: Bearer eyJhbGciOiJFZERTQSIs... // // The server extracts the token, validates the EdDSA signature, // checks expiration, and extracts claims (user_id, tenant_id, // roles, permissions) before processing the RPC.
Error Handling
Vyntech Account uses standard gRPC status codes. The error message field contains a human-readable description, and details may include structured error information.
| Code | Status | Meaning |
|---|---|---|
| 0 | OK | Success — request completed normally |
| 3 | INVALID_ARGUMENT | Bad request — missing or malformed fields |
| 5 | NOT_FOUND | Resource does not exist (user, session, role, tenant) |
| 6 | ALREADY_EXISTS | Conflict — email already registered, role name taken |
| 7 | PERMISSION_DENIED | Authenticated but lacks required permissions |
| 8 | RESOURCE_EXHAUSTED | Rate limit exceeded — back off and retry |
| 16 | UNAUTHENTICATED | Missing, expired, or invalid access token |
Streaming
ListUsers and ListSessionssupport server-side streaming for large result sets. Instead of loading all records into memory and returning a single response, the server streams individual records as they're read from the database.
// Go — consuming a server-side stream
stream, err := userClient.ListUsers(ctx, &pb.ListUsersRequest{
TenantId: "tnt_01H7ABCD9E8F4G2H1J3K5L7M",
PageSize: 100,
})
if err != nil {
log.Fatal(err)
}
for {
user, err := stream.Recv()
if err == io.EOF {
break // stream complete
}
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %s (%s)\n", user.DisplayName, user.Email)
}Streaming is optional — if your result set is small, the stream will complete quickly. For large tenants with thousands of users, streaming avoids timeout issues and reduces memory pressure on both client and server.
Best Practices
Reuse Channels
Create one gRPC channel (connection) and reuse it across all calls. Channels handle connection pooling and multiplexing internally — creating a new channel per request is expensive.
Retry with Backoff
Implement exponential backoff for UNAVAILABLE (14) status codes. Transient network issues resolve quickly, and aggressive retries create thundering herd problems.
Set Deadlines
Always set a deadline (timeout) on every RPC call. Without a deadline, a hung server will block your client indefinitely. 5 seconds is a reasonable default for auth operations.
Use Reflection for Debugging
The server supports gRPC reflection. Use grpcurl for ad-hoc debugging: grpcurl -d '{...}' grpc.id.vyntech.com.au:443 vyntech.account.v1.AuthService/Login
What's Next
GraphQL Integration →
Query users, sessions, and roles with a flexible GraphQL API
OAuth / OIDC Integration →
Use Vyntech Account as an OpenID Connect provider
Authentication API (REST) →
Full REST endpoint reference for login, register, and token flows
Sessions API →
Manage active sessions, revoke access, and monitor user activity