diff --git a/internal/frontend/api/api.proto b/internal/frontend/api/api.proto
new file mode 100644
index 0000000..0c8a42e
--- /dev/null
+++ b/internal/frontend/api/api.proto
@@ -0,0 +1,238 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+syntax = "proto3";
+
+package pkgsite.api.v1;
+
+import "google/protobuf/timestamp.proto";
+
+option go_package = "google.golang.org/pkgsite/api/v1";
+
+// PkgsiteService provides access to module and package information.
+service PkgsiteService {
+ // GetModuleInfo retrieves detailed information about a specific module version.
+ rpc GetModuleInfo(ModuleInfoRequest) returns (ModuleInfo);
+
+ // GetMajorVersions retrieves a list of major versions for a module path.
+ rpc GetMajorVersions(MajorVersionsRequest) returns (MajorVersions);
+
+ // Search performs a search for packages and modules.
+ rpc Search(SearchRequest) returns (SearchResults);
+}
+
+// --- Common Messages ---
+
+// PathInfo holds information about a package or module path.
+message PathInfo {
+ string path = 1;
+ string module_path = 2;
+ string version = 3;
+ string kind = 4;
+}
+
+// --- GetModuleInfo ---
+
+message ModuleInfoRequest {
+ // The module path.
+ string path = 1;
+ // The module version, or "latest".
+ string version = 2;
+ // If non-nil, ModuleInfo.details will be populated.
+ optional ModuleDetailsRequest details = 3;
+ // If non-nil, ModuleInfo.licenses will be populated.
+ optional LicensesRequest licenses = 4;
+ // If non-nil, ModuleInfo.packages will be populated.
+ optional PackagesRequest packages = 5;
+}
+
+message ModuleInfo {
+ // Same as ModuleInfoRequest.path.
+ string path = 1;
+ // Same as ModuleInfoRequest.version, or the resolved latest version.
+ string version = 2;
+ ModuleDetails details = 3;
+ Licenses licenses = 4;
+ Packages packages = 5;
+}
+
+message ModuleDetailsRequest {
+ // If true, ModuleDetails.readme_contents will be populated.
+ bool with_readme = 1;
+}
+
+message ModuleDetails {
+ // As returned by the proxy’s .info endpoint.
+ google.protobuf.Timestamp commit_time = 1;
+ // Path of README, relative to module root.
+ string readme_file_path = 2;
+ // Raw contents, if include_readme is true.
+ bytes readme_contents = 3;
+ // Whether the module has a go.mod file.
+ bool has_go_mod = 4;
+ // Can be nil.
+ SourceInfo source_info = 5;
+ bool is_redistributable = 6;
+}
+
+message SourceInfo {
+ // URL of repo containing module.
+ string repo_url = 1;
+ // URL of module directory; append subdir to get subdir URL.
+ string module_url = 2;
+ // Tag or ID of commit (revision) corresponding to version.
+ string commit_id = 3;
+}
+
+message LicensesRequest {
+ // If true, License.contents will be populated.
+ bool with_contents = 1;
+ // Maximum number of items to return; if < 0, all are returned.
+ int64 max = 2;
+ // First item to return; must come from a previous response's next_from field.
+ string from = 3;
+}
+
+message Licenses {
+ // No more than LicensesRequest.max items.
+ repeated License items = 1;
+ // Total number of items, disregarding max and from.
+ int64 total = 2;
+ // If non-empty, more are available; set to `from` in request to retrieve.
+ string next = 3;
+}
+
+message License {
+ // Path to license file from module root.
+ string file_path = 1;
+ // License types, as determined by our detector.
+ repeated string types = 2;
+ // The file’s bytes, if with_contents is true.
+ bytes contents = 3;
+}
+
+message PackagesRequest {
+ // RelativePath is relative to the module path.
+ // If empty, return all packages in the module.
+ // If RelativePath ends in "/", return all packages with RelativePath as a prefix.
+ // Otherwise, return the single package whose import path is modulePath/RelativePath.
+ string relative_path = 1;
+ optional PackageDetailsRequest details = 2;
+ optional LicensesRequest licenses = 3;
+ optional ImportsRequest imports = 4;
+ optional ImportersRequest importers = 5;
+ int64 max = 6;
+ string from = 7;
+}
+
+message Packages {
+ repeated Package items = 1;
+ int64 total = 2;
+ string next = 3;
+}
+
+message Package {
+ // Import path.
+ string path = 1;
+ PackageDetails details = 2;
+ Licenses licenses = 3;
+ Imports imports = 4;
+ Importers importers = 5;
+}
+
+message PackageDetailsRequest {
+ // If true, PackageDetails.doc_html will be populated.
+ bool with_doc_html = 1;
+}
+
+message PackageDetails {
+ // As distinct from its import path.
+ string name = 1;
+ // From the doc.
+ string synopsis = 2;
+ // If with_doc_html is true.
+ bytes doc_html = 3;
+ // Value of GOOS used to process this package.
+ string goos = 4;
+ // Value of GOARCH used to process this package.
+ string goarch = 5;
+ bool is_redistributable = 6;
+}
+
+message ImportsRequest {
+ int64 max = 1;
+ string from = 2;
+}
+
+message Imports {
+ // Kind is always "package". ModulePath and Version may be empty.
+ repeated PathInfo items = 1;
+ int64 total = 2;
+ string next = 3;
+}
+
+message ImportersRequest {
+ int64 max = 1;
+ string from = 2;
+}
+
+message Importers {
+ // Kind is always "package". ModulePath and Version may be empty.
+ repeated PathInfo items = 1;
+ // May be -1 if the total cannot be determined.
+ int64 total = 2;
+ string next = 3;
+}
+
+// --- GetMajorVersions RPC ---
+
+message MajorVersionsRequest {
+ // The path of the module. The "/vN" suffix is ignored.
+ string module_path = 1;
+ // If non-empty, only versions of the module that contain this import path will be returned.
+ string package_path = 2;
+ // If true, pseudo-versions are not returned.
+ bool exclude_pseudo = 3;
+ int64 max = 4;
+ string from = 5;
+}
+
+message MajorVersions {
+ // Ordered from highest to lowest.
+ repeated MajorVersionInfo versions = 1;
+ int64 total = 2;
+ string next = 3;
+}
+
+message MajorVersionInfo {
+ string module_path = 1;
+ string version = 2;
+ google.protobuf.Timestamp commit_time = 3;
+}
+
+// --- Search ---
+
+message SearchRequest {
+ string query = 1;
+ string symbol = 2;
+ int64 offset = 3;
+ bool search_symbols = 4;
+ int64 max = 5;
+ string from = 6;
+}
+
+message SearchResults {
+ repeated SearchResult results = 1;
+ int64 total = 2;
+ string next = 3;
+}
+
+message SearchResult {
+ // Module path.
+ string path = 1;
+ // Short summary of the module details.
+ string synopsis = 2;
+ // Latest module version.
+ string version = 3;
+}