diff --git a/apps/schema-manager/jest.config.js b/apps/schema-manager/jest.config.js
index 5ece9fcccb7e35c47222b8cd0db33663d595c5e9..ccdd468df2bf90570fb54087fa7dea267814c888 100644
--- a/apps/schema-manager/jest.config.js
+++ b/apps/schema-manager/jest.config.js
@@ -31,10 +31,9 @@ export default {
       : ['text-summary', 'html'],
   coveragePathIgnorePatterns: [
     '<rootDir>/node_modules/',
-    '<rootDir>/test/',
     '<rootDir>/coverage/',
     '<rootDir>/dist/',
-    '<rootDir>/**/test',
+    '__tests__',
     '@types',
     '.dto.(t|j)s',
     '.enum.ts',
diff --git a/apps/schema-manager/package.json b/apps/schema-manager/package.json
index 7f32d7f285a39aaca70d0199f8b9056f2f117bc8..7c87d2afbdf8fecab561a368e578fe553e07c71d 100644
--- a/apps/schema-manager/package.json
+++ b/apps/schema-manager/package.json
@@ -32,6 +32,7 @@
     "@nestjs/platform-express": "^10.2.8",
     "@nestjs/swagger": "^7.1.16",
     "@nestjs/terminus": "^10.1.1",
+    "@ocm/shared": "workspace:*",
     "axios": "^1.6.2",
     "class-transformer": "^0.5.1",
     "class-validator": "^0.14.0",
diff --git a/apps/schema-manager/src/__tests__/application.spec.ts b/apps/schema-manager/src/__tests__/application.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3973b1ea42fa35c5bfcee55e345334d4c7d414e3
--- /dev/null
+++ b/apps/schema-manager/src/__tests__/application.spec.ts
@@ -0,0 +1,26 @@
+import type { INestApplication } from '@nestjs/common';
+
+import { Test } from '@nestjs/testing';
+
+import { Application } from '../application.js';
+
+describe('Application', () => {
+  let app: INestApplication;
+
+  beforeAll(async () => {
+    const moduleFixture = await Test.createTestingModule({
+      imports: [Application],
+    }).compile();
+
+    app = moduleFixture.createNestApplication();
+    await app.init();
+  });
+
+  afterAll(async () => {
+    await app.close();
+  });
+
+  it('should be defined', () => {
+    expect(app).toBeDefined();
+  });
+});
diff --git a/apps/schema-manager/src/app.module.ts b/apps/schema-manager/src/application.ts
similarity index 51%
rename from apps/schema-manager/src/app.module.ts
rename to apps/schema-manager/src/application.ts
index 2f9297aa61f499f40dae0322601671a7e44b8039..f255cc8a13fa5f04402231d08eda5fe88a0701bb 100644
--- a/apps/schema-manager/src/app.module.ts
+++ b/apps/schema-manager/src/application.ts
@@ -1,11 +1,16 @@
+import type { ConfigType } from '@nestjs/config';
+
 import { Module } from '@nestjs/common';
 import { ConfigModule } from '@nestjs/config';
+import { ClientsModule, Transport } from '@nestjs/microservices';
 
+import { NATS_CLIENT } from './common/constants.js';
 import { httpConfig } from './config/http.config.js';
 import { natsConfig } from './config/nats.config.js';
 import { ssiConfig } from './config/ssi.config.js';
 import { validationSchema } from './config/validation.js';
 import { HealthModule } from './health/health.module.js';
+import { SchemasModule } from './schemas/schemas.module.js';
 
 @Module({
   imports: [
@@ -20,7 +25,25 @@ import { HealthModule } from './health/health.module.js';
         abortEarly: true,
       },
     }),
+
+    ClientsModule.registerAsync({
+      isGlobal: true,
+      clients: [
+        {
+          name: NATS_CLIENT,
+          inject: [natsConfig.KEY],
+          useFactory: (config: ConfigType<typeof natsConfig>) => ({
+            transport: Transport.NATS,
+            options: {
+              url: config.url as string,
+            },
+          }),
+        },
+      ],
+    }),
+
     HealthModule,
+    SchemasModule,
   ],
 })
-export default class AppModule {}
+export class Application {}
diff --git a/apps/schema-manager/src/common/constants.ts b/apps/schema-manager/src/common/constants.ts
index 159f0fd587e05c6213c530ed81d43363190f8c97..5122ca13e8e5fc22c79213575a8bdfa4fe670f5c 100644
--- a/apps/schema-manager/src/common/constants.ts
+++ b/apps/schema-manager/src/common/constants.ts
@@ -1 +1,2 @@
 export const SERVICE_NAME = 'SCHEMA_MANAGER_SERVICE';
+export const NATS_CLIENT = Symbol('NATS_CLIENT');
diff --git a/apps/schema-manager/src/main.ts b/apps/schema-manager/src/main.ts
index 4936407a8a19d52a4391a73f5f12faf6f76b04d8..3ee27f1bd6a33392bd8bd55018443962f6470471 100644
--- a/apps/schema-manager/src/main.ts
+++ b/apps/schema-manager/src/main.ts
@@ -1,3 +1,4 @@
+/* c8 ignore start */
 import type { MicroserviceOptions } from '@nestjs/microservices';
 
 import { VersioningType } from '@nestjs/common';
@@ -6,9 +7,9 @@ import { NestFactory } from '@nestjs/core';
 import { Transport } from '@nestjs/microservices';
 import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
 
-import AppModule from './app.module.js';
+import { Application } from './application.js';
 
-const app = await NestFactory.create(AppModule);
+const app = await NestFactory.create(Application);
 const configService = app.get(ConfigService);
 app.enableCors();
 
@@ -36,3 +37,4 @@ SwaggerModule.setup('/swagger', app, document);
 await app.startAllMicroservices();
 
 await app.listen(configService.get('PORT') || 3000);
+/* c8 ignore stop */
diff --git a/apps/schema-manager/src/schemas/__tests__/schemas.controller.spec.ts b/apps/schema-manager/src/schemas/__tests__/schemas.controller.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..055708c3cf423f6522fa47d2b7e5605ab09b8da8
--- /dev/null
+++ b/apps/schema-manager/src/schemas/__tests__/schemas.controller.spec.ts
@@ -0,0 +1,140 @@
+import type { RegisterSchemaPayload } from '../dto/register-schema.dto.js';
+import type { TestingModule } from '@nestjs/testing';
+import type {
+  EventAnonCredsSchemasGetAll,
+  EventAnonCredsSchemasGetById,
+  EventAnonCredsSchemasRegister,
+} from '@ocm/shared';
+
+import { Test } from '@nestjs/testing';
+import { Subject, of, takeUntil } from 'rxjs';
+
+import { NATS_CLIENT } from '../../common/constants.js';
+import { SchemasController } from '../schemas.controller.js';
+import { SchemasService } from '../schemas.service.js';
+
+describe('SchemasController', () => {
+  const natsClientMock = {};
+
+  let controller: SchemasController;
+  let service: SchemasService;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [SchemasController],
+      providers: [
+        { provide: NATS_CLIENT, useValue: natsClientMock },
+        SchemasService,
+      ],
+    }).compile();
+
+    controller = module.get<SchemasController>(SchemasController);
+    service = module.get<SchemasService>(SchemasService);
+  });
+
+  describe('getAll', () => {
+    it('should return a list of schemas', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const tenantId = 'exampleTenantId';
+      const expectedResult: EventAnonCredsSchemasGetAll['data'] = [];
+
+      jest.spyOn(service, 'getAll').mockReturnValue(of(expectedResult));
+
+      controller
+        .getAll({ tenantId })
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe((result) => {
+          expect(result).toStrictEqual(expectedResult);
+
+          unsubscribe$.next();
+          unsubscribe$.complete();
+
+          done();
+        });
+    });
+  });
+
+  describe('getById', () => {
+    it('should return a schema by id', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const schemaId = 'exampleSchemaId';
+      const tenantId = 'exampleTenantId';
+      const expectedResult: EventAnonCredsSchemasGetById['data'] = {
+        attrNames: ['exampleAttributeName'],
+        issuerId: 'exampleIssuerDid',
+        name: 'exampleName',
+        version: '1.0.0',
+      };
+
+      jest.spyOn(service, 'getById').mockReturnValue(of(expectedResult));
+
+      controller
+        .getById({ schemaId }, { tenantId })
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe((result) => {
+          expect(result).toStrictEqual(expectedResult);
+
+          unsubscribe$.next();
+          unsubscribe$.complete();
+
+          done();
+        });
+    });
+
+    it('should throw a NotFoundException if the service returned null', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const schemaId = 'exampleSchemaId';
+      const tenantId = 'exampleTenantId';
+
+      jest.spyOn(service, 'getById').mockReturnValue(of(null));
+
+      controller
+        .getById({ schemaId }, { tenantId })
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe({
+          error: (error) => {
+            expect(error.status).toBe(404);
+            expect(error.message).toBe(`Schema with id ${schemaId} not found`);
+
+            unsubscribe$.next();
+            unsubscribe$.complete();
+
+            done();
+          },
+        });
+    });
+  });
+
+  describe('register', () => {
+    it('should register a new schema', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const tenantId = 'exampleTenantId';
+      const payload: RegisterSchemaPayload = {
+        attributeNames: ['exampleAttributeName'],
+        issuerDid: 'exampleIssuerDid',
+        name: 'exampleName',
+        version: '1.0.0',
+      };
+      const expectedResult: EventAnonCredsSchemasRegister['data'] = {
+        attrNames: payload.attributeNames,
+        issuerId: payload.issuerDid,
+        name: payload.name,
+        version: payload.version,
+      };
+
+      jest.spyOn(service, 'register').mockReturnValue(of(expectedResult));
+
+      controller
+        .register({ tenantId }, payload)
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe((result) => {
+          expect(result).toStrictEqual(expectedResult);
+
+          unsubscribe$.next();
+          unsubscribe$.complete();
+
+          done();
+        });
+    });
+  });
+});
diff --git a/apps/schema-manager/src/schemas/__tests__/schemas.module.spec.ts b/apps/schema-manager/src/schemas/__tests__/schemas.module.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..861fa9a3ca54b1e82096c0f021cec17133ebf693
--- /dev/null
+++ b/apps/schema-manager/src/schemas/__tests__/schemas.module.spec.ts
@@ -0,0 +1,35 @@
+import { ClientsModule } from '@nestjs/microservices';
+import { Test } from '@nestjs/testing';
+
+import { NATS_CLIENT } from '../../common/constants.js';
+import { SchemasController } from '../schemas.controller.js';
+import { SchemasModule } from '../schemas.module.js';
+import { SchemasService } from '../schemas.service.js';
+
+describe('Schemas Module', () => {
+  let schemasController: SchemasController;
+  let schemasService: SchemasService;
+
+  beforeEach(async () => {
+    const moduleRef = await Test.createTestingModule({
+      imports: [
+        ClientsModule.registerAsync({
+          isGlobal: true,
+          clients: [{ name: NATS_CLIENT, useFactory: () => ({}) }],
+        }),
+        SchemasModule,
+      ],
+    }).compile();
+
+    schemasController = moduleRef.get<SchemasController>(SchemasController);
+    schemasService = moduleRef.get<SchemasService>(SchemasService);
+  });
+
+  it('should be defined', () => {
+    expect(schemasController).toBeDefined();
+    expect(schemasController).toBeInstanceOf(SchemasController);
+
+    expect(schemasService).toBeDefined();
+    expect(schemasService).toBeInstanceOf(SchemasService);
+  });
+});
diff --git a/apps/schema-manager/src/schemas/__tests__/schemas.service.spec.ts b/apps/schema-manager/src/schemas/__tests__/schemas.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8d91e99bfc4eee6f589870eca88e6f648311b2a6
--- /dev/null
+++ b/apps/schema-manager/src/schemas/__tests__/schemas.service.spec.ts
@@ -0,0 +1,139 @@
+import type { TestingModule } from '@nestjs/testing';
+
+import { Test } from '@nestjs/testing';
+import {
+  EventAnonCredsSchemasGetAll,
+  EventAnonCredsSchemasGetById,
+  EventAnonCredsSchemasRegister,
+} from '@ocm/shared';
+import { Subject, of, takeUntil } from 'rxjs';
+
+import { NATS_CLIENT } from '../../common/constants.js';
+import { SchemasService } from '../schemas.service.js';
+
+describe('SchemasService', () => {
+  let service: SchemasService;
+  const natsClientMock = { send: jest.fn() };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        { provide: NATS_CLIENT, useValue: natsClientMock },
+        SchemasService,
+      ],
+    }).compile();
+
+    service = module.get<SchemasService>(SchemasService);
+
+    jest.resetAllMocks();
+  });
+
+  describe('getAll', () => {
+    it('should return the data from NATS client', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const payload = {
+        tenantId: 'mocked tenantId',
+        endpoint: EventAnonCredsSchemasGetAll.token,
+      };
+      const expectedResult: EventAnonCredsSchemasGetAll['data'] = [];
+
+      natsClientMock.send.mockReturnValueOnce(
+        of(new EventAnonCredsSchemasGetAll([], payload.tenantId)),
+      );
+
+      service
+        .getAll(payload)
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe((result) => {
+          expect(natsClientMock.send).toHaveBeenCalledWith(
+            { endpoint: EventAnonCredsSchemasGetAll.token },
+            payload,
+          );
+
+          expect(result).toStrictEqual(expectedResult);
+
+          unsubscribe$.next();
+          unsubscribe$.complete();
+
+          done();
+        });
+    });
+  });
+
+  describe('getById', () => {
+    it('should return the data from NATS client', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const payload = {
+        tenantId: 'mocked tenantId',
+        schemaId: 'mocked id',
+      };
+      const expectedResult: EventAnonCredsSchemasGetById['data'] = {
+        issuerId: 'mocked issuerDid',
+        name: 'mocked name',
+        version: '1.0.0',
+        attrNames: ['mocked attribute1', 'mocked attribute2'],
+      };
+
+      natsClientMock.send.mockReturnValueOnce(
+        of(new EventAnonCredsSchemasGetById(expectedResult, payload.tenantId)),
+      );
+
+      service
+        .getById(payload)
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe((result) => {
+          expect(natsClientMock.send).toHaveBeenCalledWith(
+            { endpoint: EventAnonCredsSchemasGetById.token },
+            payload,
+          );
+
+          expect(result).toStrictEqual(expectedResult);
+
+          unsubscribe$.next();
+          unsubscribe$.complete();
+
+          done();
+        });
+    });
+  });
+
+  describe('register', () => {
+    it('should return the data from NATS client', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const payload = {
+        tenantId: 'mocked tenantId',
+        issuerDid: 'mocked issuerDid',
+        name: 'mocked name',
+        version: '1.0.0',
+        attributeNames: ['mocked attribute1', 'mocked attribute2'],
+      };
+      const expectedResult: EventAnonCredsSchemasRegister['data'] = {
+        issuerId: 'mocked issuerDid',
+        name: 'mocked name',
+        version: '1.0.0',
+        attrNames: ['mocked attribute1', 'mocked attribute2'],
+      };
+
+      natsClientMock.send.mockReturnValueOnce(
+        of(new EventAnonCredsSchemasRegister(expectedResult, payload.tenantId)),
+      );
+
+      service
+        .register(payload)
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe((result) => {
+          expect(natsClientMock.send).toHaveBeenCalledWith(
+            { endpoint: EventAnonCredsSchemasRegister.token },
+            payload,
+          );
+
+          expect(result).toStrictEqual(expectedResult);
+
+          unsubscribe$.next();
+          unsubscribe$.complete();
+
+          done();
+        });
+    });
+  });
+});
diff --git a/apps/schema-manager/src/schemas/dto/get-by-id.dto.ts b/apps/schema-manager/src/schemas/dto/get-by-id.dto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d01de1fdf4871bbdd1703e44fb3b78385b39951a
--- /dev/null
+++ b/apps/schema-manager/src/schemas/dto/get-by-id.dto.ts
@@ -0,0 +1,9 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { IsNotEmpty, IsString } from 'class-validator';
+
+export class GetByIdParams {
+  @IsString()
+  @IsNotEmpty()
+  @ApiProperty({ description: 'The schema ID to retrieve', format: 'string' })
+  public schemaId: string;
+}
diff --git a/apps/schema-manager/src/schemas/dto/register-schema.dto.ts b/apps/schema-manager/src/schemas/dto/register-schema.dto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f8d25366d8cf5789b1a9f6d1acfbf310d1804ba1
--- /dev/null
+++ b/apps/schema-manager/src/schemas/dto/register-schema.dto.ts
@@ -0,0 +1,26 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { IsArray, IsNotEmpty, IsSemVer, IsString } from 'class-validator';
+
+export class RegisterSchemaPayload {
+  @IsString()
+  @IsNotEmpty()
+  @ApiProperty()
+  public issuerDid: string;
+
+  @IsString()
+  @IsNotEmpty()
+  @ApiProperty()
+  public name: string;
+
+  @IsString()
+  @IsNotEmpty()
+  @IsSemVer()
+  @ApiProperty()
+  public version: string;
+
+  @IsArray()
+  @IsString({ each: true })
+  @IsNotEmpty({ each: true })
+  @ApiProperty()
+  public attributeNames: Array<string>;
+}
diff --git a/apps/schema-manager/src/schemas/dto/tenant-id.dto.ts b/apps/schema-manager/src/schemas/dto/tenant-id.dto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5498941a40eec9bd127a0d50512227c9526d5dcf
--- /dev/null
+++ b/apps/schema-manager/src/schemas/dto/tenant-id.dto.ts
@@ -0,0 +1,12 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { IsNotEmpty, IsString } from 'class-validator';
+
+export class TenantIdParam {
+  @IsString()
+  @IsNotEmpty()
+  @ApiProperty({
+    description: 'The tenant ID to use for the request',
+    format: 'string',
+  })
+  public tenantId: string;
+}
diff --git a/apps/schema-manager/src/schemas/schemas.controller.ts b/apps/schema-manager/src/schemas/schemas.controller.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8c52ef1e7689c995286438c27ac02bfaa5dd6942
--- /dev/null
+++ b/apps/schema-manager/src/schemas/schemas.controller.ts
@@ -0,0 +1,186 @@
+import type {
+  EventAnonCredsSchemasGetAll,
+  EventAnonCredsSchemasGetById,
+  EventAnonCredsSchemasRegister,
+} from '@ocm/shared';
+
+import {
+  Body,
+  Controller,
+  Get,
+  HttpStatus,
+  NotFoundException,
+  Param,
+  Post,
+  Query,
+  UsePipes,
+  ValidationPipe,
+  Version,
+} from '@nestjs/common';
+import {
+  ApiBody,
+  ApiOperation,
+  ApiParam,
+  ApiQuery,
+  ApiResponse,
+  ApiTags,
+} from '@nestjs/swagger';
+import { Observable, of, switchMap } from 'rxjs';
+
+import { GetByIdParams } from './dto/get-by-id.dto.js';
+import { RegisterSchemaPayload } from './dto/register-schema.dto.js';
+import { TenantIdParam } from './dto/tenant-id.dto.js';
+import { SchemasService } from './schemas.service.js';
+
+@Controller('schemas')
+@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
+@ApiTags('Schemas')
+export class SchemasController {
+  public constructor(private readonly schemasService: SchemasService) {}
+
+  @Version('1')
+  @Get()
+  @ApiOperation({
+    summary: 'Fetch a list of schemas',
+    description: 'This call provides a list of schemas for a given tenant',
+  })
+  @ApiQuery({ name: 'tenantId', required: true })
+  @ApiResponse({
+    status: HttpStatus.OK,
+    description: 'Schemas fetched successfully',
+    content: {
+      // TBD
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.NOT_FOUND,
+    description: 'Tenant not found',
+    content: {
+      // TBD
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.INTERNAL_SERVER_ERROR,
+    description: 'Internal server error',
+    content: {
+      // TBD
+    },
+  })
+  public getAll(
+    @Query() { tenantId }: TenantIdParam,
+  ): Observable<EventAnonCredsSchemasGetAll['data']> {
+    return this.schemasService.getAll({
+      tenantId,
+    });
+  }
+
+  @Version('1')
+  @Get(':schemaId')
+  @ApiOperation({
+    summary: 'Fetch a schema by id',
+    description:
+      'This call allows you to retrieve schema data for a given tenant by specifying the `schemaId`.',
+  })
+  @ApiParam({ name: 'schemaId', required: true })
+  @ApiQuery({ name: 'tenantId', required: true })
+  @ApiResponse({
+    status: HttpStatus.OK,
+    description: 'Schema fetched successfully',
+    content: {
+      // TBD
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.NOT_FOUND,
+    description: 'Tenant not found',
+    content: {
+      // TBD
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.NOT_FOUND,
+    description: 'Schema not found',
+    content: {
+      // TBD
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.INTERNAL_SERVER_ERROR,
+    description: 'Internal server error',
+    content: {
+      // TBD
+    },
+  })
+  public getById(
+    @Param() { schemaId }: GetByIdParams,
+    @Query() { tenantId }: TenantIdParam,
+  ): Observable<EventAnonCredsSchemasGetById['data']> {
+    return this.schemasService
+      .getById({
+        tenantId,
+        schemaId,
+      })
+      .pipe(
+        switchMap((schema) => {
+          if (schema === null) {
+            throw new NotFoundException(`Schema with id ${schemaId} not found`);
+          }
+          return of(schema);
+        }),
+      );
+  }
+
+  @Version('1')
+  @Post()
+  @ApiOperation({
+    summary: 'Register a new schema',
+    description:
+      'This call provides the capability to create new schema on ledger by name, author, version, schema attributes and type. Later this schema can be used to issue new credential definition. This call returns an information about created schema.',
+  })
+  @ApiQuery({ name: 'tenantId', required: true })
+  @ApiBody({ type: RegisterSchemaPayload })
+  @ApiResponse({
+    status: HttpStatus.CREATED,
+    description: 'Schema registered successfully',
+    content: {
+      'application/json': {},
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.NOT_FOUND,
+    description: 'Tenant not found',
+    content: {
+      'application/json': {},
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.BAD_REQUEST,
+    description: 'All fields are required for schema registration',
+    content: {
+      'application/json': {},
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.CONFLICT,
+    description: 'Schema already exists',
+    content: {
+      'application/json': {},
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.INTERNAL_SERVER_ERROR,
+    description: 'Internal server error',
+    content: {
+      'application/json': {},
+    },
+  })
+  public register(
+    @Query() { tenantId }: TenantIdParam,
+    @Body() payload: RegisterSchemaPayload,
+  ): Observable<EventAnonCredsSchemasRegister['data']> {
+    return this.schemasService.register({
+      ...payload,
+      tenantId,
+    });
+  }
+}
diff --git a/apps/schema-manager/src/schemas/schemas.module.ts b/apps/schema-manager/src/schemas/schemas.module.ts
new file mode 100644
index 0000000000000000000000000000000000000000..be2705b52ccb46cbbd3c0bfab3ab995dbeaa3d41
--- /dev/null
+++ b/apps/schema-manager/src/schemas/schemas.module.ts
@@ -0,0 +1,10 @@
+import { Module } from '@nestjs/common';
+
+import { SchemasController } from './schemas.controller.js';
+import { SchemasService } from './schemas.service.js';
+
+@Module({
+  controllers: [SchemasController],
+  providers: [SchemasService],
+})
+export class SchemasModule {}
diff --git a/apps/schema-manager/src/schemas/schemas.service.ts b/apps/schema-manager/src/schemas/schemas.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6a630e99aa9fbce641fabd27a05fc981056809b1
--- /dev/null
+++ b/apps/schema-manager/src/schemas/schemas.service.ts
@@ -0,0 +1,62 @@
+import type {
+  EventAnonCredsSchemasGetAllInput,
+  EventAnonCredsSchemasGetByIdInput,
+  EventAnonCredsSchemasRegisterInput,
+} from '@ocm/shared';
+
+import { Inject, Injectable } from '@nestjs/common';
+import { ClientProxy } from '@nestjs/microservices';
+import {
+  EventAnonCredsSchemasGetAll,
+  EventAnonCredsSchemasGetById,
+  EventAnonCredsSchemasRegister,
+} from '@ocm/shared';
+import { map, type Observable } from 'rxjs';
+
+import { NATS_CLIENT } from '../common/constants.js';
+
+@Injectable()
+export class SchemasService {
+  public constructor(
+    @Inject(NATS_CLIENT) private readonly natsClient: ClientProxy,
+  ) {}
+
+  public getAll(
+    payload: EventAnonCredsSchemasGetAllInput,
+  ): Observable<EventAnonCredsSchemasGetAll['data']> {
+    const pattern = { endpoint: EventAnonCredsSchemasGetAll.token };
+
+    return this.natsClient
+      .send<EventAnonCredsSchemasGetAll, EventAnonCredsSchemasGetAllInput>(
+        pattern,
+        payload,
+      )
+      .pipe(map((result) => result.data));
+  }
+
+  public getById(
+    payload: EventAnonCredsSchemasGetByIdInput,
+  ): Observable<EventAnonCredsSchemasGetById['data']> {
+    const pattern = { endpoint: EventAnonCredsSchemasGetById.token };
+
+    return this.natsClient
+      .send<EventAnonCredsSchemasGetById, EventAnonCredsSchemasGetByIdInput>(
+        pattern,
+        payload,
+      )
+      .pipe(map((result) => result.data));
+  }
+
+  public register(
+    payload: EventAnonCredsSchemasRegisterInput,
+  ): Observable<EventAnonCredsSchemasRegister['data']> {
+    const pattern = { endpoint: EventAnonCredsSchemasRegister.token };
+
+    return this.natsClient
+      .send<EventAnonCredsSchemasRegister, EventAnonCredsSchemasRegisterInput>(
+        pattern,
+        payload,
+      )
+      .pipe(map((result) => result.data));
+  }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f6b4d579ede4f3a4b22c4bf38edc3735f3f4ccc2..93ebdf1a54006abba74a71cf365a0f0661a32edf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -603,6 +603,9 @@ importers:
       '@nestjs/terminus':
         specifier: ^10.1.1
         version: 10.1.1(@nestjs/axios@3.0.1)(@nestjs/common@10.2.10)(@nestjs/core@10.2.10)(@nestjs/microservices@10.2.10)(reflect-metadata@0.1.13)(rxjs@7.8.1)
+      '@ocm/shared':
+        specifier: workspace:*
+        version: link:../shared
       axios:
         specifier: ^1.6.2
         version: 1.6.2