How to Get an Address ID
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:
- Create the pickup and dropoff addresses via
POST /api/v1/addresses - Receive the
address_idvalues from the responses - 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_idanddropoff_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_ridesanddropoff_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_idfield 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 Createdfor new addresses and200 OKfor 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:
- The controller extracts normalized address attributes (street_number, street_name, city, state, zip, country)
- It searches for an existing address matching these fields
- If found, it returns the existing address with
200 OK - 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_numberstreet_namecitystatezipcountry
Excluded from deduplication:
latitudeandlongitude(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” rolecounty- 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/addressesendpoint - ✅ API flow: Create addresses → Get IDs → Create ride with IDs
- ✅ Database relationships: Ride
belongs_topickup_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
Related Documentation
Need Help?
If you have questions or run into issues, please:
- Check the API Documentation
- Review the Swagger specifications
- Open an issue on GitHub