How to Get an Address ID

Overview

When creating a ride in the SafeRide API, you need to provide pickup_address_id and dropoff_address_id. However, the UI typically only has address details like formatted address strings, latitude, and longitude. This tutorial explains how to obtain the necessary address IDs.

The Answer

Yes, addresses must be created via a separate API endpoint first, then their IDs are used when creating rides.

The SafeRide API follows a Separate Addresses API Endpoint architecture. The client must:

  1. Create the pickup and dropoff addresses via POST /api/v1/addresses
  2. Receive the address_id values from the responses
  3. Use those IDs when creating the ride via POST /api/v1/rides

Architecture

Database Schema

Ride Model (app/models/ride.rb:16-17):

  • belongs_to :pickup_address, class_name: 'Address'
  • belongs_to :dropoff_address, class_name: 'Address'
  • Validates presence of both pickup_address_id and dropoff_address_id (line 204)

Address Model (app/models/address.rb:1-27):

  • belongs_to :host, class_name: 'Account' (line 2)
  • Has many rides through pickup_rides and dropoff_rides (lines 7-8)
  • Validates: latitude, longitude, street_number, street_name, city, state, zip, country, host_id
  • Automatically populates county for US addresses using CountyLookupService

Complete API Flow

Step 1: Create Pickup Address

Request:

POST /api/v1/addresses
Authorization: Bearer <jwt_token>
Content-Type: application/json

{
  "latitude": 39.4143,
  "longitude": -77.4105,
  "street_number": "122",
  "street_name": "E Patrick St",
  "city": "Frederick",
  "state": "MD",
  "zip": "21705",
  "country": "United States"
}

Note: The host_id field is omitted here as this is a typical user pickup address, not a registered venue.

Response (201 Created or 200 OK):

{
  "data": {
    "attributes": {
      "id": 456,
      "latitude": 39.4143,
      "longitude": -77.4105,
      "street_number": "122",
      "street_name": "E Patrick St",
      "city": "Frederick",
      "state": "MD",
      "county": "Frederick County",
      "zip": "21705",
      "country": "United States",
      "host_id": null,
      "created_at": "2025-11-05T12:00:00Z",
      "updated_at": "2025-11-05T12:00:00Z",
      "host": null
    }
  }
}

Note: The API returns 201 Created for new addresses and 200 OK for existing addresses. The response structure is identical in both cases, so you can extract the address ID the same way regardless of status code.

Extract the address ID:

const pickup_address_id = response.data.attributes.id; // 456

Step 2: Create Dropoff Address

Request:

POST /api/v1/addresses
Authorization: Bearer <jwt_token>
Content-Type: application/json

{
  "latitude": 39.4200,
  "longitude": -77.4300,
  "street_number": "200",
  "street_name": "Monroe Ave",
  "city": "Frederick",
  "state": "MD",
  "zip": "21701",
  "country": "United States"
}

Response (201 Created or 200 OK):

{
  "data": {
    "attributes": {
      "id": 789,
      "latitude": 39.4200,
      "longitude": -77.4300,
      "street_number": "200",
      "street_name": "Monroe Ave",
      "city": "Frederick",
      "state": "MD",
      "county": "Frederick County",
      "zip": "21701",
      "country": "United States",
      "host_id": null,
      "created_at": "2025-11-05T12:01:00Z",
      "updated_at": "2025-11-05T12:01:00Z",
      "host": null
    }
  }
}

Extract the address ID:

const dropoff_address_id = response.data.attributes.id; // 789

Step 3: Create Ride with Address IDs

Request:

POST /api/v1/rides
Authorization: Bearer <jwt_token>
X-Idempotency-Key: <unique-uuid>
Content-Type: application/json

{
  "ride": {
    "vehicle_id": 1,
    "pickup_address_id": 456,
    "dropoff_address_id": 789,
    "payment_method": "card"
  }
}

Response (201 Created):

{
  "data": {
    "attributes": {
      "id": 999,
      "status": "requested",
      "passenger_id": 123,
      "vehicle_id": 1,
      "pickup_address_id": 456,
      "dropoff_address_id": 789,
      "payment_method": "card",
      "created_at": "2025-11-05T12:02:00Z",
      "updated_at": "2025-11-05T12:02:00Z"
    }
  }
}

Implementation Details

Address Deduplication

Current behavior: The API DOES automatically deduplicate addresses using a find-or-create pattern. When you POST to /api/v1/addresses, the controller checks if an address with matching normalized fields already exists.

How it works:

  1. The controller extracts normalized address attributes (street_number, street_name, city, state, zip, country)
  2. It searches for an existing address matching these fields
  3. If found, it returns the existing address with 200 OK
  4. If not found, it creates a new address and returns 201 Created

What makes addresses unique:

Addresses are considered duplicates if they match on these fields:

  • street_number
  • street_name
  • city
  • state
  • zip
  • country

Excluded from deduplication:

  • latitude and longitude (coordinates can vary slightly for the same address)
  • host_id (multiple users can share the same physical address)

Important: If the same address already exists in the system, you’ll receive the existing address ID back. This means you can safely call POST /api/v1/addresses without worrying about creating duplicates.

Reference: app/controllers/api/v1/addresses_controller.rb:30-53,118-127

Host ID (Optional)

The host_id field is optional and should only be used for public venues that have a registered account with the “host” role in their roles array.

When to use host_id:

  • The address belongs to a registered venue/business
  • The venue’s account has the “host” role

When NOT to use host_id:

  • Regular user pickup/dropoff addresses
  • Residential addresses
  • Any non-venue locations

Reference: app/models/address.rb:2 (note the optional: true)

County Auto-Population

For US addresses, if county is not provided, it will be automatically populated using the CountyLookupService based on the coordinates.

Reference: app/models/address.rb:17-26

Validation Requirements

Address creation will fail if:

  • Latitude not in range -90 to 90
  • Longitude not in range -180 to 180
  • Missing any of these required fields: street_number, street_name, city, state, zip, country

Optional fields:

  • host_id - Only for registered venue addresses with “host” role
  • county - Auto-populated for US addresses if not provided

Reference: app/models/address.rb:10-14


Error Handling

Scenario 1: Invalid Address Data

If address creation fails (e.g., invalid coordinates):

Response (422 Unprocessable Content):

{
  "error": {
    "message": "Validation failed",
    "details": {
      "latitude": ["must be less than or equal to 90"],
      "street_number": ["can't be blank"]
    }
  }
}

Action: Show validation errors to user, fix input, retry

Scenario 2: Ride Creation with Invalid Address IDs

If you try to create a ride with non-existent address IDs, the database foreign key constraint will fail.

Action: Ensure addresses are successfully created before attempting ride creation


Code Example (JavaScript/TypeScript)

Here’s a complete example of creating a ride with addresses:

async function createRideWithAddresses(pickupData, dropoffData, vehicleId, authToken) {
  try {
    // Step 1: Create pickup address (or get existing one - API handles deduplication)
    const pickupResponse = await fetch('https://api.saferide.com/api/v1/addresses', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${authToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        latitude: pickupData.lat,
        longitude: pickupData.lng,
        street_number: pickupData.streetNumber,
        street_name: pickupData.streetName,
        city: pickupData.city,
        state: pickupData.state,
        zip: pickupData.zip,
        country: pickupData.country
        // host_id is optional - only include for registered venue addresses
      })
    });

    if (!pickupResponse.ok) {
      throw new Error('Failed to create pickup address');
    }

    const pickupResult = await pickupResponse.json();
    const pickupAddressId = pickupResult.data.attributes.id;

    // Step 2: Create dropoff address (or get existing one - API handles deduplication)
    const dropoffResponse = await fetch('https://api.saferide.com/api/v1/addresses', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${authToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        latitude: dropoffData.lat,
        longitude: dropoffData.lng,
        street_number: dropoffData.streetNumber,
        street_name: dropoffData.streetName,
        city: dropoffData.city,
        state: dropoffData.state,
        zip: dropoffData.zip,
        country: dropoffData.country
        // host_id is optional - only include for registered venue addresses
      })
    });

    if (!dropoffResponse.ok) {
      throw new Error('Failed to create dropoff address');
    }

    const dropoffResult = await dropoffResponse.json();
    const dropoffAddressId = dropoffResult.data.attributes.id;

    // Step 3: Create ride with address IDs
    const rideResponse = await fetch('https://api.saferide.com/api/v1/rides', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${authToken}`,
        'Content-Type': 'application/json',
        'X-Idempotency-Key': generateUUID()
      },
      body: JSON.stringify({
        ride: {
          vehicle_id: vehicleId,
          pickup_address_id: pickupAddressId,
          dropoff_address_id: dropoffAddressId,
          payment_method: 'card'
        }
      })
    });

    if (!rideResponse.ok) {
      throw new Error('Failed to create ride');
    }

    const ride = await rideResponse.json();
    return ride.data.attributes;

  } catch (error) {
    console.error('Error creating ride:', error);
    throw error;
  }
}

function generateUUID() {
  return crypto.randomUUID();
}

Code References

Component File Path Lines
Ride model app/models/ride.rb 16-17, 204
Address model app/models/address.rb 1-27
Rides controller app/controllers/api/v1/rides_controller.rb 38-56, 251-259
Addresses controller app/controllers/api/v1/addresses_controller.rb 30-53, 118-127
Address API spec spec/requests/api/v1/swagger/addresses_swagger_spec.rb 209-409
Ride API spec spec/requests/api/v1/swagger/rides_swagger_spec.rb 122-225

Summary

  • Addresses are managed through: Separate /api/v1/addresses endpoint
  • API flow: Create addresses → Get IDs → Create ride with IDs
  • Database relationships: Ride belongs_to pickup_address and dropoff_address
  • Deduplication: Automatic via find-or-create pattern (returns existing address if match found)
  • Validation: Full address details required with geocoding support
  • Error handling: Standard REST error responses with detailed validation messages


Need Help?

If you have questions or run into issues, please:

  1. Check the API Documentation
  2. Review the Swagger specifications
  3. Open an issue on GitHub