diff --git a/buf.gen.yaml b/buf.gen.yaml
index d5e0b8c717c46719db54857d9f848f780275d28c..3deececd84d67cc7fa80bfbddf4084b4ccbfa46c 100644
--- a/buf.gen.yaml
+++ b/buf.gen.yaml
@@ -18,6 +18,8 @@ plugins:
     out: gen/python
   - plugin: buf.build/protocolbuffers/python:v25.0
     out: gen/python
+  - plugin: buf.build/protocolbuffers/pyi:v25.0
+    out: gen/python
   - plugin: buf.build/grpc/java:v1.59.0
     out: gen/java
   - plugin: buf.build/protocolbuffers/java:v25.0
diff --git a/gen/python/quipsec/quipsec_pb2.pyi b/gen/python/quipsec/quipsec_pb2.pyi
new file mode 100644
index 0000000000000000000000000000000000000000..c3309428e2bb55ee9b64978166869f1492875eaf
--- /dev/null
+++ b/gen/python/quipsec/quipsec_pb2.pyi
@@ -0,0 +1,90 @@
+from buf.validate import validate_pb2 as _validate_pb2
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from typing import ClassVar as _ClassVar, Mapping as _Mapping, Optional as _Optional, Union as _Union
+
+DESCRIPTOR: _descriptor.FileDescriptor
+
+class Metadata(_message.Message):
+    __slots__ = ("timestamp", "vendor", "version", "keyGenerationRate", "sourceId", "destinationId")
+    TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
+    VENDOR_FIELD_NUMBER: _ClassVar[int]
+    VERSION_FIELD_NUMBER: _ClassVar[int]
+    KEYGENERATIONRATE_FIELD_NUMBER: _ClassVar[int]
+    SOURCEID_FIELD_NUMBER: _ClassVar[int]
+    DESTINATIONID_FIELD_NUMBER: _ClassVar[int]
+    timestamp: int
+    vendor: str
+    version: str
+    keyGenerationRate: int
+    sourceId: str
+    destinationId: str
+    def __init__(self, timestamp: _Optional[int] = ..., vendor: _Optional[str] = ..., version: _Optional[str] = ..., keyGenerationRate: _Optional[int] = ..., sourceId: _Optional[str] = ..., destinationId: _Optional[str] = ...) -> None: ...
+
+class KeyBulk(_message.Message):
+    __slots__ = ("keyId", "keys", "keyLength", "keyHash", "metadata")
+    KEYID_FIELD_NUMBER: _ClassVar[int]
+    KEYS_FIELD_NUMBER: _ClassVar[int]
+    KEYLENGTH_FIELD_NUMBER: _ClassVar[int]
+    KEYHASH_FIELD_NUMBER: _ClassVar[int]
+    METADATA_FIELD_NUMBER: _ClassVar[int]
+    keyId: str
+    keys: bytes
+    keyLength: int
+    keyHash: str
+    metadata: Metadata
+    def __init__(self, keyId: _Optional[str] = ..., keys: _Optional[bytes] = ..., keyLength: _Optional[int] = ..., keyHash: _Optional[str] = ..., metadata: _Optional[_Union[Metadata, _Mapping]] = ...) -> None: ...
+
+class CapabilitiesRequest(_message.Message):
+    __slots__ = ("timestamp", "version")
+    TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
+    VERSION_FIELD_NUMBER: _ClassVar[int]
+    timestamp: int
+    version: str
+    def __init__(self, timestamp: _Optional[int] = ..., version: _Optional[str] = ...) -> None: ...
+
+class CapabilitiesResponse(_message.Message):
+    __slots__ = ("timestamp", "version")
+    TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
+    VERSION_FIELD_NUMBER: _ClassVar[int]
+    timestamp: int
+    version: str
+    def __init__(self, timestamp: _Optional[int] = ..., version: _Optional[str] = ...) -> None: ...
+
+class PushKeysRequest(_message.Message):
+    __slots__ = ("timestamp", "keyBulk")
+    TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
+    KEYBULK_FIELD_NUMBER: _ClassVar[int]
+    timestamp: int
+    keyBulk: KeyBulk
+    def __init__(self, timestamp: _Optional[int] = ..., keyBulk: _Optional[_Union[KeyBulk, _Mapping]] = ...) -> None: ...
+
+class PushKeysResponse(_message.Message):
+    __slots__ = ("timestamp",)
+    TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
+    timestamp: int
+    def __init__(self, timestamp: _Optional[int] = ...) -> None: ...
+
+class QkdmMetadataRequest(_message.Message):
+    __slots__ = ("timestamp",)
+    TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
+    timestamp: int
+    def __init__(self, timestamp: _Optional[int] = ...) -> None: ...
+
+class QkdmMetadataResponse(_message.Message):
+    __slots__ = ("timestamp", "metadata")
+    TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
+    METADATA_FIELD_NUMBER: _ClassVar[int]
+    timestamp: int
+    metadata: Metadata
+    def __init__(self, timestamp: _Optional[int] = ..., metadata: _Optional[_Union[Metadata, _Mapping]] = ...) -> None: ...
+
+class ShutdownNotificationRequest(_message.Message):
+    __slots__ = ("shutdownReason",)
+    SHUTDOWNREASON_FIELD_NUMBER: _ClassVar[int]
+    shutdownReason: str
+    def __init__(self, shutdownReason: _Optional[str] = ...) -> None: ...
+
+class ShutdownNotificationResponse(_message.Message):
+    __slots__ = ()
+    def __init__(self) -> None: ...