// File: api_service.proto // // Copyright 2026 Bear Robotics, Inc. All rights reserved. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. syntax = "proto3"; package bearrobotics.api.v1.services.cloud; import "bearrobotics/api/v1/carti/conveyor.proto"; import "bearrobotics/api/v1/core/annotation.proto"; import "bearrobotics/api/v1/core/errors.proto"; import "bearrobotics/api/v1/core/fleet_selector.proto"; import "bearrobotics/api/v1/core/localization.proto"; import "bearrobotics/api/v1/core/location.proto"; import "bearrobotics/api/v1/core/map.proto"; import "bearrobotics/api/v1/core/metadata.proto"; import "bearrobotics/api/v1/core/mission.proto"; import "bearrobotics/api/v1/core/mission_status.proto"; import "bearrobotics/api/v1/core/network_status.proto"; import "bearrobotics/api/v1/core/pose.proto"; import "bearrobotics/api/v1/core/robot_status.proto"; import "bearrobotics/api/v1/core/robot_system.proto"; import "bearrobotics/api/v1/core/webhook.proto"; import "bearrobotics/api/v1/servi/tray_status.proto"; import "google/api/annotations.proto"; // APIService defines the control, navigation, monitoring, and fleet management // interface for robots. This includes mission orchestration, localization, // pose tracking, battery monitoring, and map retrieval. service APIService { // === Mission Commands ===================================================== // // Missions are atomic units of behavior that define a robot's high-level // actions. This API enables users to control robot behavior—from simple // navigation tasks to complex, conditional workflows. // Append a mission to the end of the mission queue. // // Use this when a mission is currently running; otherwise, prefer // CreateMission. Missions are executed in the order they are appended. rpc AppendMission(AppendMissionRequest) returns (AppendMissionResponse) { option (google.api.http) = {post: "/v1/mission/append"}; } // Atomically append multiple missions to the end of the mission queue. // // All missions are appended contiguously in the request order. // Missions can be appended even when other missions are queued. // // If any mission in the batch fails validation or append, no missions are appended. rpc AppendMissionBatch(AppendMissionBatchRequest) returns (AppendMissionBatchResponse) { option (google.api.http) = {post: "/v1/mission/append-batch"}; } // Instruct the robot to begin charging, regardless of its current battery // level. // // This call will fail if: // - The robot is already executing a mission. // The current mission must be canceled before issuing this command. // // Notes: // - This command is only supported on robots equipped with a contact-based // charging dock. // Robots without a compatible dock will return a INVALID_ARGUMENT error. // - Use `SubscribeBatteryStatus` to monitor the charging process. rpc ChargeRobot(ChargeRobotRequest) returns (ChargeRobotResponse) { option (google.api.http) = {post: "/v1/robot/charge"}; } // Clears the robot's mission status. // // The call will fail if the robot is on a running or paused mission. rpc ClearMissionStatus(ClearMissionStatusRequest) returns (ClearMissionStatusResponse) { option (google.api.http) = {post: "/v1/mission-status/clear"}; } // Create a new mission of a specified type. // // This call will fail if: // - The robot is already executing another mission. // - The requested mission is not compatible with the robot's current state. rpc CreateMission(CreateMissionRequest) returns (CreateMissionResponse) { option (google.api.http) = {post: "/v1/mission/create"}; } // Atomically create multiple missions. // // The returned mission IDs match the order of the missions in the request. // // This call will fail if: // - Another mission is running or queued. // - The robot is unable to start a mission. // // If any mission in the batch fails validation or creation, no missions are created. rpc CreateMissionBatch(CreateMissionBatchRequest) returns (CreateMissionBatchResponse) { option (google.api.http) = {post: "/v1/mission/create-batch"}; } // Creates a new mission workflow that mirrors touchscreen presets, // including automatic return point selection. // // This call will fail if: // - The robot is already executing another mission. // Returns a FAILED_PRECONDITION error. // - The requested workflow is not compatible with the robot's type or current state. // Returns an INVALID_ARGUMENT error. // // Returns the list of mission_ids for the created missions if successful. rpc CreateMissionWorkflow(CreateMissionWorkflowRequest) returns (CreateMissionWorkflowResponse) { option (google.api.http) = {post: "/v1/mission-workflow/create"}; } // Moves to the next destination in the current mission. // // This call will fail if: // - The robot is not on a mission. // Returns a FAILED_PRECONDITION error. // - The skip goal request could not be delivered to the robot. // Returns an INTERNAL error. // // Returns the mission_id of the current mission where the goal was skipped. rpc SkipGoal(SkipGoalRequest) returns (SkipGoalResponse) { option (google.api.http) = {post: "/v1/goal/skip"}; } // Subscribe to updates on the robot's mission status. // // Upon subscription: // - The latest known mission status is sent immediately. // - Subsequent updates are streamed as the status changes. rpc SubscribeMissionStatus(SubscribeMissionStatusRequest) returns (stream SubscribeMissionStatusResponse); // Issue a command to control or update the current mission (e.g., pause, // cancel). // // This call will fail if: // - The robot is not on the specified mission. // - The command is invalid for the robot's current state. rpc UpdateMission(UpdateMissionRequest) returns (UpdateMissionResponse) { option (google.api.http) = {post: "/v1/mission/update"}; } // === Maps & Locations ===================================================== // // Retrieve data about a robot's operating environment, including maps, // floors, annotations, and navigable destinations. // Retrieves the current map used by the robot. // // The returned map includes annotations and destinations, which can be used // in mission destination, localization goals, and navigation. // // Note: // - This API communicates directly with the robot. // - image_url may be empty, as the robot does not serve rendered images. rpc GetCurrentMap(GetCurrentMapRequest) returns (GetCurrentMapResponse) { option (google.api.http) = {post: "/v1/current-map/get"}; } // Retrieve information about a specific location by its ID. // // The location includes metadata such as floors, sections, and their // associated maps. // // Note: // - This does not require robot connectivity. rpc GetLocationInfo(GetLocationInfoRequest) returns (GetLocationInfoResponse) { option (google.api.http) = {post: "/v1/location-info/get"}; } // Retrieve a saved map by map_id from the cloud. // // Use this to fetch a stored map from the cloud database. // // Note: // - This does not require robot connectivity. rpc GetMap(GetMapRequest) returns (GetMapResponse) { option (google.api.http) = {post: "/v1/map/get"}; } // Switch the robot's current map to a specified map. // // The request must specify the floor level and section index of the desired // map. // Returns an INVALID_ARGUMENT error if there is no matching map. // Returns the map_id of the new map if successful. rpc SwitchMap(SwitchMapRequest) returns (SwitchMapResponse) { option (google.api.http) = {post: "/v1/map/switch"}; } // === Localization & Navigation ============================================ // // Provides controls and subscriptions related to a robot’s position within // its environment, including localization and pose tracking. // Localizes the robot to a known pose or destination. // // If the request is accepted, subscribe to `SubscribeLocalizationStatus` // to track localization progress. While the robot is localizing, // any subsequent requests to localize the robot will return a // FAILED_PRECONDITION error until the process is completed. rpc LocalizeRobot(LocalizeRobotRequest) returns (LocalizeRobotResponse) { option (google.api.http) = {post: "/v1/robot/localize"}; } // Update the robot's expected pose on the map. // // Unlike LocalizeRobot, the robot will not attempt to verify whether the pose // that is set align's with its camera view. rpc SetPose(SetPoseRequest) returns (SetPoseResponse) { option (google.api.http) = {post: "/v1/pose/set"}; } // Subscribe to the software emergency stop state. // // Upon subscription, the server sends the current emergency stop state of // each selected robot, followed by updates whenever an emergency stop state // changes. Each response carries the state of a single robot, identified by // its robot_id. // // Errors: // - INVALID_ARGUMENT: Both robot_id and selector are set, neither is set, // or the selector does not select any robot. rpc SubscribeEmergencyStopStatus(SubscribeEmergencyStopStatusRequest) returns (stream SubscribeEmergencyStopStatusResponse); // Subscribe to the robot’s localization status. // // Upon subscription: // - The latest localization status is sent immediately. // - Status updates are streamed while localization is active. rpc SubscribeLocalizationStatus(SubscribeLocalizationStatusRequest) returns (stream SubscribeLocalizationStatusResponse); // Subscribe to the robot's pose estimates at a regular frequency. (~10Hz) // // Use this to track the robot's estimated position in real time. rpc SubscribeRobotPose(SubscribeRobotPoseRequest) returns (stream SubscribeRobotPoseResponse); // === Robot Status ========================================================= // Streaming endpoints that provide real-time updates on robot health, // including battery levels, charging state, and connectivity. // Get the latest robot state. // // Robot state includes connectivity and operational states. rpc GetRobotStatus(GetRobotStatusRequest) returns (GetRobotStatusResponse) { option (google.api.http) = {post: "/v1/robot-state/get"}; } // Subscribe to battery status updates for the robot. // // Upon subscription: // - The latest battery state is sent immediately. // - Updates are streamed whenever the state changes. rpc SubscribeBatteryStatus(SubscribeBatteryStatusRequest) returns (stream SubscribeBatteryStatusResponse); // Subscribe to the navigation status of the selected robots. // // Upon subscription: // - The latest known navigation status is sent immediately. // - Updates are streamed when the navigation state changes. // // Each response carries the state of a single robot, identified by its // robot_id. // // The stuck state indicates the robot is unable to make navigation progress // (e.g., blocked by an obstacle or inside a restricted area) and is // distinct from a mission failure. // // Prefer this RPC over SubscribeRobotStatus when only the navigation signal // is of interest; the full robot status carries more data and updates // more frequently. rpc SubscribeNavigationStatus(SubscribeNavigationStatusRequest) returns (stream SubscribeNavigationStatusResponse); // Subscribe to the robot's connectivity and operational state. // // Upon subscription: // - The current robot status is sent immediately. // - Updates are streamed as the robot's state changes. rpc SubscribeRobotStatus(SubscribeRobotStatusRequest) returns (stream SubscribeRobotStatusResponse); // === Robot System ========================================================= // Get the overall robot system information. // // When called, the server returns robot system information. The system info // tends to be static and does not change often. rpc GetRobotSystemInfo(GetRobotSystemInfoRequest) returns (GetRobotSystemInfoResponse) { option (google.api.http) = {post: "/v0/robot-system-info/get"}; } // Execute a OS-level command on a robot. // // When rebooting the robot, a response will return immediately to acknowledge // the request but may take several minutes before the robot reconnects. rpc RunSystemCommand(RunSystemCommandRequest) returns (RunSystemCommandResponse) { option (google.api.http) = {post: "/v1/system-command/run"}; } // Subscribe to current network-related info of robots. // // Network status will include Wi-Fi and connectivity info. rpc SubscribeNetworkStatus(SubscribeNetworkStatusRequest) returns (stream SubscribeNetworkStatusResponse); // Subscribe to the cloud-to-robot connection state for a fleet of robots. // // Upon subscription: // - The latest known connection state for each selected robot is sent immediately. // - Updates are streamed as robots connect or disconnect. rpc SubscribeOnlineStatus(SubscribeOnlineStatusRequest) returns (stream SubscribeOnlineStatusResponse); // === Errors =============================================================== // Subscribe to error codes returned by the robot. rpc SubscribeErrorCodes(SubscribeErrorCodesRequest) returns (stream SubscribeErrorCodesResponse); // === Fleet Management ===================================================== // Utilities for managing multiple robots within an account or workspace. // Return a map of locations of the user has access to. // // The map consists of the location ID (key) paired with its human-readable // name (value). rpc GetAvailableLocations(GetAvailableLocationsRequest) returns (GetAvailableLocationsResponse) { option (google.api.http) = {post: "/v1/available-locations/get"}; } // Return a list of robot IDs the user has access to, filtered by optional // criteria. // // The list includes all known robots, regardless of their current connection // status. rpc ListRobotIDs(ListRobotIDsRequest) returns (ListRobotIDsResponse) { option (google.api.http) = {post: "/v1/robot-ids/list"}; } // === Servi-Specific Extensions ============================================ // These endpoints are only available for the Servi robot family. // Attempting to run a Servi command on a non-Servi robot // will result in an INVALID_ARGUMENT error. // Calibrate the trays on the robot. // // Only applicable for tray-equipped robots (e.g., Servi, Servi Plus). // // Calibrates all trays if no tray names are provided. // Returns an INVALID_ARGUMENT error and rejects the request if any tray name // is invalid. // Returns an empty response on success. rpc CalibrateTrays(CalibrateTraysRequest) returns (CalibrateTraysResponse) { option (google.api.http) = {post: "/v1/trays/calibrate"}; } // Subscribe to the robot’s tray status updates. // // Only applicable for tray-equipped robots (e.g., Servi, Servi Plus). // // Upon subscription: // - The latest known tray states are sent immediately. // - Updates are streamed when any tray state changes. // // Notes: // - Weight changes are reported with 10g precision. // - Robots without weight sensors will report UNKNOWN state and omit weight // data. rpc SubscribeTrayStatuses(SubscribeTrayStatusesRequest) returns (stream SubscribeTrayStatusesResponse); // === Conveyor ============================================================= // These endpoints are only available for Carti robot family. // Attempting to run conveyor commands on a non-Carti robot // will result in an INVALID_ARGUMENT error. // Control conveyor motor operations for the specified conveyor indexes. // // This call allows manual control of conveyor motors for // clockwise/counter-clockwise rotation or stop commands. // // This call will fail if: // - Any specified conveyor index is not installed on the robot. // - The robot is in an error state that prevents conveyor control. // // Notes: // - Carti 100: CW = right, CCW = left (X-axis rotation) // - Carti 600: CW = front→rear, CCW = rear→front (Y-axis rotation) rpc ControlConveyor(ControlConveyorRequest) returns (ControlConveyorResponse) { option (google.api.http) = {post: "/v1/conveyor/control"}; } // Retrieves the configured conveyor indexes for the robot. // // Indexes represent logical positions, not physical installation: // - Carti 100 (vertical): INDEX_1ST = uppermost conveyor // - Carti 600 (horizontal): INDEX_1ST = front facing conveyor rpc GetConveyorIndex(GetConveyorIndexRequest) returns (GetConveyorIndexResponse) { option (google.api.http) = {post: "/v1/conveyor-index/get"}; } // Subscribe to conveyor status updates for every conveyor index. // // Upon subscription: // - The latest known conveyor states are sent immediately. // - Updates are streamed when any conveyor state changes. // // Status includes: operation (rolling/stopped), payload (loaded/empty), // health (ok/error), installation (installed/not installed). // // Notes: // - Only installed conveyors report status updates. // - Payload detection may have false positives due to beam sensor // limitations. rpc SubscribeConveyorStatus(SubscribeConveyorStatusRequest) returns (stream SubscribeConveyorStatusResponse); // === Webhook ============================================================== // These endpoints define the API for managing webhooks. // They provide clients with a RESTful way of receiving streaming updates, // serving as an alternative to gRPC streaming endpoints. // Creates a new webhook subscription. // // Once created, a webhook subscription delivers an update via POST request // to the given URL each time an event matching the given type and filter // occurs on any of the selected robots. // // The delivery request payload content is customizable // via the request template option. // // The client may also provide custom HTTP headers to include in each // webhook delivery request. rpc CreateWebhook(CreateWebhookRequest) returns (CreateWebhookResponse) { option (google.api.http) = {post: "/v1/webhook/create"}; } // Deletes an existing webhook subscription by ID (soft delete). rpc DeleteWebhook(DeleteWebhookRequest) returns (DeleteWebhookResponse) { option (google.api.http) = {post: "/v1/webhook/delete"}; } // Lists all webhook subscriptions owned by the authenticated distributor. rpc ListWebhooks(ListWebhooksRequest) returns (ListWebhooksResponse) { option (google.api.http) = {post: "/v1/webhook/list"}; } } message AppendMissionBatchRequest { string robot_id = 1; repeated core.Mission missions = 2; } message AppendMissionBatchResponse { repeated string mission_ids = 1; } message AppendMissionRequest { string robot_id = 1; core.Mission mission = 2; } message AppendMissionResponse { string mission_id = 1; } message CalibrateTraysRequest { string robot_id = 1; servi.TraySelector selector = 2; } message CalibrateTraysResponse {} message ChargeRobotRequest { string robot_id = 1; } message ChargeRobotResponse { string mission_id = 1; } message ClearMissionStatusRequest { string robot_id = 1; } message ClearMissionStatusResponse { // The unique identifiers of the cleared missions. repeated string mission_ids = 1; } message ControlConveyorRequest { string robot_id = 1; repeated carti.ConveyorMotorCommand commands = 2; } message ControlConveyorResponse {} message CreateMissionBatchRequest { string robot_id = 1; repeated core.Mission missions = 2; } message CreateMissionBatchResponse { repeated string mission_ids = 1; } message CreateMissionRequest { string robot_id = 1; core.Mission mission = 2; } message CreateMissionResponse { string mission_id = 1; } message CreateMissionWorkflowRequest { string robot_id = 1; core.MissionWorkflow mission_workflow = 2; } message CreateMissionWorkflowResponse { repeated string mission_ids = 1; } message CreateWebhookRequest { // The URL to deliver webhook events to. string url = 2; // Event type to subscribe to. // Supported event types: // - "mission" // - "battery" string event_type = 3; // Required robot selector: specifies which robots this webhook targets. // Provide either explicit robot IDs or location IDs (resolved to robots // via Fleet Info). core.WebhookRobotSelector selector = 4; // Optional filter to control which events trigger delivery. core.FieldFilter filter = 5; // Optional delivery customizations (template, headers, description). core.WebhookOptions options = 6; } message CreateWebhookResponse { // The newly created webhook subscription. core.WebhookSubscription webhook = 1; } message DeleteWebhookRequest { // Unique identifier of the webhook subscription to delete. string id = 1; } message DeleteWebhookResponse {} message GetAvailableLocationsRequest {} message GetAvailableLocationsResponse { // A mapping of location ID (key) with its corresponding, human-readable // location name. // e.g. { “1D9X”: “785_Platform” } map locations = 1; } message GetConveyorIndexRequest { string robot_id = 1; } message GetConveyorIndexResponse { repeated int32 indexes = 1; } message GetCurrentMapRequest { string robot_id = 1; } message GetCurrentMapResponse { core.Map map = 1; } message GetLocationInfoRequest { // A unique identifier for the location, scoped to a Universe. Up to 36 // characters of [A-Z0-9]; newly generated IDs are 4-6 character uppercase // alphanumeric. // Examples: "LOCA", "WGXIQ", "ABC123" string location_id = 1; } message GetLocationInfoResponse { core.Location location = 1; } message GetMapRequest { string map_id = 1; } message GetMapResponse { core.Map map = 1; } message GetRobotStatusRequest { string robot_id = 1; } message GetRobotStatusResponse { core.RobotState robot_state = 1; } message GetRobotSystemInfoRequest { string robot_id = 1; } message GetRobotSystemInfoResponse { core.SystemInfo system_info = 1; } message ListRobotIDsRequest { core.RobotFilter filter = 1; } message ListRobotIDsResponse { int32 total_robots = 1; // This might not have all the robot IDs if there are too many. // It will have all the robot IDs if the number of robot_ids is same as // total_robots. repeated string robot_ids = 2; } message ListWebhooksRequest {} message ListWebhooksResponse { // The list of webhook subscriptions owned by the authenticated distributor. repeated core.WebhookSubscription webhooks = 1; } message LocalizeRobotRequest { string robot_id = 1; core.Goal goal = 2; } message LocalizeRobotResponse {} message RunSystemCommandRequest { string robot_id = 1; core.SystemCommand system_command = 2; } message RunSystemCommandResponse {} message SetPoseRequest { string robot_id = 1; core.Pose pose = 2; } message SetPoseResponse {} message SkipGoalRequest { string robot_id = 1; } message SkipGoalResponse { // The unique identifier of the mission where the goal was skipped. string mission_id = 1; } message SubscribeBatteryStatusRequest { core.RobotSelector selector = 1; } message SubscribeBatteryStatusResponse { core.EventMetadata metadata = 1; string robot_id = 2; core.BatteryState battery_state = 3; } message SubscribeConveyorStatusRequest { string robot_id = 1; } message SubscribeConveyorStatusResponse { repeated carti.ConveyorState states = 1; } message SubscribeEmergencyStopStatusRequest { // The unique robot identifier. E.g. "pennybot-abc123" // // Deprecated: use selector instead. Setting robot_id is equivalent to // setting selector.robot_ids with a single ID. It remains supported for // backwards compatibility. string robot_id = 1 [deprecated = true]; // Selects the robots to subscribe to, by explicit robot IDs or by location. // // Exactly one of robot_id or selector must be set; setting both (or // neither) returns an INVALID_ARGUMENT error. core.RobotSelector selector = 2; } message SubscribeEmergencyStopStatusResponse { core.EventMetadata metadata = 1; core.EmergencyStopState e_stop_state = 2; // ID of the robot this event pertains to. E.g. "pennybot-abc123" // Always set, including for single-robot subscriptions made via the // deprecated robot_id request field. // Note that each robot maintains its own metadata, so messages should be // correlated if and only if they correspond to the same robot ID. string robot_id = 3; } message SubscribeErrorCodesRequest { core.RobotSelector selector = 1; } message SubscribeErrorCodesResponse { // A mapping of error codes returned by individual robots. // Each entry pairs a robot ID (key) with a corresponding error code and // metadata. // Note that each robot maintains its own metadata, so messages should be // correlated if and only if they correspond to the same robot ID. map error_codes = 1; } message SubscribeLocalizationStatusRequest { string robot_id = 1; } message SubscribeLocalizationStatusResponse { core.EventMetadata metadata = 1; core.LocalizationState localization_state = 2; } message SubscribeMissionStatusRequest { core.RobotSelector selector = 1; } message SubscribeMissionStatusResponse { core.EventMetadata metadata = 1; string robot_id = 2; // Deprecated field, should use mission_states instead core.MissionState mission_state = 3; core.MissionStates mission_states = 4; } message SubscribeNavigationStatusRequest { // Selects the robots to subscribe to, by explicit robot IDs or by location. core.RobotSelector selector = 1; } message SubscribeNavigationStatusResponse { core.EventMetadata metadata = 1; // ID of the robot this event pertains to. E.g. "pennybot-abc123" // Note that each robot maintains its own metadata, so messages should be // correlated if and only if they correspond to the same robot ID. string robot_id = 2; core.NavigationState navigation_state = 3; } message SubscribeNetworkStatusRequest { core.RobotSelector selector = 1; } message SubscribeNetworkStatusResponse { // A mapping of network states reported by individual robots. // Each entry pairs a robot ID (key) with its corresponding network state and // metadata. // Note that each robot maintains its own metadata, so messages should be // correlated if and only if they correspond to the same robot ID. map network_states = 1; } message SubscribeOnlineStatusRequest { core.RobotSelector selector = 1; } message SubscribeOnlineStatusResponse { // A mapping of online states reported by individual robots. // Each entry pairs a robot ID (key) with its corresponding connection state // and metadata. // Note that each robot maintains its own metadata, so messages should be // correlated if and only if they correspond to the same robot ID. map online_states = 1; } message SubscribeRobotPoseRequest { core.RobotSelector selector = 1; } message SubscribeRobotPoseResponse { // A mapping of robot IDs to their current pose estimates. // Each entry pairs a robot ID (key) with its corresponding pose estimate and // metadata. // Note that each robot maintains its own metadata, so messages should be // correlated if and only if they correspond to the same robot ID. map poses = 2; } message SubscribeRobotStatusRequest { core.RobotSelector selector = 1; } message SubscribeRobotStatusResponse { core.EventMetadata metadata = 1; string robot_id = 2; core.RobotState robot_state = 3; } message SubscribeTrayStatusesRequest { core.RobotSelector selector = 1; } message SubscribeTrayStatusesResponse { // A mapping of tray states reported by individual robots. // Each entry pairs a robot ID (key) with its corresponding tray state and // metadata. // Note that each robot maintains its own metadata, so messages should be // correlated if and only if they correspond to the same robot ID. map tray_states = 2; } message SwitchMapRequest { string robot_id = 1; core.MapSelector map_selector = 2; } message SwitchMapResponse { string map_id = 1; } message UpdateMissionRequest { string robot_id = 1; core.MissionCommand mission_command = 2; } message UpdateMissionResponse {}