Marita Universe API
This page describes how external clients interact with the Marita Universe universe. It is intentionally practical: endpoints, payloads, and concrete examples.
Base URLs
| Environment | Base | Notes |
|---|---|---|
| Local emulator | http://127.0.0.1:5001/<projectId>/us-central1 | Functions emulator default region is us-central1. |
| Production | https://us-central1-<projectId>.cloudfunctions.net | Exact region may change later; this doc will track it. |
Authentication
For MVP, the endpoints below are implemented as plain HTTPS functions. Production access to asset and order endpoints requires a Firebase ID token: Authorization: Bearer <idToken>.
In the local emulator, authentication is permissive (most endpoints will work without a token). In production, most endpoints return 401 unauthorized unless a valid Firebase ID token is provided.
Ownership model is explicit and simple: one user account owns exactly one asset. In production, authenticated requests are restricted to that single owned asset. Order endpoints must reference the caller's owned asset.
Local emulator runs permissively to support development workflows and smoke tests.
Auth onboarding (how to get an ID token)
In production, a Marita client should authenticate with Firebase Auth, then include the returned ID token in API requests: Authorization: Bearer <idToken>.
Local emulator note: auth is permissive, so most endpoints work without a token. You can still use the Auth emulator to test a realistic flow.
Production note: the REST login flow requires your Firebase Web API key. Do not hardcode secrets; treat API keys as public but still keep them out of logs.
Orders & lifecycle
Orders are asynchronous. Order endpoints return a receipt immediately, but the simulation worker applies orders on later ticks.
Firestore storage: universe/default/orders/{orderId}. Fields include: status (queued | accepted | rejected), optional reject_reason, and timestamps.
Known limit: the simulation worker fetches and processes up to 10 queued orders per tick. If you submit more, processing may be delayed across multiple ticks.
Client strategy: - If you have Firestore access: poll the order doc by order_id until status is not queued. - If you only use the Functions API: infer effects by polling GET /getObjectState (e.g. for thrust: active_burns updates; for cancel thrust: active_burns clears).
Known limitation: there is currently no GET /getOrder endpoint, so clients that need explicit order status should use Firestore directly.
Idempotency: most order-like endpoints accept idempotency_key. If provided and valid, repeated submissions return the same order/event id. The key is trimmed and must be 1–200 characters. If it is missing or invalid, the request is treated as non-idempotent.
Simulation worker
The simulation is advanced by a separate simulation worker service (Cloud Run in production; local Node process in the emulator stack).
Time convention: the API reports zulu_time_utc as server UTC shifted +15 years (in-universe clock). It also reports in_game_time_utc derived from the simulation worker's latest tick time.
Timing: - Target tick rate is ~1 Hz. - The worker uses a lease lock so only one instance ticks at a time. - GET /getUniverseTime reports the most recent tickNumber and tSim.
Latency expectations: - Orders are usually applied within 0–2 seconds locally when the worker is healthy. - In production, latency depends on Cloud Run scheduling and Firestore load. - If the worker is stopped, orders remain queued and object state will not advance.
Data model
The MVP uses a single universe document and subcollections:
Units: positions are km, velocities are km/s, simulation time values are seconds. Order thrust vectors are in m/s^2 in the universe inertial axes.
Endpoints
All responses are JSON with cache-control: no-store.
GET /healthz
Basic liveness for the API layer.
Auth: none.
Errors: 405 method_not_allowed.
GET /getUniverseTime
Returns the latest known simulation time and tick number.
Auth: none. Known limits: time is only updated when the simulation worker is running and holding the lease.
Errors: 405 method_not_allowed, 500 internal.
GET /getBodies
Returns body metadata for the seeded solar system. Use body_ids to request a subset.
Auth: none. Outputs: body docs excluding each body's ephemeris cache.
Known limits: if body_ids is omitted, all bodies are returned. Errors: 405 method_not_allowed, 500 internal.
POST /getBodiesEphemeris
Returns interpolated position/velocity for bodies at time t (seconds). This uses each body's position_cache.
Auth: none. Requirements: body_ids must be a comma-separated string or an array with at least 1 id. t must be finite. Known limits: this calls Firestore getAll; very large body_ids arrays are not recommended.
Errors: 400 invalid_json, 400 missing_body_ids, 400 invalid_time, 405 method_not_allowed, 500 internal.
POST /getSensors
Returns mission-relevant sensor detections for an observer object. MVP returns visual + IR detections sourced from major bodies, with light-speed delay and inverse-square attenuation.
When other objects have beacon_enabled (default: true), they are returned as kind: "beacon" detections with t_emission computed using light-speed propagation (t_emission = t_observe - range / c).
Radio transmissions are returned as kind: "radio" detections when a radio message's wavefront intersects the observer at t_observe. Detections include beam gating (omni/cone/pencil), occlusion by bodies (planet/moon shadows), and inverse-square attenuation. A receiver-based deep-space fade is applied beyond Neptune orbit.
Auth: none. Requirements: observer_object_id must exist and must have position. Optional: t_observe defaults to server time (seconds). Optional: max_range_km is clamped to [0, 1e12]. Known limits: body detections use 2 iterations of retarded-time correction (sufficient for slow-moving bodies). beacon detections assume constant velocity for each object across the light-time correction. radio detections query at most 200 signal events per call and only within a lookback window of: (max_range_km / c + 3600) seconds when max_range_km is provided, otherwise 24 hours. radio payloads are returned only if payload.length <= 4096 (otherwise payload: null).
Errors: 400 invalid_json, 400 missing_observer_object_id, 404 observer_not_found, 400 observer_missing_position, 405 method_not_allowed, 500 internal.
GET /getObjectState
Returns the current state of a single asset.
The response includes beacon_enabled and operational_status. For ships, the response also includes ship_class, fuel_kg, fuel_capacity_kg, hull_points, hull_points_max, active_burns, drive_status, and backup_power_hours.
Auth: required in production. In production, you may only read the single asset you own. Errors: 400 missing_object_id, 401 unauthorized, 403 forbidden, 404 object_not_found.
Additional errors: 405 method_not_allowed, 500 internal.
GET /listAssets
Returns a list of assets, optionally filtered by owner and type.
Auth: required in production. In production, this endpoint returns at most your single owned asset. Query parameters: limit defaults to 50 and is clamped to [1, 200]. owner_user_id filtering is ignored in production.
Response fields include ship_class when object_type is "ship", as well as hull_points and hull_points_max.
Successful responses include a time block with zulu_time_utc (server UTC) and in_game_time_utc (latest simulation tick time).
Errors: 401 unauthorized, 403 no_owned_asset, 403 multiple_owned_assets, 405 method_not_allowed, 500 internal.
POST /issueThrustOrder
Issues a propulsion order for an asset. Returns an order receipt.
asset_object_id should be the ship's object_id (typically the same as the document id). thrust_vector is acceleration in m/s^2 in the universe inertial axes. duration_s is in seconds. A ship can have only one active thrust at a time; issuing a new thrust order replaces any previous thrust. Setting thrust_vector to [0,0,0] is a valid cancel command and will clear any active thrust. For non-zero thrust, duration_s must be > 0. For cancel commands, duration_s can be 0 or omitted.
Auth: required in production. Idempotency: idempotency_key (max 200 chars) makes repeated submits return the same order id. Validation: thrust_vector must be a finite 3-vector. duration_s is clamped to [0, 86400]; non-cancel orders require > 0. Known limits: orders are queued and applied by the simulation worker on a later tick. If multiple queued thrust orders exist for the same ship at once, the worker applies only the latest and marks others as rejected with reject_reason: "superseded". Applied acceleration is clamped by the ship's drive max_acceleration_g and fuel consumption scales with the applied acceleration (in g) and the ship's fuel_rate_kg_per_sec_per_g.
Errors: 400 invalid_json, 400 missing_asset_object_id, 400 invalid_thrust_vector, 400 invalid_duration_s, 401 unauthorized, 403 forbidden, 403 no_owned_asset, 403 multiple_owned_assets, 405 method_not_allowed, 500 internal.
POST /issueFireOrder
Issues an engagement order for an asset. Returns an order receipt.
Auth: required in production. Idempotency: idempotency_key (max 200 chars) makes repeated submits return the same order id. Known limits: this order is currently only persisted to Firestore; the simulation worker does not execute fire orders yet.
Validation: weapon, aim, target, and rounds are currently treated as opaque payload fields and are not validated. Errors: 400 invalid_json, 400 missing_asset_object_id, 401 unauthorized, 403 forbidden, 405 method_not_allowed, 500 internal.
POST /issueCommsTransmission
Emits an interceptable radio message as a persisted signal event.
power_w controls range (inverse-square attenuation). Use beam_pattern + beam_axis_unit + half_angle_rad for cone/pencil beams. The message payload is treated as opaque; clients may encrypt it.
Auth: required in production. Validation and defaults: message is required, max length 20000. power_w must be > 0. duration_s defaults to 1 and is clamped to [1, 3600]. retention_s defaults to 86400 (24h) and is capped at 604800 (7d). beam_pattern defaults to "omni". For non-omni beams, beam_axis_unit (a finite 3-vector) and half_angle_rad (> 0, <= pi/2) are required. Known limits: signals are stored as discrete events; receivers detect them via POST /getSensors.
Errors: 400 invalid_json, 400 missing_asset_object_id, 404 asset_not_found, 400 asset_missing_position, 400 invalid_power_w, 400 invalid_beam_axis_unit, 400 invalid_half_angle_rad, 400 missing_message, 401 unauthorized, 403 forbidden, 405 method_not_allowed, 500 internal.
POST /mintToken
Mints a signed consumable token and persists it to Firestore. Emulator-only for now.
Auth: none. Emulator-only: production returns 403 forbidden. Limits: token_type max length 40. amount must be > 0 and <= 1e18. expires_in_s (optional) is capped at 365 days. idempotency_key max length 200.
Errors: 400 invalid_json, 400 invalid_token_type, 400 invalid_amount, 400 missing_owner_user_id, 403 forbidden, 405 method_not_allowed, 500 token_signing_unavailable, 500 internal.
POST /verifyToken
Verifies a signed token and returns its decoded payload.
Auth: none. Known limits: verification uses the configured keypair; in the emulator, keys are generated on first use for that process.
Errors: 400 invalid_json, 400 missing_token, 405 method_not_allowed, 500 token_verification_unavailable, 500 internal.
POST /spendToken
Marks a token as spent to prevent reuse.
Auth: required in production. Errors: 400 invalid_token, 409 already_spent, 409 token_not_found, 403 forbidden.
Additional errors: 400 invalid_json, 400 missing_token, 400 invalid_token_payload, 401 unauthorized, 405 method_not_allowed, 500 token_verification_unavailable, 500 internal.
POST /transferToken
Transfers a token by spending the source token and minting a new token for the recipient.
Auth: required in production. Idempotency: if idempotency_key is provided, repeated calls return the same to_token_id. Errors: 400 invalid_token, 400 invalid_to_user_id, 409 already_spent, 409 token_not_found, 403 forbidden.
Additional errors: 400 invalid_json, 400 missing_token, 400 invalid_token_payload, 401 unauthorized, 405 method_not_allowed, 500 token_verification_unavailable, 500 internal.
POST /closedBetaSignup
Registers interest for closed beta access. Stores signup in database with typical 3-day response time.
Auth: none. Fields: email (required, max 254 chars, must be valid format). name (required, max 100 chars). experience (required, one of: "none", "some", "extensive", "professional"). why (optional, max 500 chars). Idempotent: same email returns existing signup without error.
Response: { ok, signup_id, created, message }. Typical response time is 3 days.
Errors: 400 invalid_json, 400 missing_email, 400 invalid_email, 400 missing_name, 400 name_too_long, 400 missing_experience, 400 invalid_experience, 400 why_too_long, 405 method_not_allowed, 500 internal.
Examples
Replace marita-online with your project id if needed.
Local emulator: health
Local emulator: universe time
Local emulator: bodies ephemeris
Local emulator: sensors
Local emulator: object state
Local emulator: list assets
Local emulator: issue thrust order
Local emulator: cancel thrust
Local emulator: issue fire order
Local emulator: issue comms transmission
Local emulator: mint + spend token
Status & stability
The Marita Universe API is under active development. Expect changes; this page is the public contract and will be kept up to date.