Gomi API v1.0

Waste collection management platform โ€” driver tracking, dispatch, and administration.

Base URL https://getgomi.xyz
Driver Dispatch Admin Public

Quick Start

Get up and running with the Gomi API in minutes. All endpoints return JSON and require authentication via Bearer token (session cookie or API key).

API Client Helper

Use this minimal wrapper for all API calls:

JavaScript
const BASE = 'https://getgomi.xyz';
let API_KEY = '';

async function api(path, opts = {}) {
  const res = await fetch(BASE + path, {
    ...opts,
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
      ...opts.headers,
    },
  });
  if (!res.ok) {
    const e = await res.json().catch(() => ({}));
    throw new Error(e.error || res.statusText);
  }
  return res.json();
}

Usage Examples

JavaScript
// Get current user
const { user } = await api('/auth/me');

// Clock in as driver
await api('/api/driver/clock-in', {
  method: 'PUT',
  body: JSON.stringify({ latitude: 35.68, longitude: 139.77 }),
});

// Fetch assigned jobs
const { jobs } = await api('/api/driver/jobs?status=assigned');

// Start a trip
const { trip } = await api('/api/driver/trips/start', {
  method: 'POST',
  body: JSON.stringify({ job_id: 'job-uuid', latitude: 35.68, longitude: 139.77 }),
});

Authentication

Gomi uses Google OAuth 2.0 for user login. After authentication, you receive a session cookie. For programmatic access, create API keys.

GET /auth/login Redirect to Google OAuth โ–พ

Redirects the user to Google's OAuth consent screen. After authorization, the user is redirected back with a session cookie set.

Note
This endpoint should be opened in a browser, not called via fetch/AJAX.
Query Parameters
ParamTypeDescription
redirectstringOptional URL to redirect after login
GET /auth/me Get current user โ–พ

Returns the currently authenticated user's profile information.

Response โ€” 200 OK
JSON
{
  "user": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "email": "driver@getgomi.xyz",
    "first_name": "Tanaka",
    "last_name": "Yuki",
    "avatar_url": "https://lh3.googleusercontent.com/...",
    "role": "driver",
    "status": "online"
  }
}
POST /auth/logout End session โ–พ

Destroys the current session and clears the authentication cookie.

Response โ€” 200 OK
JSON
{
  "message": "logged out"
}

API Keys

API keys allow headless/mobile access. The raw key is shown only once at creation time.

POST /api/keys Create API key โ–พ
โš ๏ธ Important
The raw key value is returned only once. Store it securely immediately.
Request Body
JSON
{
  "name": "phone",
  "expires_in": "90d"
}
Response โ€” 201 Created
JSON
{
  "key": "gomi_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
  "api_key": {
    "id": "key-uuid-1234",
    "name": "phone",
    "key_prefix": "gomi_a1b2...s9t0"
  }
}
GET /api/keys List your API keys โ–พ
Response โ€” 200 OK
JSON
{
  "api_keys": [
    {
      "id": "key-uuid-1234",
      "name": "phone",
      "key_prefix": "gomi_a1b2...s9t0",
      "created_at": "2026-04-01T00:00:00Z",
      "expires_at": "2026-07-01T00:00:00Z",
      "last_used_at": "2026-04-14T03:22:00Z"
    }
  ]
}
DELETE /api/keys/{id} Revoke an API key โ–พ

Permanently revokes the API key. Any requests using this key will immediately fail with 401.

Response โ€” 200 OK
JSON
{
  "message": "api key revoked"
}

Driver: Shifts & Location

Drivers must clock in before accepting jobs. Location updates keep the dispatch dashboard current. Driver

PUT /api/driver/clock-in Start shift โ–พ

Clocks the driver in and sets their status to online. Requires current GPS coordinates.

Request Body
JSON
{
  "latitude": 35.6812,
  "longitude": 139.7671
}
Response โ€” 200 OK
JSON
{
  "user": {
    "id": "driver-uuid",
    "status": "online",
    "latitude": 35.6812,
    "longitude": 139.7671
  },
  "message": "clocked in"
}
PUT /api/driver/clock-out End shift โ–พ
Request Body
JSON
{
  "notes": "Finished all scheduled routes"
}
Response โ€” 200 OK
JSON
{
  "user": { "id": "driver-uuid", "status": "offline" },
  "message": "clocked out"
}
PUT /api/driver/location Update location โ–พ

Pushes a background location update outside of active trips. Use this while online but not on an active trip.

Request Body
JSON
{
  "latitude": 35.6812,
  "longitude": 139.7671
}
GET /api/driver/dashboard Driver overview โ–พ

Returns the driver's current status, active shift, and job summary.

Response โ€” 200 OK
JSON
{
  "user": { "id": "driver-uuid", "first_name": "Tanaka", "status": "online" },
  "status": "online",
  "active_shift": {
    "clocked_in_at": "2026-04-14T08:00:00Z",
    "latitude": 35.6812,
    "longitude": 139.7671
  },
  "active_jobs": [],
  "upcoming_jobs": [
    { "id": "job-1", "title": "Shibuya pickup", "status": "assigned" }
  ],
  "stats": {
    "active": 0,
    "upcoming": 1,
    "completed_today": 5,
    "total": 142
  }
}

Driver: Jobs

View and manage assigned collection jobs. Driver

GET /api/driver/jobs List jobs โ–พ
Query Parameters
ParamTypeDescription
statusstringFilter: assigned, in_progress, completed, pending
limitintMax results (default 50)
offsetintPagination offset
Response โ€” 200 OK
JSON
{
  "jobs": [
    {
      "id": "job-uuid-001",
      "title": "Shibuya Ward - Burnable",
      "address": "1-2-3 Shibuya, Shibuya-ku, Tokyo",
      "latitude": 35.6580,
      "longitude": 139.7016,
      "status": "assigned",
      "priority": "normal",
      "waste_type_name": "Burnable",
      "waste_type_icon": "๐Ÿ”ฅ"
    }
  ],
  "total": 1
}
GET /api/driver/jobs/{id} Get job detail โ–พ
Response โ€” 200 OK
JSON
{
  "job": {
    "id": "job-uuid-001",
    "title": "Shibuya Ward - Burnable",
    "address": "1-2-3 Shibuya, Shibuya-ku, Tokyo",
    "latitude": 35.6580,
    "longitude": 139.7016,
    "status": "assigned",
    "priority": "normal",
    "scheduled_date": "2026-04-14",
    "estimated_volume": 2.5,
    "waste_type_name": "Burnable",
    "waste_type_icon": "๐Ÿ”ฅ",
    "notes": "Gate code: 1234",
    "assigned_at": "2026-04-14T07:00:00Z"
  }
}
POST /api/driver/jobs/{id}/start Start a job โ–พ
Request Body
JSON
{
  "latitude": 35.6812,
  "longitude": 139.7671
}
Response โ€” 200 OK
JSON
{
  "job": { "id": "job-uuid-001", "status": "in_progress" },
  "message": "job started"
}
POST /api/driver/jobs/{id}/complete Complete a job โ–พ
Request Body
JSON
{
  "notes": "Collected 3 bags from front gate",
  "actual_volume": 3.0
}
Response โ€” 200 OK
JSON
{
  "job": { "id": "job-uuid-001", "status": "completed" },
  "message": "job completed",
  "status": "completed"
}
GET /api/driver/stats Driver statistics โ–พ
Response โ€” 200 OK
JSON
{
  "stats": {
    "total": 142,
    "pending": 0,
    "assigned": 3,
    "in_progress": 1,
    "completed": 138,
    "completed_today": 5
  }
}

Driver: Trip Tracking โญ

The trip tracking system captures high-frequency GPS telemetry from driver devices during active collections. Trips link to jobs and provide real-time visibility for dispatch. Driver

Trip Lifecycle

start โ†’ en_route โ†’ ping ร— N โ†’ arrive โ†’ arrived โ†’ complete
POST /api/driver/trips/start Start a new trip โ–พ

Initiates a trip linked to a job. The driver's status changes to on_job.

Request Body
JSON
{
  "job_id": "job-uuid-001",
  "latitude": 35.6812,
  "longitude": 139.7671
}
Response โ€” 201 Created
JSON
{
  "trip": {
    "id": "trip-uuid-001",
    "job_id": "job-uuid-001",
    "driver_id": "driver-uuid",
    "status": "en_route",
    "start_latitude": 35.6812,
    "start_longitude": 139.7671,
    "started_at": "2026-04-14T08:15:00Z"
  },
  "next_ping_ms": 3000
}
POST /api/driver/trips/{id}/ping Send GPS telemetry ping โ–พ

Compact GPS telemetry payload. Send at the interval specified by next_ping_ms in the response. Short field names minimize bandwidth on mobile networks.

Request Body
JSON
{
  "lat": 35.6815,
  "lng": 139.7680,
  "spd": 13.9,
  "hdg": 225,
  "acc": 8.5,
  "alt": 35,
  "batt": 92,
  "ts": "2026-04-14T05:00:00Z",
  "seq": 42
}
Response โ€” 200 OK
JSON
{
  "ok": true,
  "ping_count": 42,
  "stored": true,
  "next_ping_ms": 3000
}

Ping Field Reference

FieldTypeReqDescription
latfloatโœ…Latitude (-90 to 90)
lngfloatโœ…Longitude (-180 to 180)
spdfloatSpeed in meters per second
hdgfloatHeading 0โ€“360ยฐ (0=North, clockwise)
accfloatGPS accuracy in meters (lower = better)
altfloatAltitude in meters above sea level
battfloatDevice battery level 0โ€“100
tsstringISO 8601 timestamp from client clock
seqintMonotonic sequence number (for ordering)

Adaptive Ping Frequency

The server returns next_ping_ms but the client should also adapt based on speed:

SpeedInterval
> 60 km/h2 seconds
30โ€“60 km/h3 seconds
10โ€“30 km/h5 seconds
< 10 km/h10 seconds
< 2 km/h (stopped)15 seconds
POST /api/driver/trips/{id}/ping/batch Batch send pings โ–พ

Send multiple queued pings at once. Useful when the device was offline or to reduce HTTP overhead. Accepts Content-Encoding: gzip for compressed payloads.

๐Ÿ’ก Compression
For large batches (50+ pings), gzip the request body and set Content-Encoding: gzip to reduce bandwidth by ~80%.
Request Body
JSON
{
  "pings": [
    { "lat": 35.6812, "lng": 139.7671, "spd": 0, "seq": 1, "ts": "2026-04-14T08:15:00Z" },
    { "lat": 35.6815, "lng": 139.7680, "spd": 8.2, "seq": 2, "ts": "2026-04-14T08:15:05Z" },
    { "lat": 35.6820, "lng": 139.7695, "spd": 13.1, "seq": 3, "ts": "2026-04-14T08:15:08Z" }
  ]
}
Response โ€” 200 OK
JSON
{
  "ok": true,
  "ping_count": 45,
  "stored": 3,
  "skipped": 0,
  "next_ping_ms": 3000
}
POST /api/driver/trips/{id}/arrive Mark arrival at destination โ–พ
Request Body
JSON
{
  "latitude": 35.6580,
  "longitude": 139.7016
}
Response โ€” 200 OK
JSON
{
  "trip": {
    "id": "trip-uuid-001",
    "status": "arrived",
    "arrived_at": "2026-04-14T08:32:00Z"
  }
}
POST /api/driver/trips/{id}/complete Complete trip โ–พ
Request Body
JSON
{
  "latitude": 35.6580,
  "longitude": 139.7016,
  "notes": "All collected, area clean"
}
Response โ€” 200 OK
JSON
{
  "trip": {
    "id": "trip-uuid-001",
    "status": "completed",
    "completed_at": "2026-04-14T08:45:00Z",
    "distance_meters": 4250,
    "duration_seconds": 1800
  },
  "stats": {
    "ping_count": 312,
    "avg_speed": 8.5
  }
}
POST /api/driver/trips/{id}/cancel Cancel trip โ–พ
Response โ€” 200 OK
JSON
{
  "trip": {
    "id": "trip-uuid-001",
    "status": "cancelled",
    "cancelled_at": "2026-04-14T08:20:00Z"
  }
}
GET /api/driver/trips List driver's trips โ–พ
Response โ€” 200 OK
JSON
{
  "trips": [
    {
      "id": "trip-uuid-001",
      "job_id": "job-uuid-001",
      "status": "completed",
      "started_at": "2026-04-14T08:15:00Z",
      "completed_at": "2026-04-14T08:45:00Z",
      "distance_meters": 4250
    }
  ]
}
GET /api/driver/trips/{id} Get trip detail โ–พ
Response โ€” 200 OK
JSON
{
  "trip": {
    "id": "trip-uuid-001",
    "job_id": "job-uuid-001",
    "driver_id": "driver-uuid",
    "status": "completed",
    "start_latitude": 35.6812,
    "start_longitude": 139.7671,
    "end_latitude": 35.6580,
    "end_longitude": 139.7016,
    "distance_meters": 4250,
    "duration_seconds": 1800,
    "started_at": "2026-04-14T08:15:00Z",
    "arrived_at": "2026-04-14T08:32:00Z",
    "completed_at": "2026-04-14T08:45:00Z"
  },
  "stats": {
    "ping_count": 312,
    "avg_speed": 8.5
  }
}
GET /api/driver/trips/{id}/pings Get trip pings โ–พ

Returns all stored GPS pings for a trip, ordered by sequence number.

Response โ€” 200 OK
JSON
{
  "pings": [
    { "lat": 35.6812, "lng": 139.7671, "spd": 0, "hdg": 0, "acc": 5.2, "seq": 1, "ts": "2026-04-14T08:15:00Z" },
    { "lat": 35.6815, "lng": 139.7680, "spd": 8.2, "hdg": 45, "acc": 6.1, "seq": 2, "ts": "2026-04-14T08:15:05Z" }
  ],
  "trip": {
    "id": "trip-uuid-001",
    "status": "completed"
  }
}

React Native TripTracker

Complete implementation using expo-location with adaptive frequency, offline queuing, and batch uploads:

React Native / TypeScript
import * as Location from 'expo-location';

const BASE = 'https://getgomi.xyz';

interface Ping {
  lat: number; lng: number; spd: number; hdg: number;
  acc: number; alt: number; batt: number; ts: string; seq: number;
}

class TripTracker {
  private tripId: string | null = null;
  private apiKey: string;
  private seq = 0;
  private queue: Ping[] = [];
  private intervalId: ReturnType<typeof setTimeout> | null = null;
  private locationSub: Location.LocationSubscription | null = null;
  private nextPingMs = 3000;
  private lastPing: Ping | null = null;
  private sending = false;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  private async request(path: string, body?: object) {
    const res = await fetch(BASE + path, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
      },
      body: body ? JSON.stringify(body) : undefined,
    });
    if (!res.ok) {
      const e = await res.json().catch(() => ({}));
      throw new Error(e.error || res.statusText);
    }
    return res.json();
  }

  private getIntervalMs(speedMs: number): number {
    const kmh = speedMs * 3.6;
    if (kmh > 60) return 2000;
    if (kmh > 30) return 3000;
    if (kmh > 10) return 5000;
    if (kmh > 2) return 10000;
    return 15000;
  }

  async start(jobId: string): Promise<void> {
    const { status } = await Location.requestForegroundPermissionsAsync();
    if (status !== 'granted') throw new Error('Location permission denied');

    const loc = await Location.getCurrentPositionAsync({
      accuracy: Location.Accuracy.High,
    });

    const data = await this.request('/api/driver/trips/start', {
      job_id: jobId,
      latitude: loc.coords.latitude,
      longitude: loc.coords.longitude,
    });

    this.tripId = data.trip.id;
    this.nextPingMs = data.next_ping_ms || 3000;
    this.seq = 0;
    this.queue = [];

    // Start watching location
    this.locationSub = await Location.watchPositionAsync(
      {
        accuracy: Location.Accuracy.High,
        distanceInterval: 5,
        timeInterval: 1000,
      },
      (location) => {
        this.lastPing = {
          lat: location.coords.latitude,
          lng: location.coords.longitude,
          spd: location.coords.speed || 0,
          hdg: location.coords.heading || 0,
          acc: location.coords.accuracy || 0,
          alt: location.coords.altitude || 0,
          batt: 0, // integrate battery API separately
          ts: new Date(location.timestamp).toISOString(),
          seq: ++this.seq,
        };
      },
    );

    // Start ping loop
    this.schedulePing();
  }

  private schedulePing() {
    if (this.intervalId) clearTimeout(this.intervalId);
    this.intervalId = setTimeout(async () => {
      await this.sendPing();
      this.schedulePing();
    }, this.nextPingMs);
  }

  private async sendPing(): Promise<void> {
    if (!this.tripId || !this.lastPing || this.sending) return;
    this.sending = true;

    const ping = { ...this.lastPing };
    this.queue.push(ping);

    // Adaptive interval based on speed
    this.nextPingMs = this.getIntervalMs(ping.spd);

    try {
      if (this.queue.length > 1) {
        // Batch send queued pings
        const data = await this.request(
          `/api/driver/trips/${this.tripId}/ping/batch`,
          { pings: this.queue },
        );
        this.nextPingMs = data.next_ping_ms || this.nextPingMs;
        this.queue = []; // Clear queue on success
      } else {
        // Single ping
        const data = await this.request(
          `/api/driver/trips/${this.tripId}/ping`,
          ping,
        );
        this.nextPingMs = data.next_ping_ms || this.nextPingMs;
        this.queue = [];
      }
    } catch (err) {
      // Keep pings in queue for next batch attempt
      console.warn('Ping failed, queued:', this.queue.length);
    } finally {
      this.sending = false;
    }
  }

  async arrive(): Promise<any> {
    if (!this.tripId || !this.lastPing) throw new Error('No active trip');
    return this.request(`/api/driver/trips/${this.tripId}/arrive`, {
      latitude: this.lastPing.lat,
      longitude: this.lastPing.lng,
    });
  }

  async complete(notes?: string): Promise<any> {
    if (!this.tripId || !this.lastPing) throw new Error('No active trip');

    // Flush remaining pings
    if (this.queue.length > 0) {
      await this.sendPing();
    }

    const result = await this.request(
      `/api/driver/trips/${this.tripId}/complete`,
      {
        latitude: this.lastPing.lat,
        longitude: this.lastPing.lng,
        notes: notes || '',
      },
    );

    this.cleanup();
    return result;
  }

  async cancel(): Promise<any> {
    if (!this.tripId) throw new Error('No active trip');
    const result = await this.request(
      `/api/driver/trips/${this.tripId}/cancel`,
    );
    this.cleanup();
    return result;
  }

  private cleanup() {
    if (this.intervalId) clearTimeout(this.intervalId);
    if (this.locationSub) this.locationSub.remove();
    this.tripId = null;
    this.lastPing = null;
    this.queue = [];
    this.seq = 0;
  }

  get isActive(): boolean {
    return this.tripId !== null;
  }

  get currentTripId(): string | null {
    return this.tripId;
  }
}

export default TripTracker;

// --- Usage ---
// const tracker = new TripTracker('gomi_xxx...');
// await tracker.start('job-uuid-001');
// ... driving ...
// await tracker.arrive();
// ... collecting waste ...
// const result = await tracker.complete('All done');
// console.log(result.trip.distance_meters);

SSE Real-Time (Dispatch)

Server-Sent Events stream for live trip updates on the dispatch dashboard. Dispatch

GET /api/dispatch/trips/live EventSource SSE stream โ–พ

Opens a persistent SSE connection. Authenticate via query parameter since EventSource doesn't support custom headers.

Query Parameters
ParamTypeDescription
keystringAPI key: gomi_xxx...

Event Types

init
Connection established. Payload: { "active_trips": [...] } โ€” current state snapshot.
trip_start
A driver started a new trip. Payload: { "trip": {...}, "driver": {...}, "job": {...} }
ping
GPS telemetry update. Payload: { "trip_id", "lat", "lng", "spd", "hdg", "ts" }
trip_arrive
Driver arrived at destination. Payload: { "trip": {...} }
trip_complete
Trip completed. Payload: { "trip": {...}, "stats": {...} }
trip_cancel
Trip cancelled. Payload: { "trip": {...} }

React Native EventSource Example

React Native / TypeScript
import { useEffect, useRef, useCallback, useState } from 'react';

interface TripPing {
  trip_id: string;
  lat: number;
  lng: number;
  spd: number;
  hdg: number;
  ts: string;
}

interface Trip {
  id: string;
  driver_id: string;
  job_id: string;
  status: string;
  start_latitude: number;
  start_longitude: number;
}

const BASE = 'https://getgomi.xyz';

export function useDispatchSSE(apiKey: string) {
  const esRef = useRef<EventSource | null>(null);
  const [trips, setTrips] = useState<Map<string, Trip>>(new Map());
  const [latestPing, setLatestPing] = useState<TripPing | null>(null);

  const connect = useCallback(() => {
    if (esRef.current) esRef.current.close();

    const es = new EventSource(
      `${BASE}/api/dispatch/trips/live?key=${apiKey}`
    );
    esRef.current = es;

    es.addEventListener('init', (e: MessageEvent) => {
      const data = JSON.parse(e.data);
      const map = new Map<string, Trip>();
      data.active_trips.forEach((t: Trip) => map.set(t.id, t));
      setTrips(map);
    });

    es.addEventListener('trip_start', (e: MessageEvent) => {
      const { trip } = JSON.parse(e.data);
      setTrips(prev => new Map(prev).set(trip.id, trip));
    });

    es.addEventListener('ping', (e: MessageEvent) => {
      const ping: TripPing = JSON.parse(e.data);
      setLatestPing(ping);
    });

    es.addEventListener('trip_arrive', (e: MessageEvent) => {
      const { trip } = JSON.parse(e.data);
      setTrips(prev => {
        const next = new Map(prev);
        next.set(trip.id, { ...next.get(trip.id)!, ...trip });
        return next;
      });
    });

    es.addEventListener('trip_complete', (e: MessageEvent) => {
      const { trip } = JSON.parse(e.data);
      setTrips(prev => {
        const next = new Map(prev);
        next.delete(trip.id);
        return next;
      });
    });

    es.addEventListener('trip_cancel', (e: MessageEvent) => {
      const { trip } = JSON.parse(e.data);
      setTrips(prev => {
        const next = new Map(prev);
        next.delete(trip.id);
        return next;
      });
    });

    es.onerror = () => {
      es.close();
      // Reconnect after 3 seconds
      setTimeout(connect, 3000);
    };
  }, [apiKey]);

  useEffect(() => {
    connect();
    return () => esRef.current?.close();
  }, [connect]);

  return { trips, latestPing };
}

// --- Usage in a component ---
// const { trips, latestPing } = useDispatchSSE('gomi_xxx...');
// trips is a Map<tripId, Trip> of all active trips
// latestPing updates on every GPS telemetry event

Dispatch: Jobs

Create, manage, and assign collection jobs to drivers. Dispatch

POST /api/dispatch/jobs Create job โ–พ
Request Body
JSON
{
  "title": "Meguro Ward - Recyclables",
  "address": "4-5-6 Meguro, Meguro-ku, Tokyo",
  "latitude": 35.6339,
  "longitude": 139.7154,
  "waste_type_id": "wt-uuid-002",
  "priority": "high",
  "scheduled_date": "2026-04-15",
  "estimated_volume": 5.0,
  "notes": "Large apartment complex, use rear entrance"
}
Response โ€” 201 Created
JSON
{
  "job": {
    "id": "job-uuid-002",
    "title": "Meguro Ward - Recyclables",
    "status": "pending",
    "created_at": "2026-04-14T10:00:00Z"
  }
}
GET /api/dispatch/jobs List all jobs โ–พ
Query Parameters
ParamTypeDescription
statusstringFilter by status
driver_idstringFilter by assigned driver
datestringFilter by scheduled date (YYYY-MM-DD)
limitintMax results (default 50)
offsetintPagination offset
Response โ€” 200 OK
JSON
{
  "jobs": [ { "id": "...", "title": "...", "status": "pending", ... } ],
  "total": 47
}
GET /api/dispatch/jobs/{id} Get job detail โ–พ

Returns full job details including assigned driver information.

PUT /api/dispatch/jobs/{id} Update job โ–พ

Update any mutable job fields (title, address, coordinates, notes, priority, scheduled_date, estimated_volume).

POST /api/dispatch/jobs/{id}/assign Assign driver โ–พ
Request Body
JSON
{
  "driver_id": "driver-uuid"
}
Response โ€” 200 OK
JSON
{
  "job": { "id": "job-uuid-002", "status": "assigned", "driver_id": "driver-uuid" },
  "message": "job assigned"
}
POST /api/dispatch/jobs/{id}/cancel Cancel job โ–พ
Request Body
JSON
{
  "notes": "Customer cancelled pickup"
}

Dispatch: Active Trips

GET /api/dispatch/trips/active List active trips โ–พ

Returns all currently active trips with embedded driver and job info for the dispatch map.

Response โ€” 200 OK
JSON
{
  "trips": [
    {
      "id": "trip-uuid-001",
      "status": "en_route",
      "started_at": "2026-04-14T08:15:00Z",
      "latest_lat": 35.6820,
      "latest_lng": 139.7695,
      "latest_spd": 13.1,
      "driver": {
        "id": "driver-uuid",
        "first_name": "Tanaka",
        "last_name": "Yuki",
        "avatar_url": "..."
      },
      "job": {
        "id": "job-uuid-001",
        "title": "Shibuya Ward - Burnable",
        "address": "1-2-3 Shibuya, Shibuya-ku, Tokyo"
      }
    }
  ]
}
GET /api/dispatch/trips/{id}/pings Get trip pings (dispatch) โ–พ

Same as the driver endpoint but accessible to dispatch users. Returns all pings for route visualization.

Response โ€” 200 OK
JSON
{
  "pings": [
    { "lat": 35.6812, "lng": 139.7671, "spd": 0, "seq": 1, "ts": "..." },
    { "lat": 35.6815, "lng": 139.7680, "spd": 8.2, "seq": 2, "ts": "..." }
  ],
  "trip": { "id": "trip-uuid-001", "status": "en_route" }
}

Admin: Users

Manage platform users, roles, and access. Admin

GET /api/admin/users List all users โ–พ
Response โ€” 200 OK
JSON
{
  "users": [
    {
      "id": "user-uuid",
      "email": "driver@getgomi.xyz",
      "first_name": "Tanaka",
      "last_name": "Yuki",
      "role": "driver",
      "status": "online",
      "created_at": "2026-01-15T00:00:00Z",
      "last_login_at": "2026-04-14T08:00:00Z"
    }
  ],
  "total": 24
}
GET /api/admin/users/{id} Get user detail โ–พ

Returns full user profile including activity history summary.

PATCH /api/admin/users/{id}/role Change user role โ–พ
Request Body
JSON
{
  "role": "dispatch"
}

Valid roles: driver, dispatch, admin

PATCH /api/admin/users/{id}/status Change user status โ–พ
Request Body
JSON
{
  "status": "suspended"
}

Valid statuses: offline, online, suspended

Admin: Invites

POST /api/admin/invites Send invite โ–พ
Request Body
JSON
{
  "email": "newdriver@example.com",
  "role": "driver"
}
Response โ€” 201 Created
JSON
{
  "invite": {
    "id": "invite-uuid",
    "email": "newdriver@example.com",
    "role": "driver",
    "invited_by": "admin-uuid",
    "created_at": "2026-04-14T10:00:00Z",
    "expires_at": "2026-04-21T10:00:00Z"
  }
}
GET /api/admin/invites List invites โ–พ

Returns all pending and expired invites.

DELETE /api/admin/invites/{id} Revoke invite โ–พ

Revokes a pending invite so it can no longer be used.

Admin: Audit & Keys

GET /api/admin/audit/auth Auth audit log โ–พ

Returns authentication events (login, logout, key usage) for all users.

Response โ€” 200 OK
JSON
{
  "events": [
    {
      "id": "evt-uuid",
      "user_id": "user-uuid",
      "email": "driver@getgomi.xyz",
      "event_type": "login",
      "ip_address": "203.0.113.42",
      "user_agent": "Mozilla/5.0 ...",
      "created_at": "2026-04-14T08:00:00Z"
    }
  ],
  "total": 1502
}
GET /api/admin/keys List all API keys โ–พ

Returns all API keys across all users. Admins can see key metadata but not raw key values.

DELETE /api/admin/keys/{id} Revoke any API key โ–พ

Admins can revoke any user's API key. The key is immediately invalidated.

Reference: Waste Types

Public

GET /api/waste-types List waste types โ–พ
Response โ€” 200 OK
JSON
{
  "waste_types": [
    {
      "id": "wt-uuid-001",
      "name": "Burnable",
      "name_local": "็‡ƒใˆใ‚‹ใ‚ดใƒŸ",
      "color": "#ef4444",
      "icon": "๐Ÿ”ฅ",
      "country_code": "JP"
    },
    {
      "id": "wt-uuid-002",
      "name": "Recyclable",
      "name_local": "่ณ‡ๆบใ‚ดใƒŸ",
      "color": "#3b82f6",
      "icon": "โ™ป๏ธ",
      "country_code": "JP"
    },
    {
      "id": "wt-uuid-003",
      "name": "Non-Burnable",
      "name_local": "็‡ƒใˆใชใ„ใ‚ดใƒŸ",
      "color": "#6b7280",
      "icon": "๐Ÿชจ",
      "country_code": "JP"
    },
    {
      "id": "wt-uuid-004",
      "name": "Oversized",
      "name_local": "็ฒ—ๅคงใ‚ดใƒŸ",
      "color": "#f59e0b",
      "icon": "๐Ÿ“ฆ",
      "country_code": "JP"
    }
  ]
}

Status Flows

User Status

offline โ†’ online โ†’ on_job โ†’ online โ†’ offline

Job Status

pending โ†’ assigned โ†’ in_progress โ†’ completed

Trip Status

en_route โ†’ arrived โ†’ completed

Or from any state โ†’ cancelled

Error Handling

All errors return a consistent JSON structure with an appropriate HTTP status code:

Error Response
{
  "error": "Human-readable error message"
}
StatusMeaningExample
400Bad RequestMissing required field, invalid format
401UnauthorizedMissing or invalid API key / session
403ForbiddenInsufficient role (driver accessing admin endpoint)
404Not FoundResource doesn't exist
409ConflictAlready clocked in, trip already active
500Server ErrorInternal server error
Error Handling Example
try {
  await api('/api/driver/clock-in', {
    method: 'PUT',
    body: JSON.stringify({ latitude: 35.68, longitude: 139.77 }),
  });
} catch (err) {
  if (err.message === 'already clocked in') {
    // Handle 409 conflict
    console.log('Driver is already on shift');
  } else {
    console.error('Clock-in failed:', err.message);
  }
}

Health Check

GET /health Service health โ–พ

Public endpoint for monitoring. No authentication required.

Response โ€” 200 OK
JSON
{
  "status": "ok"
}

Gomi API Documentation โ€” Built for waste collection in Japan ๐Ÿ‡ฏ๐Ÿ‡ต

ยฉ 2026 getgomi.xyz