Address Validation 3 C# Rest Code Snippet

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using System.Threading.Tasks;
namespace address_validation_us_3_dot_net.REST
{
/// <summary>
/// Client for the AV3 GetBestMatches operation, providing both sync and async methods.
/// Handles live vs. trial endpoints, URL encoding, fallback logic, and JSON deserialization.
/// </summary>
public static class GetBestMatchesClient
{
// Base URL constants: production, backup, and trial
private const string LiveBaseUrl = "https://sws.serviceobjects.com/AV3/api.svc/";
private const string BackupBaseUrl = "https://swsbackup.serviceobjects.com/AV3/api.svc/";
private const string TrialBaseUrl = "https://trial.serviceobjects.com/AV3/api.svc/";
/// <summary>
/// Synchronously invoke the GetBestMatchesJson endpoint.
/// </summary>
/// <param name="input">Data for the request (address components, license key, isLive).</param>
/// <returns>Deserialized <see cref="GBMResponse"/>.</returns>
public static GBMResponse Invoke(GetBestMatchesInput input)
{
//Use query string parameters so missing/options fields don't break
//the URL as path parameters would.
var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
GBMResponse response = Helper.HttpGet<GBMResponse>(url, input.TimeoutSeconds);
// Fallback on error payload in live mode
if (input.IsLive && !IsValid(response))
{
var fallbackUrl = BuildUrl(input, BackupBaseUrl);
GBMResponse fallbackResponse = Helper.HttpGet<GBMResponse>(fallbackUrl, input.TimeoutSeconds);
return fallbackResponse;
}
return response;
}
/// <summary>
/// Asynchronously invoke the GetBestMatchesJson endpoint.
/// </summary>
/// <param name="input">Data for the request (address components, license key, isLive).</param>
/// <returns>Deserialized <see cref="GBMResponse"/>.</returns>
public static async Task<GBMResponse> InvokeAsync(GetBestMatchesInput input)
{
//Use query string parameters so missing/options fields don't break
//the URL as path parameters would.
var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
GBMResponse response = await Helper.HttpGetAsync<GBMResponse>(url, input.TimeoutSeconds).ConfigureAwait(false);
if (input.IsLive && !IsValid(response))
{
var fallbackUrl = BuildUrl(input, BackupBaseUrl);
GBMResponse fallbackResponse = await Helper.HttpGetAsync<GBMResponse>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
return fallbackResponse;
}
return response;
}
// Build the full request URL, including URL-encoded query string
private static string BuildUrl(GetBestMatchesInput input, string baseUrl)
{
var qs = $"GetBestMatchesJson?BusinessName={Helper.UrlEncode(input.BusinessName)}" +
$"&Address={Helper.UrlEncode(input.Address)}" +
$"&Address2={Helper.UrlEncode(input.Address2)}" +
$"&City={Helper.UrlEncode(input.City)}" +
$"&State={Helper.UrlEncode(input.State)}" +
$"&PostalCode={Helper.UrlEncode(input.PostalCode)}" +
$"&LicenseKey={Helper.UrlEncode(input.LicenseKey)}";
return baseUrl + qs;
}
private static bool IsValid(GBMResponse response) => response?.Error == null || response.Error.TypeCode != "3";
/// <summary>
/// Input parameters for the GetBestMatchesSingleLine operation.
/// </summary>
/// <param name="BusinessName">Company name to assist suite parsing (e.g., "Acme Corp"). - Optional</param>
/// <param name="Address">Address line 1 (e.g., "123 Main St") - Required.</param>
/// <param name="Address2">Address line 2 - Optional</param>
/// <param name="City">City - Required when PostalCode is missing.</param>
/// <param name="State">State - Required when PostalCode is missing.</param>
/// <param name="PostalCode">PostalCode - Required when City and state are missing.</param>
/// <param name="LicenseKey">Service Objects AV3 license key. - Required</param>
/// <param name="IsLive">True for live (production+backup) endpoints; false for trial only. - Required</param>
/// <param name="TimeoutSeconds">Request timeout in seconds (default: 15).</param>
public record GetBestMatchesInput(
string BusinessName = "",
string Address = "",
string Address2 = "",
string City = "",
string State = "",
string PostalCode = "",
string LicenseKey = "",
bool IsLive = true,
int TimeoutSeconds = 15
);
}
}
using System.Runtime.Serialization;
namespace address_validation_us_3_dot_net.REST
{
/// <summary>
/// Response object for capturing the reponse from the API for GetBestMatches
/// </summary>
[DataContract]
public class GBMResponse
{
[DataMember(Name = "Addresses")]
public Address[] Addresses { get; set; }
[DataMember(Name = "IsCASS")]
public bool IsCASS { get; set; }
[DataMember(Name = "Error")]
public Error Error { get; set; }
public override string ToString()
{
string Output = "";
Output += $"GBM Response:\n";
Output += $"List of all Addresses: [";
if (Addresses != null && Addresses.Length > 0)
{
foreach (Address A in Addresses)
{
Output += A.ToString() + "\n";
}
}
Output += "]\n";
Output += $"IsCASS: {IsCASS}\n";
Output += $"Error: {{{Error}}}\n";
return Output;
}
}
/// <summary>
/// Response object for capturing the reponse from the API for GetSecondaryNumbers
/// </summary>
public class GSNResponse
{
[DataMember(Name = "Address1")]
public string Address1 { get; set; }
[DataMember(Name = "City")]
public string City { get; set; }
[DataMember(Name = "State")]
public string State { get; set; }
[DataMember(Name = "Zip")]
public string Zip { get; set; }
[DataMember(Name = "TotalCount")]
public int TotalCount { get; set; }
[DataMember(Name = "SecondaryNumbers")]
public string[] SecondaryNumbers { get; set; }
[DataMember(Name = "Error")]
public Error Error { get; set; }
public override string ToString()
{
string Output = "";
Output += $"GSN Response:\n";
Output += $"Address1: {Address1}\n";
Output += $"City: {City}\n";
Output += $"State: {State}\n";
Output += $"Zip: {Zip}\n";
Output += $"Total Count: {TotalCount}\n";
Output += $"SecondaryNumbers: [";
if (SecondaryNumbers != null && SecondaryNumbers.Length > 0)
{
foreach (string A in SecondaryNumbers)
{
Output += A.ToString() + "\n";
}
}
Output += "]\n";
Output += $"Error: {Error}\n";
return Output;
}
}
/// <summary>
/// Response object for capturing the reponse from the API for CityStateZip
/// </summary>
public class CSZResponse
{
[DataMember(Name = "CityStateZip")]
public CityStateZip CityStateZip { get; set; }
[DataMember(Name = "Error")]
public Error Error { get; set; }
public override string ToString()
{
return $"CSZ Response:\n" +
$"CityStateZip: {{{CityStateZip}}}\n" +
$"Error: {{{Error}}}\n";
}
}
/// <summary>
/// Object to house the addresses being returned.
/// </summary>
public class Address
{
[DataMember(Name = "Address1")]
public string Address1 { get; set; }
[DataMember(Name = "Address2")]
public string Address2 { get; set; }
[DataMember(Name = "City")]
public string City { get; set; }
[DataMember(Name = "State")]
public string State { get; set; }
[DataMember(Name = "Zip")]
public string Zip { get; set; }
[DataMember(Name = "IsResidential")]
public string IsResidential { get; set; }
[DataMember(Name = "DPV")]
public string DPV { get; set; }
[DataMember(Name = "DPVDesc")]
public string DPVDesc { get; set; }
[DataMember(Name = "DPVNotes")]
public string DPVNotes { get; set; }
[DataMember(Name = "DPVNotesDesc")]
public string DPVNotesDesc { get; set; }
[DataMember(Name = "Corrections")]
public string Corrections { get; set; }
[DataMember(Name = "CorrectionsDesc")]
public string CorrectionsDesc { get; set; }
[DataMember(Name = "BarcodeDigits")]
public string BarcodeDigits { get; set; }
[DataMember(Name = "CarrierRoute")]
public string CarrierRoute { get; set; }
[DataMember(Name = "CongressCode")]
public string CongressCode { get; set; }
[DataMember(Name = "CountyCode")]
public string CountyCode { get; set; }
[DataMember(Name = "CountyName")]
public string CountyName { get; set; }
[DataMember(Name = "FragmentHouse")]
public string FragmentHouse { get; set; }
[DataMember(Name = "FragmentPreDir")]
public string FragmentPreDir { get; set; }
[DataMember(Name = "FragmentStreet")]
public string FragmentStreet { get; set; }
[DataMember(Name = "FragmentSuffix")]
public string FragmentSuffix { get; set; }
[DataMember(Name = "FragmentPostDir")]
public string FragmentPostDir { get; set; }
[DataMember(Name = "FragmentUnit")]
public string FragmentUnit { get; set; }
[DataMember(Name = "Fragment")]
public string Fragment { get; set; }
[DataMember(Name = "FragmentPMBPrefix")]
public string FragmentPMBPrefix { get; set; }
[DataMember(Name = "FragmentPMBNumber")]
public string FragmentPMBNumber { get; set; }
public override string ToString()
{
return $"\n{{Address1: {Address1}\n" +
$"\tAddress2: {Address2}\n" +
$"\tCity: {City}\n" +
$"\tState: {State}\n" +
$"\tZip: {Zip}\n" +
$"\tIsResidential: {IsResidential}\n" +
$"\tDPV: {DPV}\n" +
$"\tDPVDesc: {DPVDesc}\n" +
$"\tDPVNotes: {DPVNotes}\n" +
$"\tDPVNotesDesc: {DPVNotesDesc}\n" +
$"\tCorrections: {Corrections}\n" +
$"\tCorrectionsDesc: {CorrectionsDesc}\n" +
$"\tBarcodeDigits: {BarcodeDigits}\n" +
$"\tCarrierRoute: {CarrierRoute}\n" +
$"\tCongressCode: {CongressCode}\n" +
$"\tCountyCode: {CountyCode}\n" +
$"\tCountyName: {CountyName}\n" +
$"\tFragmentHouse: {FragmentHouse}\n" +
$"\tFragmentPreDir: {FragmentPreDir}\n" +
$"\tFragmentStreet: {FragmentStreet}\n" +
$"\tFragmentSuffix: {FragmentSuffix}\n" +
$"\tFragmentPostDir: {FragmentPostDir}\n" +
$"\tFragmentUnit: {FragmentUnit}\n" +
$"\tFragment: {Fragment}\n" +
$"\tFragmentPMBPrefix: {FragmentPMBPrefix}\n" +
$"\tFragmentPMBNumber: {FragmentPMBNumber}}}\n";
}
}
public class CityStateZip
{
[DataMember(Name = "City")]
public string City { get; set; }
[DataMember(Name = "State")]
public string State { get; set; }
[DataMember(Name = "Zip")]
public string Zip { get; set; }
[DataMember(Name = "GeneralDeliveryService")]
public string GeneralDeliveryService { get; set; }
[DataMember(Name = "POBoxService")]
public string POBoxService { get; set; }
[DataMember(Name = "StreetService")]
public string StreetService { get; set; }
[DataMember(Name = "RRHCService")]
public string RRHCService { get; set; }
[DataMember(Name = "UrbanizationService")]
public string UrbanizationService { get; set; }
[DataMember(Name = "POBoxRangeLow")]
public string POBoxRangeLow { get; set; }
[DataMember(Name = "POBoxRangeHigh")]
public string POBoxRangeHigh { get; set; }
[DataMember(Name = "IsUniqueZipCode")]
public string IsUniqueZipCode { get; set; }
public override string ToString()
{
return $"City: {City} " +
$"State: {State} " +
$"Zip: {Zip} " +
$"GeneralDeliveryService: {GeneralDeliveryService} " +
$"POBoxService: {POBoxService} " +
$"StreetService: {StreetService} " +
$"RRHCService: {RRHCService} " +
$"UrbanizationService: {UrbanizationService} " +
$"POBoxRangeLow: {POBoxRangeLow} " +
$"POBoxRangeHigh: {POBoxRangeHigh} " +
$"IsUniqueZipCode: {IsUniqueZipCode} ";
}
}
public class Error
{
[DataMember(Name = "Type")]
public string Type { get; set; }
[DataMember(Name = "TypeCode")]
public string TypeCode { get; set; }
[DataMember(Name = "Desc")]
public string Desc { get; set; }
[DataMember(Name = "DescCode")]
public string DescCode { get; set; }
public override string ToString()
{
return $"Type: {Type} " +
$"TypeCode: {TypeCode} " +
$"Desc: {Desc} " +
$"DescCode: {DescCode} ";
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;
namespace address_validation_us_3_dot_net.REST
{
public class Helper
{
public static T HttpGet<T>(string url, int timeoutSeconds)
{
using var httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(timeoutSeconds)
};
using var request = new HttpRequestMessage(HttpMethod.Get, url);
using HttpResponseMessage response = httpClient
.SendAsync(request)
.GetAwaiter()
.GetResult();
response.EnsureSuccessStatusCode();
using Stream responseStream = response.Content
.ReadAsStreamAsync()
.GetAwaiter()
.GetResult();
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
object? obj = JsonSerializer.Deserialize(responseStream, typeof(T), options);
T result = (T)obj!;
return result;
}
// Asynchronous HTTP GET and JSON deserialize
public static async Task<T> HttpGetAsync<T>(string url, int timeoutSeconds)
{
HttpClient HttpClient = new HttpClient();
HttpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
using var httpResponse = await HttpClient.GetAsync(url).ConfigureAwait(false);
httpResponse.EnsureSuccessStatusCode();
var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
return JsonSerializer.Deserialize<T>(stream)!;
}
public static string UrlEncode(string value) => HttpUtility.UrlEncode(value ?? string.Empty);
}
}
using System.Threading.Tasks; namespace address_validation_us_3_dot_net.REST { /// <summary> /// Client for the AV3 GetBestMatches operation, providing both sync and async methods. /// Handles live vs. trial endpoints, URL encoding, fallback logic, and JSON deserialization. /// </summary> public static class GetBestMatchesClient { // Base URL constants: production, backup, and trial private const string LiveBaseUrl = "https://sws.serviceobjects.com/AV3/api.svc/"; private const string BackupBaseUrl = "https://swsbackup.serviceobjects.com/AV3/api.svc/"; private const string TrialBaseUrl = "https://trial.serviceobjects.com/AV3/api.svc/"; /// <summary> /// Synchronously invoke the GetBestMatchesJson endpoint. /// </summary> /// <param name="input">Data for the request (address components, license key, isLive).</param> /// <returns>Deserialized <see cref="GBMResponse"/>.</returns> public static GBMResponse Invoke(GetBestMatchesInput input) { //Use query string parameters so missing/options fields don't break //the URL as path parameters would. var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl); GBMResponse response = Helper.HttpGet<GBMResponse>(url, input.TimeoutSeconds); // Fallback on error payload in live mode if (input.IsLive && !IsValid(response)) { var fallbackUrl = BuildUrl(input, BackupBaseUrl); GBMResponse fallbackResponse = Helper.HttpGet<GBMResponse>(fallbackUrl, input.TimeoutSeconds); return fallbackResponse; } return response; } /// <summary> /// Asynchronously invoke the GetBestMatchesJson endpoint. /// </summary> /// <param name="input">Data for the request (address components, license key, isLive).</param> /// <returns>Deserialized <see cref="GBMResponse"/>.</returns> public static async Task<GBMResponse> InvokeAsync(GetBestMatchesInput input) { //Use query string parameters so missing/options fields don't break //the URL as path parameters would. var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl); GBMResponse response = await Helper.HttpGetAsync<GBMResponse>(url, input.TimeoutSeconds).ConfigureAwait(false); if (input.IsLive && !IsValid(response)) { var fallbackUrl = BuildUrl(input, BackupBaseUrl); GBMResponse fallbackResponse = await Helper.HttpGetAsync<GBMResponse>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false); return fallbackResponse; } return response; } // Build the full request URL, including URL-encoded query string private static string BuildUrl(GetBestMatchesInput input, string baseUrl) { var qs = $"GetBestMatchesJson?BusinessName={Helper.UrlEncode(input.BusinessName)}" + $"&Address={Helper.UrlEncode(input.Address)}" + $"&Address2={Helper.UrlEncode(input.Address2)}" + $"&City={Helper.UrlEncode(input.City)}" + $"&State={Helper.UrlEncode(input.State)}" + $"&PostalCode={Helper.UrlEncode(input.PostalCode)}" + $"&LicenseKey={Helper.UrlEncode(input.LicenseKey)}"; return baseUrl + qs; } private static bool IsValid(GBMResponse response) => response?.Error == null || response.Error.TypeCode != "3"; /// <summary> /// Input parameters for the GetBestMatchesSingleLine operation. /// </summary> /// <param name="BusinessName">Company name to assist suite parsing (e.g., "Acme Corp"). - Optional</param> /// <param name="Address">Address line 1 (e.g., "123 Main St") - Required.</param> /// <param name="Address2">Address line 2 - Optional</param> /// <param name="City">City - Required when PostalCode is missing.</param> /// <param name="State">State - Required when PostalCode is missing.</param> /// <param name="PostalCode">PostalCode - Required when City and state are missing.</param> /// <param name="LicenseKey">Service Objects AV3 license key. - Required</param> /// <param name="IsLive">True for live (production+backup) endpoints; false for trial only. - Required</param> /// <param name="TimeoutSeconds">Request timeout in seconds (default: 15).</param> public record GetBestMatchesInput( string BusinessName = "", string Address = "", string Address2 = "", string City = "", string State = "", string PostalCode = "", string LicenseKey = "", bool IsLive = true, int TimeoutSeconds = 15 ); } } using System.Runtime.Serialization; namespace address_validation_us_3_dot_net.REST { /// <summary> /// Response object for capturing the reponse from the API for GetBestMatches /// </summary> [DataContract] public class GBMResponse { [DataMember(Name = "Addresses")] public Address[] Addresses { get; set; } [DataMember(Name = "IsCASS")] public bool IsCASS { get; set; } [DataMember(Name = "Error")] public Error Error { get; set; } public override string ToString() { string Output = ""; Output += $"GBM Response:\n"; Output += $"List of all Addresses: ["; if (Addresses != null && Addresses.Length > 0) { foreach (Address A in Addresses) { Output += A.ToString() + "\n"; } } Output += "]\n"; Output += $"IsCASS: {IsCASS}\n"; Output += $"Error: {{{Error}}}\n"; return Output; } } /// <summary> /// Response object for capturing the reponse from the API for GetSecondaryNumbers /// </summary> public class GSNResponse { [DataMember(Name = "Address1")] public string Address1 { get; set; } [DataMember(Name = "City")] public string City { get; set; } [DataMember(Name = "State")] public string State { get; set; } [DataMember(Name = "Zip")] public string Zip { get; set; } [DataMember(Name = "TotalCount")] public int TotalCount { get; set; } [DataMember(Name = "SecondaryNumbers")] public string[] SecondaryNumbers { get; set; } [DataMember(Name = "Error")] public Error Error { get; set; } public override string ToString() { string Output = ""; Output += $"GSN Response:\n"; Output += $"Address1: {Address1}\n"; Output += $"City: {City}\n"; Output += $"State: {State}\n"; Output += $"Zip: {Zip}\n"; Output += $"Total Count: {TotalCount}\n"; Output += $"SecondaryNumbers: ["; if (SecondaryNumbers != null && SecondaryNumbers.Length > 0) { foreach (string A in SecondaryNumbers) { Output += A.ToString() + "\n"; } } Output += "]\n"; Output += $"Error: {Error}\n"; return Output; } } /// <summary> /// Response object for capturing the reponse from the API for CityStateZip /// </summary> public class CSZResponse { [DataMember(Name = "CityStateZip")] public CityStateZip CityStateZip { get; set; } [DataMember(Name = "Error")] public Error Error { get; set; } public override string ToString() { return $"CSZ Response:\n" + $"CityStateZip: {{{CityStateZip}}}\n" + $"Error: {{{Error}}}\n"; } } /// <summary> /// Object to house the addresses being returned. /// </summary> public class Address { [DataMember(Name = "Address1")] public string Address1 { get; set; } [DataMember(Name = "Address2")] public string Address2 { get; set; } [DataMember(Name = "City")] public string City { get; set; } [DataMember(Name = "State")] public string State { get; set; } [DataMember(Name = "Zip")] public string Zip { get; set; } [DataMember(Name = "IsResidential")] public string IsResidential { get; set; } [DataMember(Name = "DPV")] public string DPV { get; set; } [DataMember(Name = "DPVDesc")] public string DPVDesc { get; set; } [DataMember(Name = "DPVNotes")] public string DPVNotes { get; set; } [DataMember(Name = "DPVNotesDesc")] public string DPVNotesDesc { get; set; } [DataMember(Name = "Corrections")] public string Corrections { get; set; } [DataMember(Name = "CorrectionsDesc")] public string CorrectionsDesc { get; set; } [DataMember(Name = "BarcodeDigits")] public string BarcodeDigits { get; set; } [DataMember(Name = "CarrierRoute")] public string CarrierRoute { get; set; } [DataMember(Name = "CongressCode")] public string CongressCode { get; set; } [DataMember(Name = "CountyCode")] public string CountyCode { get; set; } [DataMember(Name = "CountyName")] public string CountyName { get; set; } [DataMember(Name = "FragmentHouse")] public string FragmentHouse { get; set; } [DataMember(Name = "FragmentPreDir")] public string FragmentPreDir { get; set; } [DataMember(Name = "FragmentStreet")] public string FragmentStreet { get; set; } [DataMember(Name = "FragmentSuffix")] public string FragmentSuffix { get; set; } [DataMember(Name = "FragmentPostDir")] public string FragmentPostDir { get; set; } [DataMember(Name = "FragmentUnit")] public string FragmentUnit { get; set; } [DataMember(Name = "Fragment")] public string Fragment { get; set; } [DataMember(Name = "FragmentPMBPrefix")] public string FragmentPMBPrefix { get; set; } [DataMember(Name = "FragmentPMBNumber")] public string FragmentPMBNumber { get; set; } public override string ToString() { return $"\n{{Address1: {Address1}\n" + $"\tAddress2: {Address2}\n" + $"\tCity: {City}\n" + $"\tState: {State}\n" + $"\tZip: {Zip}\n" + $"\tIsResidential: {IsResidential}\n" + $"\tDPV: {DPV}\n" + $"\tDPVDesc: {DPVDesc}\n" + $"\tDPVNotes: {DPVNotes}\n" + $"\tDPVNotesDesc: {DPVNotesDesc}\n" + $"\tCorrections: {Corrections}\n" + $"\tCorrectionsDesc: {CorrectionsDesc}\n" + $"\tBarcodeDigits: {BarcodeDigits}\n" + $"\tCarrierRoute: {CarrierRoute}\n" + $"\tCongressCode: {CongressCode}\n" + $"\tCountyCode: {CountyCode}\n" + $"\tCountyName: {CountyName}\n" + $"\tFragmentHouse: {FragmentHouse}\n" + $"\tFragmentPreDir: {FragmentPreDir}\n" + $"\tFragmentStreet: {FragmentStreet}\n" + $"\tFragmentSuffix: {FragmentSuffix}\n" + $"\tFragmentPostDir: {FragmentPostDir}\n" + $"\tFragmentUnit: {FragmentUnit}\n" + $"\tFragment: {Fragment}\n" + $"\tFragmentPMBPrefix: {FragmentPMBPrefix}\n" + $"\tFragmentPMBNumber: {FragmentPMBNumber}}}\n"; } } public class CityStateZip { [DataMember(Name = "City")] public string City { get; set; } [DataMember(Name = "State")] public string State { get; set; } [DataMember(Name = "Zip")] public string Zip { get; set; } [DataMember(Name = "GeneralDeliveryService")] public string GeneralDeliveryService { get; set; } [DataMember(Name = "POBoxService")] public string POBoxService { get; set; } [DataMember(Name = "StreetService")] public string StreetService { get; set; } [DataMember(Name = "RRHCService")] public string RRHCService { get; set; } [DataMember(Name = "UrbanizationService")] public string UrbanizationService { get; set; } [DataMember(Name = "POBoxRangeLow")] public string POBoxRangeLow { get; set; } [DataMember(Name = "POBoxRangeHigh")] public string POBoxRangeHigh { get; set; } [DataMember(Name = "IsUniqueZipCode")] public string IsUniqueZipCode { get; set; } public override string ToString() { return $"City: {City} " + $"State: {State} " + $"Zip: {Zip} " + $"GeneralDeliveryService: {GeneralDeliveryService} " + $"POBoxService: {POBoxService} " + $"StreetService: {StreetService} " + $"RRHCService: {RRHCService} " + $"UrbanizationService: {UrbanizationService} " + $"POBoxRangeLow: {POBoxRangeLow} " + $"POBoxRangeHigh: {POBoxRangeHigh} " + $"IsUniqueZipCode: {IsUniqueZipCode} "; } } public class Error { [DataMember(Name = "Type")] public string Type { get; set; } [DataMember(Name = "TypeCode")] public string TypeCode { get; set; } [DataMember(Name = "Desc")] public string Desc { get; set; } [DataMember(Name = "DescCode")] public string DescCode { get; set; } public override string ToString() { return $"Type: {Type} " + $"TypeCode: {TypeCode} " + $"Desc: {Desc} " + $"DescCode: {DescCode} "; } } } using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Reflection; using System.Text; using System.Text.Json; using System.Threading.Tasks; using System.Web; namespace address_validation_us_3_dot_net.REST { public class Helper { public static T HttpGet<T>(string url, int timeoutSeconds) { using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(timeoutSeconds) }; using var request = new HttpRequestMessage(HttpMethod.Get, url); using HttpResponseMessage response = httpClient .SendAsync(request) .GetAwaiter() .GetResult(); response.EnsureSuccessStatusCode(); using Stream responseStream = response.Content .ReadAsStreamAsync() .GetAwaiter() .GetResult(); var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; object? obj = JsonSerializer.Deserialize(responseStream, typeof(T), options); T result = (T)obj!; return result; } // Asynchronous HTTP GET and JSON deserialize public static async Task<T> HttpGetAsync<T>(string url, int timeoutSeconds) { HttpClient HttpClient = new HttpClient(); HttpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds); using var httpResponse = await HttpClient.GetAsync(url).ConfigureAwait(false); httpResponse.EnsureSuccessStatusCode(); var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false); return JsonSerializer.Deserialize<T>(stream)!; } public static string UrlEncode(string value) => HttpUtility.UrlEncode(value ?? string.Empty); } }
using System.Threading.Tasks;

namespace address_validation_us_3_dot_net.REST
{
    /// <summary>
    /// Client for the AV3 GetBestMatches operation, providing both sync and async methods.
    /// Handles live vs. trial endpoints, URL encoding, fallback logic, and JSON deserialization.
    /// </summary>
    public static class GetBestMatchesClient
    {
        // Base URL constants: production, backup, and trial
        private const string LiveBaseUrl = "https://sws.serviceobjects.com/AV3/api.svc/";
        private const string BackupBaseUrl = "https://swsbackup.serviceobjects.com/AV3/api.svc/";
        private const string TrialBaseUrl = "https://trial.serviceobjects.com/AV3/api.svc/";

        /// <summary>
        /// Synchronously invoke the GetBestMatchesJson endpoint.
        /// </summary>
        /// <param name="input">Data for the request (address components, license key, isLive).</param>
        /// <returns>Deserialized <see cref="GBMResponse"/>.</returns>
        public static GBMResponse Invoke(GetBestMatchesInput input)
        {
            //Use query string parameters so missing/options fields don't break
            //the URL as path parameters would.
            var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            GBMResponse response = Helper.HttpGet<GBMResponse>(url, input.TimeoutSeconds);

            // Fallback on error payload in live mode
            if (input.IsLive && !IsValid(response))
            {
                var fallbackUrl = BuildUrl(input, BackupBaseUrl);
                GBMResponse fallbackResponse = Helper.HttpGet<GBMResponse>(fallbackUrl, input.TimeoutSeconds);
                return fallbackResponse;
            }

            return response;
        }

        /// <summary>
        /// Asynchronously invoke the GetBestMatchesJson endpoint.
        /// </summary>
        /// <param name="input">Data for the request (address components, license key, isLive).</param>
        /// <returns>Deserialized <see cref="GBMResponse"/>.</returns>
        public static async Task<GBMResponse> InvokeAsync(GetBestMatchesInput input)
        {
            //Use query string parameters so missing/options fields don't break
            //the URL as path parameters would.
            var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            GBMResponse response = await Helper.HttpGetAsync<GBMResponse>(url, input.TimeoutSeconds).ConfigureAwait(false);

            if (input.IsLive && !IsValid(response))
            {
                var fallbackUrl = BuildUrl(input, BackupBaseUrl);
                GBMResponse fallbackResponse = await Helper.HttpGetAsync<GBMResponse>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
                return fallbackResponse;
            }

            return response;
        }

        // Build the full request URL, including URL-encoded query string
        private static string BuildUrl(GetBestMatchesInput input, string baseUrl)
        {
            var qs = $"GetBestMatchesJson?BusinessName={Helper.UrlEncode(input.BusinessName)}" +
                     $"&Address={Helper.UrlEncode(input.Address)}" +
                     $"&Address2={Helper.UrlEncode(input.Address2)}" +
                     $"&City={Helper.UrlEncode(input.City)}" +
                     $"&State={Helper.UrlEncode(input.State)}" +
                     $"&PostalCode={Helper.UrlEncode(input.PostalCode)}" +
                     $"&LicenseKey={Helper.UrlEncode(input.LicenseKey)}";
            return baseUrl + qs;
        }

        private static bool IsValid(GBMResponse response) => response?.Error == null || response.Error.TypeCode != "3";

        /// <summary>
        /// Input parameters for the GetBestMatchesSingleLine operation.
        /// </summary>
        /// <param name="BusinessName">Company name to assist suite parsing (e.g., "Acme Corp"). - Optional</param>
        /// <param name="Address">Address line 1 (e.g., "123 Main St") - Required.</param>
        /// <param name="Address2">Address line 2 - Optional</param>
        /// <param name="City">City - Required when PostalCode is missing.</param>
        /// <param name="State">State - Required when PostalCode is missing.</param>
        /// <param name="PostalCode">PostalCode - Required when City and state are missing.</param>
        /// <param name="LicenseKey">Service Objects AV3 license key. - Required</param>
        /// <param name="IsLive">True for live (production+backup) endpoints; false for trial only. - Required</param>
        /// <param name="TimeoutSeconds">Request timeout in seconds (default: 15).</param>
        public record GetBestMatchesInput(
                                            string BusinessName = "",
                                            string Address = "",
                                            string Address2 = "",
                                            string City = "",
                                            string State = "",
                                            string PostalCode = "",
                                            string LicenseKey = "",
                                            bool IsLive = true,
                                            int TimeoutSeconds = 15
        );
    }
}


using System.Runtime.Serialization;

namespace address_validation_us_3_dot_net.REST
{
    /// <summary>
    /// Response object for capturing the reponse from the API for GetBestMatches
    /// </summary>
    [DataContract]
    public class GBMResponse
    {
        [DataMember(Name = "Addresses")]
        public Address[] Addresses { get; set; }
        [DataMember(Name = "IsCASS")]
        public bool IsCASS { get; set; }
        [DataMember(Name = "Error")]
        public Error Error { get; set; }
        public override string ToString()
        {
            string Output = "";
            Output += $"GBM Response:\n";
            Output += $"List of all Addresses: [";
            if (Addresses != null && Addresses.Length > 0)
            {
                foreach (Address A in Addresses)
                {
                    Output += A.ToString() + "\n";
                }
            }
            Output += "]\n";
            Output += $"IsCASS: {IsCASS}\n";
            Output += $"Error: {{{Error}}}\n";
            return Output;
        }

    }

    /// <summary>
    /// Response object for capturing the reponse from the API for GetSecondaryNumbers
    /// </summary>
    public class GSNResponse
    {
        [DataMember(Name = "Address1")]
        public string Address1 { get; set; }
        [DataMember(Name = "City")]
        public string City { get; set; }
        [DataMember(Name = "State")]
        public string State { get; set; }
        [DataMember(Name = "Zip")]
        public string Zip { get; set; }
        [DataMember(Name = "TotalCount")]
        public int TotalCount { get; set; }
        [DataMember(Name = "SecondaryNumbers")]
        public string[] SecondaryNumbers { get; set; }

        [DataMember(Name = "Error")]
        public Error Error { get; set; }

        public override string ToString()
        {
            string Output = "";
            Output += $"GSN Response:\n";
            Output += $"Address1: {Address1}\n";
            Output += $"City: {City}\n";
            Output += $"State: {State}\n";
            Output += $"Zip: {Zip}\n";
            Output += $"Total Count: {TotalCount}\n";
            Output += $"SecondaryNumbers: [";
            if (SecondaryNumbers != null && SecondaryNumbers.Length > 0)
            {
                foreach (string A in SecondaryNumbers)

                {
                    Output += A.ToString() + "\n";
                }
            }
            Output += "]\n";
            Output += $"Error: {Error}\n";
            return Output;
        }
    }


    /// <summary>
    /// Response object for capturing the reponse from the API for CityStateZip
    /// </summary>
    public class CSZResponse
    {
        [DataMember(Name = "CityStateZip")]
        public CityStateZip CityStateZip { get; set; }

        [DataMember(Name = "Error")]
        public Error Error { get; set; }
        public override string ToString()
        {
            return $"CSZ Response:\n" +
                $"CityStateZip: {{{CityStateZip}}}\n" +
                $"Error: {{{Error}}}\n";
        }
    }

    /// <summary>
    /// Object to house the addresses being returned.
    /// </summary>
    public class Address
    {
        [DataMember(Name = "Address1")]
        public string Address1 { get; set; }
        [DataMember(Name = "Address2")]
        public string Address2 { get; set; }
        [DataMember(Name = "City")]
        public string City { get; set; }
        [DataMember(Name = "State")]
        public string State { get; set; }
        [DataMember(Name = "Zip")]
        public string Zip { get; set; }
        [DataMember(Name = "IsResidential")]
        public string IsResidential { get; set; }
        [DataMember(Name = "DPV")]
        public string DPV { get; set; }
        [DataMember(Name = "DPVDesc")]
        public string DPVDesc { get; set; }
        [DataMember(Name = "DPVNotes")]
        public string DPVNotes { get; set; }
        [DataMember(Name = "DPVNotesDesc")]
        public string DPVNotesDesc { get; set; }
        [DataMember(Name = "Corrections")]
        public string Corrections { get; set; }
        [DataMember(Name = "CorrectionsDesc")]
        public string CorrectionsDesc { get; set; }
        [DataMember(Name = "BarcodeDigits")]
        public string BarcodeDigits { get; set; }
        [DataMember(Name = "CarrierRoute")]
        public string CarrierRoute { get; set; }
        [DataMember(Name = "CongressCode")]
        public string CongressCode { get; set; }
        [DataMember(Name = "CountyCode")]
        public string CountyCode { get; set; }
        [DataMember(Name = "CountyName")]
        public string CountyName { get; set; }
        [DataMember(Name = "FragmentHouse")]
        public string FragmentHouse { get; set; }
        [DataMember(Name = "FragmentPreDir")]
        public string FragmentPreDir { get; set; }
        [DataMember(Name = "FragmentStreet")]
        public string FragmentStreet { get; set; }
        [DataMember(Name = "FragmentSuffix")]
        public string FragmentSuffix { get; set; }
        [DataMember(Name = "FragmentPostDir")]
        public string FragmentPostDir { get; set; }
        [DataMember(Name = "FragmentUnit")]
        public string FragmentUnit { get; set; }
        [DataMember(Name = "Fragment")]
        public string Fragment { get; set; }
        [DataMember(Name = "FragmentPMBPrefix")]
        public string FragmentPMBPrefix { get; set; }
        [DataMember(Name = "FragmentPMBNumber")]
        public string FragmentPMBNumber { get; set; }
        public override string ToString()
        {
            return $"\n{{Address1: {Address1}\n" +
                $"\tAddress2: {Address2}\n" +
                $"\tCity: {City}\n" +
                $"\tState: {State}\n" +
                $"\tZip: {Zip}\n" +
                $"\tIsResidential: {IsResidential}\n" +
                $"\tDPV: {DPV}\n" +
                $"\tDPVDesc: {DPVDesc}\n" +
                $"\tDPVNotes: {DPVNotes}\n" +
                $"\tDPVNotesDesc: {DPVNotesDesc}\n" +
                $"\tCorrections: {Corrections}\n" +
                $"\tCorrectionsDesc: {CorrectionsDesc}\n" +
                $"\tBarcodeDigits: {BarcodeDigits}\n" +
                $"\tCarrierRoute: {CarrierRoute}\n" +
                $"\tCongressCode: {CongressCode}\n" +
                $"\tCountyCode: {CountyCode}\n" +
                $"\tCountyName: {CountyName}\n" +
                $"\tFragmentHouse: {FragmentHouse}\n" +
                $"\tFragmentPreDir: {FragmentPreDir}\n" +
                $"\tFragmentStreet: {FragmentStreet}\n" +
                $"\tFragmentSuffix: {FragmentSuffix}\n" +
                $"\tFragmentPostDir: {FragmentPostDir}\n" +
                $"\tFragmentUnit: {FragmentUnit}\n" +
                $"\tFragment: {Fragment}\n" +
                $"\tFragmentPMBPrefix: {FragmentPMBPrefix}\n" +
                $"\tFragmentPMBNumber: {FragmentPMBNumber}}}\n";
        }
    }

    public class CityStateZip
    {
        [DataMember(Name = "City")]
        public string City { get; set; }
        [DataMember(Name = "State")]
        public string State { get; set; }
        [DataMember(Name = "Zip")]
        public string Zip { get; set; }
        [DataMember(Name = "GeneralDeliveryService")]
        public string GeneralDeliveryService { get; set; }
        [DataMember(Name = "POBoxService")]
        public string POBoxService { get; set; }
        [DataMember(Name = "StreetService")]
        public string StreetService { get; set; }
        [DataMember(Name = "RRHCService")]
        public string RRHCService { get; set; }
        [DataMember(Name = "UrbanizationService")]
        public string UrbanizationService { get; set; }
        [DataMember(Name = "POBoxRangeLow")]
        public string POBoxRangeLow { get; set; }
        [DataMember(Name = "POBoxRangeHigh")]
        public string POBoxRangeHigh { get; set; }
        [DataMember(Name = "IsUniqueZipCode")]
        public string IsUniqueZipCode { get; set; }
        public override string ToString()
        {
            return $"City: {City} " +
                $"State: {State} " +
                $"Zip: {Zip} " +
                $"GeneralDeliveryService: {GeneralDeliveryService} " +
                $"POBoxService: {POBoxService} " +
                $"StreetService: {StreetService} " +
                $"RRHCService: {RRHCService} " +
                $"UrbanizationService: {UrbanizationService} " +
                $"POBoxRangeLow: {POBoxRangeLow} " +
                $"POBoxRangeHigh: {POBoxRangeHigh} " +
                $"IsUniqueZipCode: {IsUniqueZipCode} ";
        }
    }

    public class Error
    {
        [DataMember(Name = "Type")]
        public string Type { get; set; }
        [DataMember(Name = "TypeCode")]
        public string TypeCode { get; set; }
        [DataMember(Name = "Desc")]
        public string Desc { get; set; }
        [DataMember(Name = "DescCode")]
        public string DescCode { get; set; }
        public override string ToString()
        {
            return $"Type: {Type} " +
                $"TypeCode: {TypeCode} " +
                $"Desc: {Desc} " +
                $"DescCode: {DescCode} ";
        }
    }
}


using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Web;

namespace address_validation_us_3_dot_net.REST
{
    public class Helper
    {
        public static T HttpGet<T>(string url, int timeoutSeconds)
        {
            using var httpClient = new HttpClient
            {
                Timeout = TimeSpan.FromSeconds(timeoutSeconds)
            };
            using var request = new HttpRequestMessage(HttpMethod.Get, url);
            using HttpResponseMessage response = httpClient
                .SendAsync(request)
                .GetAwaiter()
                .GetResult();
            response.EnsureSuccessStatusCode();
            using Stream responseStream = response.Content
                .ReadAsStreamAsync()
                .GetAwaiter()
                .GetResult();
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };
            object? obj = JsonSerializer.Deserialize(responseStream, typeof(T), options);
            T result = (T)obj!;
            return result;
        }

        // Asynchronous HTTP GET and JSON deserialize
        public static async Task<T> HttpGetAsync<T>(string url, int timeoutSeconds)
        {
            HttpClient HttpClient = new HttpClient();
            HttpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
            using var httpResponse = await HttpClient.GetAsync(url).ConfigureAwait(false);
            httpResponse.EnsureSuccessStatusCode();
            var stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
            return JsonSerializer.Deserialize<T>(stream)!;
        }

        public static string UrlEncode(string value) => HttpUtility.UrlEncode(value ?? string.Empty);
    }
}

Address Validation US 3 Python Code Snippet

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
'''
Service Objects, Inc.
This module provides the get_best_matches function to validate and standardize US addresses
using Service Objects AV3 API. It handles live/trial endpoints, fallback logic, and JSON parsing.
Functions:
get_best_matches(business_name: str,
address: str,
address_2: str,
city: str,
state: str,
postal_code: str,
license_key: str,
is_live: bool) -> dict:
'''
import requests # HTTP client for RESTful API calls
# Endpoint URLs for AV3 service
def get_best_matches(business_name: str,
address: str,
address_2: str,
city: str,
state: str,
postal_code: str,
license_key: str,
is_live: bool) -> dict:
"""
Call AV3 get_best_matches API and return validation results.
Parameters:
business_name (str): Company name to assist suite parsing.
address (str): Primary street address (e.g., '123 Main St'). Required.
address_2 (str): Secondary address info (e.g., 'C/O John Smith').
city (str): City name; required if PostalCode not provided.
state (str): State code or full name; required if PostalCode not provided.
postal_code (str): 5- or 9-digit ZIP; required if City/State not provided.
license_key (str): Service Objects license key.
is_live (bool): True for production endpoints, False for trial URL.
Returns:
dict: Parsed JSON response with address addresss or error details.
Raises:
RuntimeError: If the API returns an error payload.
requests.RequestException: On network/HTTP failures (trial mode).
"""
# Prepare query parameters for AV3 API
params = {
'BusinessName': business_name,
'Address' : address,
'Address2' : address_2,
'City' : city,
'State' : state,
'PostalCode' : postal_code,
'LicenseKey' : license_key
}
# Select the base URL: production vs trial
url = primary_url if is_live else trial_url
# Attempt primary (or trial) endpoint first
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
# If API returned an error in JSON payload, trigger fallback
error = getattr(response, 'Error', None)
if not (error is None or getattr(error, 'TypeCode', None) != "3"):
if is_live:
# Try backup URL when live
response = requests.get(backup_url, params=params, timeout=10)
data = response.json()
# If still error, propagate exception
if 'Error' in data:
raise RuntimeError(f"AV3 service error: {data['Error']}")
else:
# Trial mode should not fallback; error is terminal
raise RuntimeError(f"AV3 trial error: {data['Error']}")
# Success: return parsed JSON data
return data
except requests.RequestException as req_exc:
# Network or HTTP-level error occurred
if is_live:
try:
# Fallback to backup URL on network failure
response = requests.get(backup_url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if 'Error' in data:
raise RuntimeError(f"AV3 backup error: {data['Error']}") from req_exc
return data
except Exception as backup_exc:
# Both primary and backup failed; escalate
raise RuntimeError("AV3 service unreachable on both endpoints") from backup_exc
else:
# In trial mode, propagate the network exception
raise RuntimeError(f"AV3 trial error: {str(req_exc)}") from req_exc
''' Service Objects, Inc. This module provides the get_best_matches function to validate and standardize US addresses using Service Objects AV3 API. It handles live/trial endpoints, fallback logic, and JSON parsing. Functions: get_best_matches(business_name: str, address: str, address_2: str, city: str, state: str, postal_code: str, license_key: str, is_live: bool) -> dict: ''' import requests # HTTP client for RESTful API calls # Endpoint URLs for AV3 service primary_url = 'https://sws.serviceobjects.com/AV3/api.svc/GetBestMatchesJson?' backup_url = 'https://swsbackup.serviceobjects.com/AV3/api.svc/GetBestMatchesJson?' trial_url = 'https://trial.serviceobjects.com/AV3/api.svc/GetBestMatchesJson?' def get_best_matches(business_name: str, address: str, address_2: str, city: str, state: str, postal_code: str, license_key: str, is_live: bool) -> dict: """ Call AV3 get_best_matches API and return validation results. Parameters: business_name (str): Company name to assist suite parsing. address (str): Primary street address (e.g., '123 Main St'). Required. address_2 (str): Secondary address info (e.g., 'C/O John Smith'). city (str): City name; required if PostalCode not provided. state (str): State code or full name; required if PostalCode not provided. postal_code (str): 5- or 9-digit ZIP; required if City/State not provided. license_key (str): Service Objects license key. is_live (bool): True for production endpoints, False for trial URL. Returns: dict: Parsed JSON response with address addresss or error details. Raises: RuntimeError: If the API returns an error payload. requests.RequestException: On network/HTTP failures (trial mode). """ # Prepare query parameters for AV3 API params = { 'BusinessName': business_name, 'Address' : address, 'Address2' : address_2, 'City' : city, 'State' : state, 'PostalCode' : postal_code, 'LicenseKey' : license_key } # Select the base URL: production vs trial url = primary_url if is_live else trial_url # Attempt primary (or trial) endpoint first try: response = requests.get(url, params=params, timeout=10) response.raise_for_status() data = response.json() # If API returned an error in JSON payload, trigger fallback error = getattr(response, 'Error', None) if not (error is None or getattr(error, 'TypeCode', None) != "3"): if is_live: # Try backup URL when live response = requests.get(backup_url, params=params, timeout=10) data = response.json() # If still error, propagate exception if 'Error' in data: raise RuntimeError(f"AV3 service error: {data['Error']}") else: # Trial mode should not fallback; error is terminal raise RuntimeError(f"AV3 trial error: {data['Error']}") # Success: return parsed JSON data return data except requests.RequestException as req_exc: # Network or HTTP-level error occurred if is_live: try: # Fallback to backup URL on network failure response = requests.get(backup_url, params=params, timeout=10) response.raise_for_status() data = response.json() if 'Error' in data: raise RuntimeError(f"AV3 backup error: {data['Error']}") from req_exc return data except Exception as backup_exc: # Both primary and backup failed; escalate raise RuntimeError("AV3 service unreachable on both endpoints") from backup_exc else: # In trial mode, propagate the network exception raise RuntimeError(f"AV3 trial error: {str(req_exc)}") from req_exc
'''
Service Objects, Inc.

This module provides the get_best_matches function to validate and standardize US addresses
using Service Objects AV3 API. It handles live/trial endpoints, fallback logic, and JSON parsing.

Functions:
    get_best_matches(business_name: str,
                     address: str,
                     address_2: str,
                     city: str,
                     state: str,
                     postal_code: str,
                     license_key: str,
                     is_live: bool) -> dict:
'''  

import requests  # HTTP client for RESTful API calls

# Endpoint URLs for AV3 service
primary_url = 'https://sws.serviceobjects.com/AV3/api.svc/GetBestMatchesJson?'
backup_url = 'https://swsbackup.serviceobjects.com/AV3/api.svc/GetBestMatchesJson?'
trial_url = 'https://trial.serviceobjects.com/AV3/api.svc/GetBestMatchesJson?'

def get_best_matches(business_name: str,
                   address: str,
                   address_2: str,
                   city: str,
                   state: str,
                   postal_code: str,
                   license_key: str,
                   is_live: bool) -> dict:
    """
    Call AV3 get_best_matches API and return validation results.

    Parameters:
        business_name (str): Company name to assist suite parsing.
        address (str): Primary street address (e.g., '123 Main St'). Required.
        address_2 (str): Secondary address info (e.g., 'C/O John Smith').
        city (str): City name; required if PostalCode not provided.
        state (str): State code or full name; required if PostalCode not provided.
        postal_code (str): 5- or 9-digit ZIP; required if City/State not provided.
        license_key (str): Service Objects license key.
        is_live (bool): True for production endpoints, False for trial URL.

    Returns:
        dict: Parsed JSON response with address addresss or error details.

    Raises:
        RuntimeError: If the API returns an error payload.
        requests.RequestException: On network/HTTP failures (trial mode).
    """

    # Prepare query parameters for AV3 API
    params = {
        'BusinessName': business_name,
        'Address'     : address,
        'Address2'    : address_2,
        'City'        : city,
        'State'       : state,
        'PostalCode'  : postal_code,
        'LicenseKey'  : license_key
    }

    # Select the base URL: production vs trial
    url = primary_url if is_live else trial_url

    # Attempt primary (or trial) endpoint first
    try:
        response = requests.get(url, params=params, timeout=10)
        response.raise_for_status()
        data = response.json()

        # If API returned an error in JSON payload, trigger fallback
        error = getattr(response, 'Error', None)
        if not (error is None or getattr(error, 'TypeCode', None) != "3"):
            if is_live:
                # Try backup URL when live
                response = requests.get(backup_url, params=params, timeout=10)
                data = response.json()

                # If still error, propagate exception
                if 'Error' in data:
                    raise RuntimeError(f"AV3 service error: {data['Error']}")
            else:
                # Trial mode should not fallback; error is terminal
                raise RuntimeError(f"AV3 trial error: {data['Error']}")

        # Success: return parsed JSON data
        return data

    except requests.RequestException as req_exc:
        # Network or HTTP-level error occurred
        if is_live:
            try:
                # Fallback to backup URL on network failure
                response = requests.get(backup_url, params=params, timeout=10)
                response.raise_for_status()
                data = response.json()
                if 'Error' in data:
                    raise RuntimeError(f"AV3 backup error: {data['Error']}") from req_exc
                return data
            except Exception as backup_exc:
                # Both primary and backup failed; escalate
                raise RuntimeError("AV3 service unreachable on both endpoints") from backup_exc
        else:
            # In trial mode, propagate the network exception
            raise RuntimeError(f"AV3 trial error: {str(req_exc)}") from req_exc

Address Validation 3 NodeJS REST Code Snippet

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import axios from 'axios';
import querystring from 'querystring';
import { GetBestMatchesResponse } from './av3_response.js';
/**
* @constant
* @type {string}
* @description The base URL for the live ServiceObjects Address Validation (AV3) API service.
*/
/**
* @constant
* @type {string}
* @description The base URL for the backup ServiceObjects Address Validation (AV3) API service.
*/
/**
* @constant
* @type {string}
* @description The base URL for the trial ServiceObjects Address Validation (AV3) API service.
*/
/**
* <summary>
* Checks if a response from the API is valid by verifying that it either has no Error object
* or the Error.TypeCode is not equal to '3'.
* </summary>
* <param name="response" type="Object">The API response object to validate.</param>
* <returns type="boolean">True if the response is valid, false otherwise.</returns>
*/
const isValid = (response) => !response?.Error || response.Error.TypeCode !== '3';
/**
* <summary>
* Constructs a full URL for the GetBestMatches API endpoint by combining the base URL
* with query parameters derived from the input parameters.
* </summary>
* <param name="params" type="Object">An object containing all the input parameters.</param>
* <param name="baseUrl" type="string">The base URL for the API service (live, backup, or trial).</param>
* <returns type="string">The constructed URL with query parameters.</returns>
*/
const buildUrl = (params, baseUrl) =>
`${baseUrl}GetBestMatchesJson?${querystring.stringify(params)}`;
/**
* <summary>
* Performs an HTTP GET request to the specified URL with a given timeout.
* </summary>
* <param name="url" type="string">The URL to send the GET request to.</param>
* <param name="timeoutSeconds" type="number">The timeout duration in seconds for the request.</param>
* <returns type="Promise<GetBestMatchesResponse>">A promise that resolves to a GetBestMatchesResponse object containing the API response data.</returns>
* <exception cref="Error">Thrown if the HTTP request fails, with a message detailing the error.</exception>
*/
const httpGet = async (url, timeoutSeconds) => {
try {
const response = await axios.get(url, { timeout: timeoutSeconds * 1000 });
return new GetBestMatchesResponse(response.data);
} catch (error) {
throw new Error(`HTTP request failed: ${error.message}`);
}
};
/**
* <summary>
* Provides functionality to call the ServiceObjects Address Validation (AV3) API's GetBestMatches endpoint,
* retrieving validated and corrected address information for a given U.S. address with fallback to a backup endpoint for reliability in live mode.
* </summary>
*/
const GetBestMatchesClient = {
/**
* <summary>
* Asynchronously invokes the GetBestMatches API endpoint, attempting the primary endpoint
* first and falling back to the backup if the response is invalid (Error.TypeCode == '3') in live mode.
* </summary>
* @param {string} BusinessName - Name of business associated with this address. Optional.
* @param {string} Address - Address line of the address to validate (e.g., "123 Main Street"). Required.
* @param {string} Address2 - Additional address information (e.g., "C/O John Smith"). Optional.
* @param {string} City - The city of the address to validate (e.g., "New York"). Optional, but required if PostalCode is not provided.
* @param {string} State - The state of the address to validate (e.g., "NY"). Optional, but required if PostalCode is not provided.
* @param {string} PostalCode - The zip code of the address to validate. Optional, but required if City and State are not provided.
* @param {string} LicenseKey - Your license key to use the service. Required.
* @param {boolean} isLive - Value to determine whether to use the live or trial servers. Defaults to true.
* @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service. Defaults to 15.
* @returns {Promise<GetBestMatchesResponse>} - A promise that resolves to a GetBestMatchesResponse object.
*/
async invokeAsync(BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive = true, timeoutSeconds = 15) {
const params = {
BusinessName,
Address,
Address2,
City,
State,
PostalCode,
LicenseKey
};
// Remove null/undefined params to avoid empty query params
Object.keys(params).forEach(key => params[key] == null && delete params[key]);
const url = buildUrl(params, isLive ? LiveBaseUrl : TrialBaseUrl);
let response = await httpGet(url, timeoutSeconds);
if (isLive && !isValid(response)) {
const fallbackUrl = buildUrl(params, BackupBaseUrl);
const fallbackResponse = await httpGet(fallbackUrl, timeoutSeconds);
return fallbackResponse;
}
return response;
},
/**
* <summary>
* Synchronously invokes the GetBestMatches API endpoint by wrapping the async call
* and awaiting its result immediately.
* </summary>
* @param {string} BusinessName - Name of business associated with this address. Optional.
* @param {string} Address - Address line of the address to validate (e.g., "123 Main Street"). Required.
* @param {string} Address2 - Additional address information (e.g., "C/O John Smith"). Optional.
* @param {string} City - The city of the address to validate (e.g., "New York"). Optional, but required if PostalCode is not provided.
* @param {string} State - The state of the address to validate (e.g., "NY"). Optional, but required if PostalCode is not provided.
* @param {string} PostalCode - The zip code of the address to validate. Optional, but required if City and State are not provided.
* @param {string} LicenseKey - Your license key to use the service. Required.
* @param {boolean} isLive - Value to determine whether to use the live or trial servers. Defaults to true.
* @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service. Defaults to 15.
* @returns {GetBestMatchesResponse} - A GetBestMatchesResponse object with validated address details or an error.
*/
invoke(BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive = true, timeoutSeconds = 15) {
return (async () => await this.invokeAsync(
BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive, timeoutSeconds
))();
}
};
export { GetBestMatchesClient, GetBestMatchesResponse };
export class Address {
constructor(data = {}) {
this.Address1 = data.Address1;
this.Address2 = data.Address2;
this.City = data.City;
this.State = data.State;
this.Zip = data.Zip;
this.IsResidential = data.IsResidential;
this.DPV = data.DPV;
this.DPVDesc = data.DPVDesc;
this.DPVNotes = data.DPVNotes;
this.DPVNotesDesc = data.DPVNotesDesc;
this.Corrections = data.Corrections;
this.CorrectionsDesc = data.CorrectionsDesc;
this.BarcodeDigits = data.BarcodeDigits;
this.CarrierRoute = data.CarrierRoute;
this.CongressCode = data.CongressCode;
this.CountyCode = data.CountyCode;
this.CountyName = data.CountyName;
this.FragmentHouse = data.FragmentHouse;
this.FragmentPreDir = data.FragmentPreDir;
this.FragmentStreet = data.FragmentStreet;
this.FragmentSuffix = data.FragmentSuffix;
this.FragmentPostDir = data.FragmentPostDir;
this.FragmentUnit = data.FragmentUnit;
this.Fragment = data.Fragment;
this.FragmentPMBPrefix = data.FragmentPMBPrefix;
this.FragmentPMBNumber = data.FragmentPMBNumber;
}
toString() {
return `Address1: ${this.Address1}, ` +
`Address2: ${this.Address2}, ` +
`City: ${this.City}, ` +
`State: ${this.State}, ` +
`Zip: ${this.Zip}, ` +
`IsResidential: ${this.IsResidential}, ` +
`DPV: ${this.DPV}, ` +
`DPVDesc: ${this.DPVDesc}, ` +
`DPVNotes: ${this.DPVNotes}, ` +
`DPVNotesDesc: ${this.DPVNotesDesc}, ` +
`Corrections: ${this.Corrections}, ` +
`CorrectionsDesc: ${this.CorrectionsDesc}, ` +
`BarcodeDigits: ${this.BarcodeDigits}, ` +
`CarrierRoute: ${this.CarrierRoute}, ` +
`CongressCode: ${this.CongressCode}, ` +
`CountyCode: ${this.CountyCode}, ` +
`CountyName: ${this.CountyName}, ` +
`FragmentHouse: ${this.FragmentHouse}, ` +
`FragmentPreDir: ${this.FragmentPreDir}, ` +
`FragmentStreet: ${this.FragmentStreet}, ` +
`FragmentSuffix: ${this.FragmentSuffix}, ` +
`FragmentPostDir: ${this.FragmentPostDir}, ` +
`FragmentUnit: ${this.FragmentUnit}, ` +
`Fragment: ${this.Fragment}, ` +
`FragmentPMBPrefix: ${this.FragmentPMBPrefix}, ` +
`FragmentPMBNumber: ${this.FragmentPMBNumber}`;
}
}
export class Error {
constructor(data = {}) {
this.Type = data.Type;
this.TypeCode = data.TypeCode;
this.Desc = data.Desc;
this.DescCode = data.DescCode;
}
toString() {
return `Type: ${this.Type}, TypeCode: ${this.TypeCode}, Desc: ${this.Desc}, DescCode: ${this.DescCode}`;
}
}
export class GetBestMatchesResponse {
constructor(data = {}) {
this.Addresses = (data.Addresses || []).map(address => new Address(address));
this.IsCASS = data.IsCASS;
this.Error = data.Error ? new Error(data.Error) : null;
}
toString() {
const addressesString = this.Addresses.length
? this.Addresses.map(address => address.toString()).join('\n')
: 'None';
return `GetBestMatchesResponse:\n` +
`Addresses: [${addressesString}], ` +
`IsCASS: ${this.IsCASS}, ` +
`Error: ${this.Error ? this.Error.toString() : 'null'}`;
}
}
export class GetSecondaryNumbersResponse {
constructor(data = {}) {
this.Address1 = data.Address1;
this.City = data.City;
this.State = data.State;
this.Zip = data.Zip;
this.SecondaryNumbers = data.SecondaryNumbers || [];
this.TotalCount = data.TotalCount || 0;
this.Error = data.Error ? new Error(data.Error) : null;
}
toString() {
const secondaryNumbersString = this.SecondaryNumbers.length
? this.SecondaryNumbers.join(', ')
: 'None';
return `GetSecondaryNumbersResponse:\n` +
`Address1: ${this.Address1}, ` +
`City: ${this.City}, ` +
`State: ${this.State}, ` +
`Zip: ${this.Zip}, ` +
`SecondaryNumbers: [${secondaryNumbersString}], ` +
`TotalCount: ${this.TotalCount}, ` +
`Error: ${this.Error ? this.Error.toString() : 'null'}`;
}
}
export class CityStateZip {
constructor(data = {}) {
this.City = data.City;
this.State = data.State;
this.Zip = data.Zip;
this.GeneralDeliveryService = data.GeneralDeliveryService;
this.POBoxService = data.POBoxService;
this.StreetService = data.StreetService;
this.RRHCService = data.RRHCService;
this.UrbanizationService = data.UrbanizationService;
this.POBoxRangeLow = data.POBoxRangeLow;
this.POBoxRangeHigh = data.POBoxRangeHigh;
this.IsUniqueZipCode = data.IsUniqueZipCode;
}
toString() {
return `City: ${this.City}, ` +
`State: ${this.State}, ` +
`Zip: ${this.Zip}, ` +
`GeneralDeliveryService: ${this.GeneralDeliveryService}, ` +
`POBoxService: ${this.POBoxService}, ` +
`StreetService: ${this.StreetService}, ` +
`RRHCService: ${this.RRHCService}, ` +
`UrbanizationService: ${this.UrbanizationService}, ` +
`POBoxRangeLow: ${this.POBoxRangeLow}, ` +
`POBoxRangeHigh: ${this.POBoxRangeHigh}, ` +
`IsUniqueZipCode: ${this.IsUniqueZipCode}`;
}
}
export class ValidateCityStateZipResponse {
constructor(data = {}) {
this.CityStateZip = data.CityStateZip ? new CityStateZip(data.CityStateZip) : null;
this.Error = data.Error ? new Error(data.Error) : null;
}
toString() {
return `ValidateCityStateZipResponse:\n` +
`CityStateZip: ${this.CityStateZip ? this.CityStateZip.toString() : 'null'}, ` +
`Error: ${this.Error ? this.Error.toString() : 'null'}`;
}
}
export class GetBestMatchesSingleLineResponse {
constructor(data = {}) {
this.Addresses = (data.Addresses || []).map(address => new Address(address));
this.IsCASS = data.IsCASS;
this.Error = data.Error ? new Error(data.Error) : null;
}
toString() {
const addressesString = this.Addresses.length
? this.Addresses.map(address => address.toString()).join('\n')
: 'None';
return `GetBestMatchesSingleLineResponse:\n` +
`Addresses: [${addressesString}], ` +
`IsCASS: ${this.IsCASS}, ` +
`Error: ${this.Error ? this.Error.toString() : 'null'}`;
}
}
export default {
GetBestMatchesResponse,
GetSecondaryNumbersResponse,
ValidateCityStateZipResponse,
GetBestMatchesSingleLineResponse
};
import axios from 'axios'; import querystring from 'querystring'; import { GetBestMatchesResponse } from './av3_response.js'; /** * @constant * @type {string} * @description The base URL for the live ServiceObjects Address Validation (AV3) API service. */ const LiveBaseUrl = 'https://sws.serviceobjects.com/av3/api.svc/json/'; /** * @constant * @type {string} * @description The base URL for the backup ServiceObjects Address Validation (AV3) API service. */ const BackupBaseUrl = 'https://swsbackup.serviceobjects.com/av3/api.svc/json/'; /** * @constant * @type {string} * @description The base URL for the trial ServiceObjects Address Validation (AV3) API service. */ const TrialBaseUrl = 'https://trial.serviceobjects.com/av3/api.svc/json/'; /** * <summary> * Checks if a response from the API is valid by verifying that it either has no Error object * or the Error.TypeCode is not equal to '3'. * </summary> * <param name="response" type="Object">The API response object to validate.</param> * <returns type="boolean">True if the response is valid, false otherwise.</returns> */ const isValid = (response) => !response?.Error || response.Error.TypeCode !== '3'; /** * <summary> * Constructs a full URL for the GetBestMatches API endpoint by combining the base URL * with query parameters derived from the input parameters. * </summary> * <param name="params" type="Object">An object containing all the input parameters.</param> * <param name="baseUrl" type="string">The base URL for the API service (live, backup, or trial).</param> * <returns type="string">The constructed URL with query parameters.</returns> */ const buildUrl = (params, baseUrl) => `${baseUrl}GetBestMatchesJson?${querystring.stringify(params)}`; /** * <summary> * Performs an HTTP GET request to the specified URL with a given timeout. * </summary> * <param name="url" type="string">The URL to send the GET request to.</param> * <param name="timeoutSeconds" type="number">The timeout duration in seconds for the request.</param> * <returns type="Promise<GetBestMatchesResponse>">A promise that resolves to a GetBestMatchesResponse object containing the API response data.</returns> * <exception cref="Error">Thrown if the HTTP request fails, with a message detailing the error.</exception> */ const httpGet = async (url, timeoutSeconds) => { try { const response = await axios.get(url, { timeout: timeoutSeconds * 1000 }); return new GetBestMatchesResponse(response.data); } catch (error) { throw new Error(`HTTP request failed: ${error.message}`); } }; /** * <summary> * Provides functionality to call the ServiceObjects Address Validation (AV3) API's GetBestMatches endpoint, * retrieving validated and corrected address information for a given U.S. address with fallback to a backup endpoint for reliability in live mode. * </summary> */ const GetBestMatchesClient = { /** * <summary> * Asynchronously invokes the GetBestMatches API endpoint, attempting the primary endpoint * first and falling back to the backup if the response is invalid (Error.TypeCode == '3') in live mode. * </summary> * @param {string} BusinessName - Name of business associated with this address. Optional. * @param {string} Address - Address line of the address to validate (e.g., "123 Main Street"). Required. * @param {string} Address2 - Additional address information (e.g., "C/O John Smith"). Optional. * @param {string} City - The city of the address to validate (e.g., "New York"). Optional, but required if PostalCode is not provided. * @param {string} State - The state of the address to validate (e.g., "NY"). Optional, but required if PostalCode is not provided. * @param {string} PostalCode - The zip code of the address to validate. Optional, but required if City and State are not provided. * @param {string} LicenseKey - Your license key to use the service. Required. * @param {boolean} isLive - Value to determine whether to use the live or trial servers. Defaults to true. * @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service. Defaults to 15. * @returns {Promise<GetBestMatchesResponse>} - A promise that resolves to a GetBestMatchesResponse object. */ async invokeAsync(BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive = true, timeoutSeconds = 15) { const params = { BusinessName, Address, Address2, City, State, PostalCode, LicenseKey }; // Remove null/undefined params to avoid empty query params Object.keys(params).forEach(key => params[key] == null && delete params[key]); const url = buildUrl(params, isLive ? LiveBaseUrl : TrialBaseUrl); let response = await httpGet(url, timeoutSeconds); if (isLive && !isValid(response)) { const fallbackUrl = buildUrl(params, BackupBaseUrl); const fallbackResponse = await httpGet(fallbackUrl, timeoutSeconds); return fallbackResponse; } return response; }, /** * <summary> * Synchronously invokes the GetBestMatches API endpoint by wrapping the async call * and awaiting its result immediately. * </summary> * @param {string} BusinessName - Name of business associated with this address. Optional. * @param {string} Address - Address line of the address to validate (e.g., "123 Main Street"). Required. * @param {string} Address2 - Additional address information (e.g., "C/O John Smith"). Optional. * @param {string} City - The city of the address to validate (e.g., "New York"). Optional, but required if PostalCode is not provided. * @param {string} State - The state of the address to validate (e.g., "NY"). Optional, but required if PostalCode is not provided. * @param {string} PostalCode - The zip code of the address to validate. Optional, but required if City and State are not provided. * @param {string} LicenseKey - Your license key to use the service. Required. * @param {boolean} isLive - Value to determine whether to use the live or trial servers. Defaults to true. * @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service. Defaults to 15. * @returns {GetBestMatchesResponse} - A GetBestMatchesResponse object with validated address details or an error. */ invoke(BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive = true, timeoutSeconds = 15) { return (async () => await this.invokeAsync( BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive, timeoutSeconds ))(); } }; export { GetBestMatchesClient, GetBestMatchesResponse }; export class Address { constructor(data = {}) { this.Address1 = data.Address1; this.Address2 = data.Address2; this.City = data.City; this.State = data.State; this.Zip = data.Zip; this.IsResidential = data.IsResidential; this.DPV = data.DPV; this.DPVDesc = data.DPVDesc; this.DPVNotes = data.DPVNotes; this.DPVNotesDesc = data.DPVNotesDesc; this.Corrections = data.Corrections; this.CorrectionsDesc = data.CorrectionsDesc; this.BarcodeDigits = data.BarcodeDigits; this.CarrierRoute = data.CarrierRoute; this.CongressCode = data.CongressCode; this.CountyCode = data.CountyCode; this.CountyName = data.CountyName; this.FragmentHouse = data.FragmentHouse; this.FragmentPreDir = data.FragmentPreDir; this.FragmentStreet = data.FragmentStreet; this.FragmentSuffix = data.FragmentSuffix; this.FragmentPostDir = data.FragmentPostDir; this.FragmentUnit = data.FragmentUnit; this.Fragment = data.Fragment; this.FragmentPMBPrefix = data.FragmentPMBPrefix; this.FragmentPMBNumber = data.FragmentPMBNumber; } toString() { return `Address1: ${this.Address1}, ` + `Address2: ${this.Address2}, ` + `City: ${this.City}, ` + `State: ${this.State}, ` + `Zip: ${this.Zip}, ` + `IsResidential: ${this.IsResidential}, ` + `DPV: ${this.DPV}, ` + `DPVDesc: ${this.DPVDesc}, ` + `DPVNotes: ${this.DPVNotes}, ` + `DPVNotesDesc: ${this.DPVNotesDesc}, ` + `Corrections: ${this.Corrections}, ` + `CorrectionsDesc: ${this.CorrectionsDesc}, ` + `BarcodeDigits: ${this.BarcodeDigits}, ` + `CarrierRoute: ${this.CarrierRoute}, ` + `CongressCode: ${this.CongressCode}, ` + `CountyCode: ${this.CountyCode}, ` + `CountyName: ${this.CountyName}, ` + `FragmentHouse: ${this.FragmentHouse}, ` + `FragmentPreDir: ${this.FragmentPreDir}, ` + `FragmentStreet: ${this.FragmentStreet}, ` + `FragmentSuffix: ${this.FragmentSuffix}, ` + `FragmentPostDir: ${this.FragmentPostDir}, ` + `FragmentUnit: ${this.FragmentUnit}, ` + `Fragment: ${this.Fragment}, ` + `FragmentPMBPrefix: ${this.FragmentPMBPrefix}, ` + `FragmentPMBNumber: ${this.FragmentPMBNumber}`; } } export class Error { constructor(data = {}) { this.Type = data.Type; this.TypeCode = data.TypeCode; this.Desc = data.Desc; this.DescCode = data.DescCode; } toString() { return `Type: ${this.Type}, TypeCode: ${this.TypeCode}, Desc: ${this.Desc}, DescCode: ${this.DescCode}`; } } export class GetBestMatchesResponse { constructor(data = {}) { this.Addresses = (data.Addresses || []).map(address => new Address(address)); this.IsCASS = data.IsCASS; this.Error = data.Error ? new Error(data.Error) : null; } toString() { const addressesString = this.Addresses.length ? this.Addresses.map(address => address.toString()).join('\n') : 'None'; return `GetBestMatchesResponse:\n` + `Addresses: [${addressesString}], ` + `IsCASS: ${this.IsCASS}, ` + `Error: ${this.Error ? this.Error.toString() : 'null'}`; } } export class GetSecondaryNumbersResponse { constructor(data = {}) { this.Address1 = data.Address1; this.City = data.City; this.State = data.State; this.Zip = data.Zip; this.SecondaryNumbers = data.SecondaryNumbers || []; this.TotalCount = data.TotalCount || 0; this.Error = data.Error ? new Error(data.Error) : null; } toString() { const secondaryNumbersString = this.SecondaryNumbers.length ? this.SecondaryNumbers.join(', ') : 'None'; return `GetSecondaryNumbersResponse:\n` + `Address1: ${this.Address1}, ` + `City: ${this.City}, ` + `State: ${this.State}, ` + `Zip: ${this.Zip}, ` + `SecondaryNumbers: [${secondaryNumbersString}], ` + `TotalCount: ${this.TotalCount}, ` + `Error: ${this.Error ? this.Error.toString() : 'null'}`; } } export class CityStateZip { constructor(data = {}) { this.City = data.City; this.State = data.State; this.Zip = data.Zip; this.GeneralDeliveryService = data.GeneralDeliveryService; this.POBoxService = data.POBoxService; this.StreetService = data.StreetService; this.RRHCService = data.RRHCService; this.UrbanizationService = data.UrbanizationService; this.POBoxRangeLow = data.POBoxRangeLow; this.POBoxRangeHigh = data.POBoxRangeHigh; this.IsUniqueZipCode = data.IsUniqueZipCode; } toString() { return `City: ${this.City}, ` + `State: ${this.State}, ` + `Zip: ${this.Zip}, ` + `GeneralDeliveryService: ${this.GeneralDeliveryService}, ` + `POBoxService: ${this.POBoxService}, ` + `StreetService: ${this.StreetService}, ` + `RRHCService: ${this.RRHCService}, ` + `UrbanizationService: ${this.UrbanizationService}, ` + `POBoxRangeLow: ${this.POBoxRangeLow}, ` + `POBoxRangeHigh: ${this.POBoxRangeHigh}, ` + `IsUniqueZipCode: ${this.IsUniqueZipCode}`; } } export class ValidateCityStateZipResponse { constructor(data = {}) { this.CityStateZip = data.CityStateZip ? new CityStateZip(data.CityStateZip) : null; this.Error = data.Error ? new Error(data.Error) : null; } toString() { return `ValidateCityStateZipResponse:\n` + `CityStateZip: ${this.CityStateZip ? this.CityStateZip.toString() : 'null'}, ` + `Error: ${this.Error ? this.Error.toString() : 'null'}`; } } export class GetBestMatchesSingleLineResponse { constructor(data = {}) { this.Addresses = (data.Addresses || []).map(address => new Address(address)); this.IsCASS = data.IsCASS; this.Error = data.Error ? new Error(data.Error) : null; } toString() { const addressesString = this.Addresses.length ? this.Addresses.map(address => address.toString()).join('\n') : 'None'; return `GetBestMatchesSingleLineResponse:\n` + `Addresses: [${addressesString}], ` + `IsCASS: ${this.IsCASS}, ` + `Error: ${this.Error ? this.Error.toString() : 'null'}`; } } export default { GetBestMatchesResponse, GetSecondaryNumbersResponse, ValidateCityStateZipResponse, GetBestMatchesSingleLineResponse };
import axios from 'axios';
import querystring from 'querystring';
import { GetBestMatchesResponse } from './av3_response.js';

/**
 * @constant
 * @type {string}
 * @description The base URL for the live ServiceObjects Address Validation (AV3) API service.
 */
const LiveBaseUrl = 'https://sws.serviceobjects.com/av3/api.svc/json/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the backup ServiceObjects Address Validation (AV3) API service.
 */
const BackupBaseUrl = 'https://swsbackup.serviceobjects.com/av3/api.svc/json/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the trial ServiceObjects Address Validation (AV3) API service.
 */
const TrialBaseUrl = 'https://trial.serviceobjects.com/av3/api.svc/json/';

/**
 * <summary>
 * Checks if a response from the API is valid by verifying that it either has no Error object
 * or the Error.TypeCode is not equal to '3'.
 * </summary>
 * <param name="response" type="Object">The API response object to validate.</param>
 * <returns type="boolean">True if the response is valid, false otherwise.</returns>
 */
const isValid = (response) => !response?.Error || response.Error.TypeCode !== '3';

/**
 * <summary>
 * Constructs a full URL for the GetBestMatches API endpoint by combining the base URL
 * with query parameters derived from the input parameters.
 * </summary>
 * <param name="params" type="Object">An object containing all the input parameters.</param>
 * <param name="baseUrl" type="string">The base URL for the API service (live, backup, or trial).</param>
 * <returns type="string">The constructed URL with query parameters.</returns>
 */
const buildUrl = (params, baseUrl) =>
    `${baseUrl}GetBestMatchesJson?${querystring.stringify(params)}`;

/**
 * <summary>
 * Performs an HTTP GET request to the specified URL with a given timeout.
 * </summary>
 * <param name="url" type="string">The URL to send the GET request to.</param>
 * <param name="timeoutSeconds" type="number">The timeout duration in seconds for the request.</param>
 * <returns type="Promise<GetBestMatchesResponse>">A promise that resolves to a GetBestMatchesResponse object containing the API response data.</returns>
 * <exception cref="Error">Thrown if the HTTP request fails, with a message detailing the error.</exception>
 */
const httpGet = async (url, timeoutSeconds) => {
    try {
        const response = await axios.get(url, { timeout: timeoutSeconds * 1000 });
        return new GetBestMatchesResponse(response.data);
    } catch (error) {
        throw new Error(`HTTP request failed: ${error.message}`);
    }
};

/**
 * <summary>
 * Provides functionality to call the ServiceObjects Address Validation (AV3) API's GetBestMatches endpoint,
 * retrieving validated and corrected address information for a given U.S. address with fallback to a backup endpoint for reliability in live mode.
 * </summary>
 */
const GetBestMatchesClient = {
    /**
     * <summary>
     * Asynchronously invokes the GetBestMatches API endpoint, attempting the primary endpoint
     * first and falling back to the backup if the response is invalid (Error.TypeCode == '3') in live mode.
     * </summary>
     * @param {string} BusinessName - Name of business associated with this address. Optional.
     * @param {string} Address - Address line of the address to validate (e.g., "123 Main Street"). Required.
     * @param {string} Address2 - Additional address information (e.g., "C/O John Smith"). Optional.
     * @param {string} City - The city of the address to validate (e.g., "New York"). Optional, but required if PostalCode is not provided.
     * @param {string} State - The state of the address to validate (e.g., "NY"). Optional, but required if PostalCode is not provided.
     * @param {string} PostalCode - The zip code of the address to validate. Optional, but required if City and State are not provided.
     * @param {string} LicenseKey - Your license key to use the service. Required.
     * @param {boolean} isLive - Value to determine whether to use the live or trial servers. Defaults to true.
     * @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service. Defaults to 15.
     * @returns {Promise<GetBestMatchesResponse>} - A promise that resolves to a GetBestMatchesResponse object.
     */
    async invokeAsync(BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive = true, timeoutSeconds = 15) {
        const params = {
            BusinessName,
            Address,
            Address2,
            City,
            State,
            PostalCode,
            LicenseKey
        };

        // Remove null/undefined params to avoid empty query params
        Object.keys(params).forEach(key => params[key] == null && delete params[key]);

        const url = buildUrl(params, isLive ? LiveBaseUrl : TrialBaseUrl);
        let response = await httpGet(url, timeoutSeconds);

        if (isLive && !isValid(response)) {
            const fallbackUrl = buildUrl(params, BackupBaseUrl);
            const fallbackResponse = await httpGet(fallbackUrl, timeoutSeconds);
            return fallbackResponse;
        }
        return response;
    },

    /**
     * <summary>
     * Synchronously invokes the GetBestMatches API endpoint by wrapping the async call
     * and awaiting its result immediately.
     * </summary>
     * @param {string} BusinessName - Name of business associated with this address. Optional.
     * @param {string} Address - Address line of the address to validate (e.g., "123 Main Street"). Required.
     * @param {string} Address2 - Additional address information (e.g., "C/O John Smith"). Optional.
     * @param {string} City - The city of the address to validate (e.g., "New York"). Optional, but required if PostalCode is not provided.
     * @param {string} State - The state of the address to validate (e.g., "NY"). Optional, but required if PostalCode is not provided.
     * @param {string} PostalCode - The zip code of the address to validate. Optional, but required if City and State are not provided.
     * @param {string} LicenseKey - Your license key to use the service. Required.
     * @param {boolean} isLive - Value to determine whether to use the live or trial servers. Defaults to true.
     * @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service. Defaults to 15.
     * @returns {GetBestMatchesResponse} - A GetBestMatchesResponse object with validated address details or an error.
     */
    invoke(BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive = true, timeoutSeconds = 15) {
        return (async () => await this.invokeAsync(
            BusinessName, Address, Address2, City, State, PostalCode, LicenseKey, isLive, timeoutSeconds
        ))();
    }
};

export { GetBestMatchesClient, GetBestMatchesResponse };


export class Address {
    constructor(data = {}) {
        this.Address1 = data.Address1;
        this.Address2 = data.Address2;
        this.City = data.City;
        this.State = data.State;
        this.Zip = data.Zip;
        this.IsResidential = data.IsResidential;
        this.DPV = data.DPV;
        this.DPVDesc = data.DPVDesc;
        this.DPVNotes = data.DPVNotes;
        this.DPVNotesDesc = data.DPVNotesDesc;
        this.Corrections = data.Corrections;
        this.CorrectionsDesc = data.CorrectionsDesc;
        this.BarcodeDigits = data.BarcodeDigits;
        this.CarrierRoute = data.CarrierRoute;
        this.CongressCode = data.CongressCode;
        this.CountyCode = data.CountyCode;
        this.CountyName = data.CountyName;
        this.FragmentHouse = data.FragmentHouse;
        this.FragmentPreDir = data.FragmentPreDir;
        this.FragmentStreet = data.FragmentStreet;
        this.FragmentSuffix = data.FragmentSuffix;
        this.FragmentPostDir = data.FragmentPostDir;
        this.FragmentUnit = data.FragmentUnit;
        this.Fragment = data.Fragment;
        this.FragmentPMBPrefix = data.FragmentPMBPrefix;
        this.FragmentPMBNumber = data.FragmentPMBNumber;
    }

    toString() {
        return `Address1: ${this.Address1}, ` +
            `Address2: ${this.Address2}, ` +
            `City: ${this.City}, ` +
            `State: ${this.State}, ` +
            `Zip: ${this.Zip}, ` +
            `IsResidential: ${this.IsResidential}, ` +
            `DPV: ${this.DPV}, ` +
            `DPVDesc: ${this.DPVDesc}, ` +
            `DPVNotes: ${this.DPVNotes}, ` +
            `DPVNotesDesc: ${this.DPVNotesDesc}, ` +
            `Corrections: ${this.Corrections}, ` +
            `CorrectionsDesc: ${this.CorrectionsDesc}, ` +
            `BarcodeDigits: ${this.BarcodeDigits}, ` +
            `CarrierRoute: ${this.CarrierRoute}, ` +
            `CongressCode: ${this.CongressCode}, ` +
            `CountyCode: ${this.CountyCode}, ` +
            `CountyName: ${this.CountyName}, ` +
            `FragmentHouse: ${this.FragmentHouse}, ` +
            `FragmentPreDir: ${this.FragmentPreDir}, ` +
            `FragmentStreet: ${this.FragmentStreet}, ` +
            `FragmentSuffix: ${this.FragmentSuffix}, ` +
            `FragmentPostDir: ${this.FragmentPostDir}, ` +
            `FragmentUnit: ${this.FragmentUnit}, ` +
            `Fragment: ${this.Fragment}, ` +
            `FragmentPMBPrefix: ${this.FragmentPMBPrefix}, ` +
            `FragmentPMBNumber: ${this.FragmentPMBNumber}`;
    }
}

export class Error {
    constructor(data = {}) {
        this.Type = data.Type;
        this.TypeCode = data.TypeCode;
        this.Desc = data.Desc;
        this.DescCode = data.DescCode;
    }

    toString() {
        return `Type: ${this.Type}, TypeCode: ${this.TypeCode}, Desc: ${this.Desc}, DescCode: ${this.DescCode}`;
    }
}

export class GetBestMatchesResponse {
    constructor(data = {}) {
        this.Addresses = (data.Addresses || []).map(address => new Address(address));
        this.IsCASS = data.IsCASS;
        this.Error = data.Error ? new Error(data.Error) : null;
    }

    toString() {
        const addressesString = this.Addresses.length
            ? this.Addresses.map(address => address.toString()).join('\n')
            : 'None';
        return `GetBestMatchesResponse:\n` +
            `Addresses: [${addressesString}], ` +
            `IsCASS: ${this.IsCASS}, ` +
            `Error: ${this.Error ? this.Error.toString() : 'null'}`;
    }
}

export class GetSecondaryNumbersResponse {
    constructor(data = {}) {
        this.Address1 = data.Address1;
        this.City = data.City;
        this.State = data.State;
        this.Zip = data.Zip;
        this.SecondaryNumbers = data.SecondaryNumbers || [];
        this.TotalCount = data.TotalCount || 0;
        this.Error = data.Error ? new Error(data.Error) : null;
    }

    toString() {
        const secondaryNumbersString = this.SecondaryNumbers.length
            ? this.SecondaryNumbers.join(', ')
            : 'None';
        return `GetSecondaryNumbersResponse:\n` +
            `Address1: ${this.Address1}, ` +
            `City: ${this.City}, ` +
            `State: ${this.State}, ` +
            `Zip: ${this.Zip}, ` +
            `SecondaryNumbers: [${secondaryNumbersString}], ` +
            `TotalCount: ${this.TotalCount}, ` +
            `Error: ${this.Error ? this.Error.toString() : 'null'}`;
    }
}

export class CityStateZip {
    constructor(data = {}) {
        this.City = data.City;
        this.State = data.State;
        this.Zip = data.Zip;
        this.GeneralDeliveryService = data.GeneralDeliveryService;
        this.POBoxService = data.POBoxService;
        this.StreetService = data.StreetService;
        this.RRHCService = data.RRHCService;
        this.UrbanizationService = data.UrbanizationService;
        this.POBoxRangeLow = data.POBoxRangeLow;
        this.POBoxRangeHigh = data.POBoxRangeHigh;
        this.IsUniqueZipCode = data.IsUniqueZipCode;
    }

    toString() {
        return `City: ${this.City}, ` +
            `State: ${this.State}, ` +
            `Zip: ${this.Zip}, ` +
            `GeneralDeliveryService: ${this.GeneralDeliveryService}, ` +
            `POBoxService: ${this.POBoxService}, ` +
            `StreetService: ${this.StreetService}, ` +
            `RRHCService: ${this.RRHCService}, ` +
            `UrbanizationService: ${this.UrbanizationService}, ` +
            `POBoxRangeLow: ${this.POBoxRangeLow}, ` +
            `POBoxRangeHigh: ${this.POBoxRangeHigh}, ` +
            `IsUniqueZipCode: ${this.IsUniqueZipCode}`;
    }
}

export class ValidateCityStateZipResponse {
    constructor(data = {}) {
        this.CityStateZip = data.CityStateZip ? new CityStateZip(data.CityStateZip) : null;
        this.Error = data.Error ? new Error(data.Error) : null;
    }

    toString() {
        return `ValidateCityStateZipResponse:\n` +
            `CityStateZip: ${this.CityStateZip ? this.CityStateZip.toString() : 'null'}, ` +
            `Error: ${this.Error ? this.Error.toString() : 'null'}`;
    }
}

export class GetBestMatchesSingleLineResponse {
    constructor(data = {}) {
        this.Addresses = (data.Addresses || []).map(address => new Address(address));
        this.IsCASS = data.IsCASS;
        this.Error = data.Error ? new Error(data.Error) : null;
    }

    toString() {
        const addressesString = this.Addresses.length
            ? this.Addresses.map(address => address.toString()).join('\n')
            : 'None';
        return `GetBestMatchesSingleLineResponse:\n` +
            `Addresses: [${addressesString}], ` +
            `IsCASS: ${this.IsCASS}, ` +
            `Error: ${this.Error ? this.Error.toString() : 'null'}`;
    }
}

export default {
    GetBestMatchesResponse,
    GetSecondaryNumbersResponse,
    ValidateCityStateZipResponse,
    GetBestMatchesSingleLineResponse
};