APIs that cross module boundaries, wire formats, or event streams should be designed so callers can’t accidentally construct invalid requests or rely on unstable semantics.

Apply these rules: 1) Prefer request objects/enums over ambiguous parameters

Example:

async fn report_shutdown(&self, req: ReportShutdownRequest) -> Result<()> {
    // handle only valid states; unreachable invalid combinations
}

// vs:
// async fn report_shutdown(&self, error_category: Option<String>, error_message: Option<String>) -> Result<()>;

2) Use named fields (and owned inputs) for client interfaces

Example:

pub struct InitializeRequest {
    pub user_id: String,
    pub user_email: String,
    pub crash_reporting_enabled: bool,
}

pub async fn initialize(&self, auth_token: Option<String>, req: InitializeRequest) -> Result<()>;

3) Evolve event/wire contracts compatibly

4) Be cautious with serialization changes

5) Keep public API surfaces intentional

These practices reduce misuse at the boundary, make intent obvious at call sites, and prevent subtle regressions when contracts evolve.