diff --git a/apps/credential-manager/src/credential-offers/__tests__/credential-offers.service.spec.ts b/apps/credential-manager/src/credential-offers/__tests__/credential-offers.service.spec.ts
index d568df68c66fb269a33d79f80df960f2e5a3df0d..06493aca2578b39fca79e2db0b3eacc58948fad0 100644
--- a/apps/credential-manager/src/credential-offers/__tests__/credential-offers.service.spec.ts
+++ b/apps/credential-manager/src/credential-offers/__tests__/credential-offers.service.spec.ts
@@ -1,9 +1,15 @@
 import type { TestingModule } from '@nestjs/testing';
+import type {
+  EventDidcommAnonCredsCredentialsOfferInput,
+  EventDidcommAnonCredsCredentialsOfferToSelfInput,
+} from '@ocm/shared';
 
 import { Test } from '@nestjs/testing';
 import {
   EventAnonCredsCredentialOfferGetAll,
   EventAnonCredsCredentialOfferGetById,
+  EventDidcommAnonCredsCredentialsOffer,
+  EventDidcommAnonCredsCredentialsOfferToSelf,
 } from '@ocm/shared';
 import { Subject, of, takeUntil } from 'rxjs';
 
@@ -88,4 +94,85 @@ describe('CredentialOffersService', () => {
         });
     });
   });
+
+  describe('createCredentialOffer', () => {
+    it('should call natsClient.send with the correct arguments', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const tenantId = 'tenantId';
+      const connectionId = 'connectionId';
+      const credentialDefinitionId = 'credentialDefinitionId';
+      const attributes: EventDidcommAnonCredsCredentialsOfferInput['attributes'] =
+        [];
+      const expectedResult =
+        {} as EventDidcommAnonCredsCredentialsOffer['data'];
+
+      natsClientMock.send.mockReturnValueOnce(
+        of(new EventDidcommAnonCredsCredentialsOffer(expectedResult, tenantId)),
+      );
+
+      service
+        .offer(tenantId, connectionId, credentialDefinitionId, attributes)
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe((result) => {
+          expect(natsClientMock.send).toHaveBeenCalledWith(
+            EventDidcommAnonCredsCredentialsOffer.token,
+            {
+              tenantId,
+              connectionId,
+              credentialDefinitionId,
+              attributes,
+            },
+          );
+
+          expect(result).toStrictEqual(expectedResult);
+
+          unsubscribe$.next();
+          unsubscribe$.complete();
+
+          done();
+        });
+    });
+  });
+
+  describe('createCredentialOfferToSelf', () => {
+    it('should call natsClient.send with the correct arguments', (done) => {
+      const unsubscribe$ = new Subject<void>();
+      const tenantId = 'tenantId';
+      const credentialDefinitionId = 'credentialDefinitionId';
+      const attributes: EventDidcommAnonCredsCredentialsOfferToSelfInput['attributes'] =
+        [];
+      const expectedResult =
+        {} as EventDidcommAnonCredsCredentialsOfferToSelf['data'];
+
+      natsClientMock.send.mockReturnValueOnce(
+        of(
+          new EventDidcommAnonCredsCredentialsOfferToSelf(
+            expectedResult,
+            tenantId,
+          ),
+        ),
+      );
+
+      service
+        .offerToSelf(tenantId, credentialDefinitionId, attributes)
+        .pipe(takeUntil(unsubscribe$))
+        .subscribe((result) => {
+          expect(natsClientMock.send).toHaveBeenCalledWith(
+            EventDidcommAnonCredsCredentialsOfferToSelf.token,
+            {
+              tenantId,
+              credentialDefinitionId,
+              attributes,
+            },
+          );
+
+          expect(result).toStrictEqual(expectedResult);
+
+          unsubscribe$.next();
+          unsubscribe$.complete();
+
+          done();
+        });
+    });
+  });
 });
diff --git a/apps/credential-manager/src/credential-offers/credential-offers.controller.ts b/apps/credential-manager/src/credential-offers/credential-offers.controller.ts
index 1bd6ae85733d36b6c1bd08a77a40e9b00b16ccf7..8a00d0165af0252e9cc36a7116c04d3be9114086 100644
--- a/apps/credential-manager/src/credential-offers/credential-offers.controller.ts
+++ b/apps/credential-manager/src/credential-offers/credential-offers.controller.ts
@@ -1,8 +1,10 @@
 import {
+  Body,
   Controller,
   Get,
   HttpStatus,
   Param,
+  Post,
   Query,
   UseInterceptors,
   UsePipes,
@@ -13,6 +15,7 @@ import { MultitenancyParams, ResponseFormatInterceptor } from '@ocm/shared';
 
 import { CredentialOffersService } from './credential-offers.service.js';
 import { GetByIdParams } from './dto/get-by-id.dto.js';
+import { OfferPayload } from './dto/offer.dto.js';
 
 @Controller()
 @UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
@@ -168,4 +171,178 @@ export class CredentialOffersController {
   ) {
     return this.service.getCredentialOfferById(tenantId, credentialOfferId);
   }
+
+  @Post()
+  @ApiOperation({
+    summary: 'Create a credential offer',
+    description:
+      'This call creates a credential offer for a given connection ID and credential definition ID',
+  })
+  @ApiResponse({
+    status: HttpStatus.OK,
+    description: 'Credential offer created successfully',
+    content: {
+      'application/json': {
+        schema: {},
+        examples: {
+          'Credential offer created successfully': {
+            value: {
+              statusCode: 200,
+              message: 'Credential offer created successfully',
+              data: {
+                id: '71b784a3',
+              },
+            },
+          },
+        },
+      },
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.NOT_FOUND,
+    content: {
+      'application/json': {
+        schema: {},
+        examples: {
+          'Credential offer not found': {
+            value: {
+              statusCode: 404,
+              message: 'Credential offer not found',
+              data: null,
+            },
+          },
+          'Tenant not found': {
+            value: {
+              statusCode: 404,
+              message: 'Tenant not found',
+              data: null,
+            },
+          },
+          'Credential definition not found': {
+            value: {
+              statusCode: 404,
+              message: 'Credential definition not found',
+              data: null,
+            },
+          },
+        },
+      },
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.INTERNAL_SERVER_ERROR,
+    description: 'Something went wrong',
+    content: {
+      'application/json': {
+        schema: {},
+        examples: {
+          'Something went wrong': {
+            value: {
+              statusCode: 500,
+              message: 'Something went wrong',
+              error: 'Internal Server Error',
+            },
+          },
+        },
+      },
+    },
+  })
+  public offer(
+    @Query() { tenantId }: MultitenancyParams,
+    @Body() { connectionId, credentialDefinitionId, attributes }: OfferPayload,
+  ) {
+    return this.service.offer(
+      tenantId,
+      connectionId,
+      credentialDefinitionId,
+      attributes,
+    );
+  }
+
+  @Post('self')
+  @ApiOperation({
+    summary: 'Create a credential offer to self',
+    description:
+      'This call creates a credential offer for a given credential definition ID',
+  })
+  @ApiResponse({
+    status: HttpStatus.OK,
+    description: 'Credential offer created successfully',
+    content: {
+      'application/json': {
+        schema: {},
+        examples: {
+          'Credential offer created successfully': {
+            value: {
+              statusCode: 200,
+              message: 'Credential offer created successfully',
+              data: {
+                id: '71b784a3',
+              },
+            },
+          },
+        },
+      },
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.NOT_FOUND,
+    content: {
+      'application/json': {
+        schema: {},
+        examples: {
+          'Credential offer not found': {
+            value: {
+              statusCode: 404,
+              message: 'Credential offer not found',
+              data: null,
+            },
+          },
+          'Tenant not found': {
+            value: {
+              statusCode: 404,
+              message: 'Tenant not found',
+              data: null,
+            },
+          },
+          'Credential definition not found': {
+            value: {
+              statusCode: 404,
+              message: 'Credential definition not found',
+              data: null,
+            },
+          },
+        },
+      },
+    },
+  })
+  @ApiResponse({
+    status: HttpStatus.INTERNAL_SERVER_ERROR,
+    description: 'Something went wrong',
+    content: {
+      'application/json': {
+        schema: {},
+        examples: {
+          'Something went wrong': {
+            value: {
+              statusCode: 500,
+              message: 'Something went wrong',
+              error: 'Internal Server Error',
+            },
+          },
+        },
+      },
+    },
+  })
+  public offerToSelf(
+    @Query() { tenantId }: MultitenancyParams,
+    @Body()
+    { credentialDefinitionId, attributes }: Omit<OfferPayload, 'connectionId'>,
+  ) {
+    return this.service.offerToSelf(
+      tenantId,
+      credentialDefinitionId,
+      attributes,
+    );
+  }
 }
diff --git a/apps/credential-manager/src/credential-offers/credential-offers.service.ts b/apps/credential-manager/src/credential-offers/credential-offers.service.ts
index 30c3ff6903b58a8134f532607515d3f7b9f094f2..6a5426067b428a878a3ba5d89035175f857f8ff0 100644
--- a/apps/credential-manager/src/credential-offers/credential-offers.service.ts
+++ b/apps/credential-manager/src/credential-offers/credential-offers.service.ts
@@ -1,11 +1,16 @@
 import type {
   EventAnonCredsCredentialOfferGetAllInput,
   EventAnonCredsCredentialOfferGetByIdInput,
+  EventDidcommAnonCredsCredentialsOfferInput,
+  EventDidcommAnonCredsCredentialsOfferToSelfInput,
 } from '@ocm/shared';
+import type { Observable } from 'rxjs';
 
 import { Inject, Injectable } from '@nestjs/common';
 import { ClientProxy } from '@nestjs/microservices';
 import {
+  EventDidcommAnonCredsCredentialsOffer,
+  EventDidcommAnonCredsCredentialsOfferToSelf,
   EventAnonCredsCredentialOfferGetAll,
   EventAnonCredsCredentialOfferGetById,
 } from '@ocm/shared';
@@ -39,4 +44,40 @@ export class CredentialOffersService {
       })
       .pipe(map(({ data }) => data));
   }
+
+  public offer(
+    tenantId: string,
+    connectionId: string,
+    credentialDefinitionId: string,
+    attributes: EventDidcommAnonCredsCredentialsOfferInput['attributes'],
+  ): Observable<EventDidcommAnonCredsCredentialsOffer['data']> {
+    return this.natsClient
+      .send<
+        EventDidcommAnonCredsCredentialsOffer,
+        EventDidcommAnonCredsCredentialsOfferInput
+      >(EventDidcommAnonCredsCredentialsOffer.token, {
+        tenantId,
+        connectionId,
+        credentialDefinitionId,
+        attributes,
+      })
+      .pipe(map(({ data }) => data));
+  }
+
+  public offerToSelf(
+    tenantId: string,
+    credentialDefinitionId: string,
+    attributes: EventDidcommAnonCredsCredentialsOfferToSelfInput['attributes'],
+  ): Observable<EventDidcommAnonCredsCredentialsOfferToSelf['data']> {
+    return this.natsClient
+      .send<
+        EventDidcommAnonCredsCredentialsOfferToSelf,
+        EventDidcommAnonCredsCredentialsOfferToSelfInput
+      >(EventDidcommAnonCredsCredentialsOfferToSelf.token, {
+        tenantId,
+        credentialDefinitionId,
+        attributes,
+      })
+      .pipe(map(({ data }) => data));
+  }
 }
diff --git a/apps/credential-manager/src/credential-offers/dto/offer.dto.ts b/apps/credential-manager/src/credential-offers/dto/offer.dto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..618ac356f7fa9fac4ac0fef9e4ea3bf237a4237e
--- /dev/null
+++ b/apps/credential-manager/src/credential-offers/dto/offer.dto.ts
@@ -0,0 +1,9 @@
+export class OfferPayload {
+  public connectionId: string;
+  public credentialDefinitionId: string;
+  public attributes: Array<{
+    name: string;
+    value: string;
+    mimeType?: string;
+  }>;
+}