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.
Requirements
Section titled “Requirements”- Node.js 20 or later (or Bun);
- npm (or any other package manager);
- network access to the Buf Schema Registry, from which
bufdownloads the two proto dependencies.
Generate the client
Section titled “Generate the client”- Download the Synchro Service
.protofiles and unzip them at the root of your project. Theubikap-synchro-service-proto/folder holds the services, plus abuf.yamland abuf.lockthat pin their dependencies (googleapis,protovalidate). - Install the code generators and the runtime libraries:
npm install --save-dev @bufbuild/buf ts-protonpm install @grpc/grpc-js @bufbuild/protobuf- 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- Generate the client from the
ubikap-synchro-service-proto/folder.npxputsprotoc-gen-ts_protoon thePATHforbuf:
cd ubikap-synchro-service-protonpx 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.
Call the service
Section titled “Call the service”- Create the transport.
ts-protodoes not depend on any gRPC library: every generated client takes anrpcobject, 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)); }, ); }); },});- 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});- 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.
Call the REST gateway
Section titled “Call the REST gateway”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.
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"}'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:
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"