Skip to content

Getting Started

This page generates a TypeScript gRPC client with ts-proto and @grpc/grpc-js. Any other gRPC toolchain works from the same proto files. To try the service without writing code, see Call the service with grpcurl or Postman.

  • Node.js 20 or later (or Bun);
  • npm (or any other package manager);
  • network access to the Buf Schema Registry, from which buf downloads the two proto dependencies.
  1. Download the Synchro Service .proto files and unzip them at the root of your project. The ubikap-synchro-service-proto/ folder holds the services, plus a buf.yaml and a buf.lock that pin their dependencies (googleapis, protovalidate).
  2. Install the code generators and the runtime libraries:
Terminal window
npm install --save-dev @bufbuild/buf ts-proto
npm install @grpc/grpc-js @bufbuild/protobuf
  1. Create ubikap-synchro-service-proto/buf.gen.yaml:
version: v2
clean: true
managed:
enabled: true
plugins:
- local: protoc-gen-ts_proto
out: ../src/generated # Emptied before each generation
strategy: all
opt:
- outputClientImpl=true
- esModuleInterop=true
- stringEnums=true
  1. Generate the client from the ubikap-synchro-service-proto/ folder. npx puts protoc-gen-ts_proto on the PATH for buf:
Terminal window
cd ubikap-synchro-service-proto
npx buf generate --template buf.gen.yaml .

The files are written to src/generated/, mirroring the proto paths: src/generated/services/synchronization/v1/synchronization_service.ts, and so on.

  1. Create the transport. ts-proto does not depend on any gRPC library: every generated client takes an rpc object, which you implement once on top of @grpc/grpc-js.
import * as grpc from '@grpc/grpc-js';
const connection = new grpc.Client('synchro.api.external-integration.ubikap.dev:443', grpc.credentials.createSsl());
const CALL_TIMEOUT_MS = 30_000;
interface Rpc {
request(service: string, method: string, data: Uint8Array): Promise<Uint8Array>;
}
const makeRpc = (token?: string): Rpc => ({
request: (service, method, data) => {
const metadata = new grpc.Metadata();
if (token) {
metadata.set('authorization', `Bearer ${token}`);
}
return new Promise((resolve, reject) => {
connection.makeUnaryRequest(
// A gRPC method path is "/<package>.<Service>/<Method>"
`/${service}/${method}`,
(message: Uint8Array) => Buffer.from(message),
(response: Buffer) => response,
data,
metadata,
{ deadline: Date.now() + CALL_TIMEOUT_MS },
(error, response) => {
if (error) {
return reject(error);
}
resolve(response ?? Buffer.alloc(0));
},
);
});
},
});
  1. Log in for an office, identified by its id in your software:
import { AuthenticationServiceClientImpl } from './generated/services/authentication/v1/authentication_service';
const authenticationService = new AuthenticationServiceClientImpl(makeRpc());
const { token } = await authenticationService.LogIn({
officeScope: { target: { office: { externalId: '12345' } } }, // The office's id in your software
userUuid: '01920000-0000-7000-8000-000000000000', // UUIDv7 given by Ubikap
userCode: '12345678', // Current 8-digit TOTP code
});
  1. Post a synchronization, then read its state:
import { SynchronizationServiceClientImpl } from './generated/services/synchronization/v1/synchronization_service';
const synchronizationService = new SynchronizationServiceClientImpl(makeRpc(token));
const { synchronizationId } = await synchronizationService.PostAsyncTasks({
tasks: [
{
synchronizeFullWorkspace: {
target: { workspace: { externalId: '4242' } }, // The dossier's id in your software
source: { credential: { externalId: '67890' } }, // The user whose access Ubikap uses to read it
},
},
],
});
const { tasks, stats } = await synchronizationService.GetSynchroState({ synchronizationId });

A failed call rejects with a grpc.ServiceError, whose code is a gRPC status code. The Synchronize a workspace recipe polls the state until the synchronization ends.

The Synchro Service is served at https://synchro.api.external-integration.ubikap.dev, over gRPC and REST on the same host. Every RPC is also served over HTTP at /<package>.<Service>/<Method>, with JSON that uses the camelCase field names.

Terminal window
curl -X POST https://synchro.api.external-integration.ubikap.dev/services.authentication.v1.AuthenticationService/LogIn \
-H 'Content-Type: application/json' \
-d '{"officeScope":{"target":{"office":{"externalId":"12345"}}},"userUuid":"01920000-0000-7000-8000-000000000000","userCode":"12345678"}'
Terminal window
curl -X POST https://synchro.api.external-integration.ubikap.dev/services.synchronization.v1.SynchronizationService/PostAsyncTasks \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"tasks":[{"synchronizeFullWorkspace":{"target":{"workspace":{"externalId":"4242"}},"source":{"credential":{"externalId":"67890"}}}}]}'

The two state RPCs are GET requests, with their fields in the query string:

Terminal window
curl "https://synchro.api.external-integration.ubikap.dev/services.synchronization.v1.SynchronizationService/GetSynchroState?synchronizationId=$SYNCHRONIZATION_ID" \
-H "Authorization: Bearer $TOKEN"
curl "https://synchro.api.external-integration.ubikap.dev/services.synchronization.v1.SynchronizationService/GetGlobalSynchroState?filter.workspace.externalId=4242" \
-H "Authorization: Bearer $TOKEN"