- C#
- Python
- NodeJS
Address Validation 3 C# Rest Code Snippet
static void Main(string[] args)
{
//Usage example
GetBestMatchesInput In = new("", "26 S Chestnut", "", "Ventura", "CA", "93033", "XXXXXXXXXXXXXX", true);
GBMResponse Response = GetBestMatchesClient.InvokeAsync(In).Result;
}
public static class GetBestMatchesClient
{
// Base URL constants: production, backup, and trial
/// <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)
{
var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
var response = Helper.HttpGet<GBMResponse>(url, input.TimeoutSeconds);
// Fallback on error payload in live mode
if (input.IsLive && !IsValid(response))
{
var fallbackUrl = BuildUrl(input, BackupBaseUrl);
var fallbackResponse = Helper.HttpGet<GBMResponse>(fallbackUrl, input.TimeoutSeconds);
return IsValid(fallbackResponse) ? fallbackResponse : response;
}
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)
{
var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
var response = await Helper.HttpGetAsync<GBMResponse>(url, input.TimeoutSeconds).ConfigureAwait(false);
if (input.IsLive && !IsValid(response))
{
var fallbackUrl = BuildUrl(input, BackupBaseUrl);
var fallbackResponse = await Helper.HttpGetAsync<GBMResponse>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
return IsValid(fallbackResponse) ? fallbackResponse : response;
}
return response;
}
/// <summary>
/// Input container for the AV3 GetBestMatches operation.
/// Encapsulates all required address fields, license key, and execution options.
/// </summary>
/// <param name="BusinessName">Company name to assist suite parsing (e.g., "Acme Corp").</param>
/// <param name="Address">Primary street address (e.g., "123 Main St").</param>
/// <param name="Address2">Secondary address information (e.g., "C/O John Smith").</param>
/// <param name="City">City name; required if PostalCode is omitted.</param>
/// <param name="State">State code or full name; required if PostalCode is omitted.</param>
/// <param name="PostalCode">5- or 9-digit ZIP; required if City/State are omitted.</param>
/// <param name="LicenseKey">Service Objects AV3 license key.</param>
/// <param name="IsLive">True to use production endpoints (with fallback); false for trial endpoint only.</param>
/// <param name="TimeoutSeconds">Timeout for HTTP requests in seconds (default: 15).</param>
public record GetBestMatchesInput(
string BusinessName,
string Address,
string Address2,
string City,
string State,
string PostalCode,
string LicenseKey,
bool IsLive,
int TimeoutSeconds = 15
);
// 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>
/// Represents the response from the GetBestMatches operation,
/// including a list of validated addresses, CASS status, and any error information.
/// </summary>
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>
/// Represents a single address candidate returned by the AV3 service,
/// including parsed components, delivery indicators, and postal fragments.
/// </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";
}
}
/// <summary>
/// Represents error information returned by the AV3 service,
/// including error type codes and descriptive messages.
/// </summary>
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} ";
}
}
/// <summary>
/// Utility class providing synchronous and asynchronous HTTP GET methods
/// and URL encoding for ServiceObjects API clients.
/// </summary>
public static class Helper
{
// Reuse a single HttpClient instance for performance and resource management
private static readonly HttpClient HttpClient = new HttpClient();
/// <summary>
/// Performs a synchronous HTTP GET request and deserializes the JSON response into the specified type.
/// </summary>
/// <typeparam name="T">The target type to deserialize the JSON response into.</typeparam>
/// <param name="url">Full URL including query string.</param>
/// <param name="timeoutSeconds">Request timeout in seconds.</param>
/// <returns>Deserialized object of type T.</returns>
public static T HttpGet<T>(string url, int timeoutSeconds)
{
HttpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
using var httpResponse = HttpClient.GetAsync(url).GetAwaiter().GetResult();
httpResponse.EnsureSuccessStatusCode();
var stream = httpResponse.Content.ReadAsStream();
return JsonSerializer.Deserialize<T>(stream)!;
}
/// <summary>
/// Performs an asynchronous HTTP GET request and deserializes the JSON response into the specified type.
/// </summary>
/// <typeparam name="T">The target type to deserialize the JSON response into.</typeparam>
/// <param name="url">Full URL including query string.</param>
/// <param name="timeoutSeconds">Request timeout in seconds.</param>
/// <returns>A Task that resolves to a deserialized object of type T.</returns>
public static async Task<T> HttpGetAsync<T>(string url, int timeoutSeconds)
{
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)!;
}
/// <summary>
/// URL-encodes the specified string, treating null as an empty string.
/// </summary>
/// <param name="value">The string to URL-encode.</param>
/// <returns>URL-encoded representation of the input.</returns>
public static string UrlEncode(string value) => HttpUtility.UrlEncode(value ?? string.Empty);
}
static void Main(string[] args)
{
//Usage example
GetBestMatchesInput In = new("", "26 S Chestnut", "", "Ventura", "CA", "93033", "XXXXXXXXXXXXXX", true);
GBMResponse Response = GetBestMatchesClient.InvokeAsync(In).Result;
}
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)
{
var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
var response = Helper.HttpGet<GBMResponse>(url, input.TimeoutSeconds);
// Fallback on error payload in live mode
if (input.IsLive && !IsValid(response))
{
var fallbackUrl = BuildUrl(input, BackupBaseUrl);
var fallbackResponse = Helper.HttpGet<GBMResponse>(fallbackUrl, input.TimeoutSeconds);
return IsValid(fallbackResponse) ? fallbackResponse : response;
}
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)
{
var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
var response = await Helper.HttpGetAsync<GBMResponse>(url, input.TimeoutSeconds).ConfigureAwait(false);
if (input.IsLive && !IsValid(response))
{
var fallbackUrl = BuildUrl(input, BackupBaseUrl);
var fallbackResponse = await Helper.HttpGetAsync<GBMResponse>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
return IsValid(fallbackResponse) ? fallbackResponse : response;
}
return response;
}
/// <summary>
/// Input container for the AV3 GetBestMatches operation.
/// Encapsulates all required address fields, license key, and execution options.
/// </summary>
/// <param name="BusinessName">Company name to assist suite parsing (e.g., "Acme Corp").</param>
/// <param name="Address">Primary street address (e.g., "123 Main St").</param>
/// <param name="Address2">Secondary address information (e.g., "C/O John Smith").</param>
/// <param name="City">City name; required if PostalCode is omitted.</param>
/// <param name="State">State code or full name; required if PostalCode is omitted.</param>
/// <param name="PostalCode">5- or 9-digit ZIP; required if City/State are omitted.</param>
/// <param name="LicenseKey">Service Objects AV3 license key.</param>
/// <param name="IsLive">True to use production endpoints (with fallback); false for trial endpoint only.</param>
/// <param name="TimeoutSeconds">Timeout for HTTP requests in seconds (default: 15).</param>
public record GetBestMatchesInput(
string BusinessName,
string Address,
string Address2,
string City,
string State,
string PostalCode,
string LicenseKey,
bool IsLive,
int TimeoutSeconds = 15
);
// 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>
/// Represents the response from the GetBestMatches operation,
/// including a list of validated addresses, CASS status, and any error information.
/// </summary>
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>
/// Represents a single address candidate returned by the AV3 service,
/// including parsed components, delivery indicators, and postal fragments.
/// </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";
}
}
/// <summary>
/// Represents error information returned by the AV3 service,
/// including error type codes and descriptive messages.
/// </summary>
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} ";
}
}
/// <summary>
/// Utility class providing synchronous and asynchronous HTTP GET methods
/// and URL encoding for ServiceObjects API clients.
/// </summary>
public static class Helper
{
// Reuse a single HttpClient instance for performance and resource management
private static readonly HttpClient HttpClient = new HttpClient();
/// <summary>
/// Performs a synchronous HTTP GET request and deserializes the JSON response into the specified type.
/// </summary>
/// <typeparam name="T">The target type to deserialize the JSON response into.</typeparam>
/// <param name="url">Full URL including query string.</param>
/// <param name="timeoutSeconds">Request timeout in seconds.</param>
/// <returns>Deserialized object of type T.</returns>
public static T HttpGet<T>(string url, int timeoutSeconds)
{
HttpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
using var httpResponse = HttpClient.GetAsync(url).GetAwaiter().GetResult();
httpResponse.EnsureSuccessStatusCode();
var stream = httpResponse.Content.ReadAsStream();
return JsonSerializer.Deserialize<T>(stream)!;
}
/// <summary>
/// Performs an asynchronous HTTP GET request and deserializes the JSON response into the specified type.
/// </summary>
/// <typeparam name="T">The target type to deserialize the JSON response into.</typeparam>
/// <param name="url">Full URL including query string.</param>
/// <param name="timeoutSeconds">Request timeout in seconds.</param>
/// <returns>A Task that resolves to a deserialized object of type T.</returns>
public static async Task<T> HttpGetAsync<T>(string url, int timeoutSeconds)
{
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)!;
}
/// <summary>
/// URL-encodes the specified string, treating null as an empty string.
/// </summary>
/// <param name="value">The string to URL-encode.</param>
/// <returns>URL-encoded representation of the input.</returns>
public static string UrlEncode(string value) => HttpUtility.UrlEncode(value ?? string.Empty);
}
static void Main(string[] args) { //Usage example GetBestMatchesInput In = new("", "26 S Chestnut", "", "Ventura", "CA", "93033", "XXXXXXXXXXXXXX", true); GBMResponse Response = GetBestMatchesClient.InvokeAsync(In).Result; } 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) { var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl); var response = Helper.HttpGet<GBMResponse>(url, input.TimeoutSeconds); // Fallback on error payload in live mode if (input.IsLive && !IsValid(response)) { var fallbackUrl = BuildUrl(input, BackupBaseUrl); var fallbackResponse = Helper.HttpGet<GBMResponse>(fallbackUrl, input.TimeoutSeconds); return IsValid(fallbackResponse) ? fallbackResponse : response; } 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) { var url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl); var response = await Helper.HttpGetAsync<GBMResponse>(url, input.TimeoutSeconds).ConfigureAwait(false); if (input.IsLive && !IsValid(response)) { var fallbackUrl = BuildUrl(input, BackupBaseUrl); var fallbackResponse = await Helper.HttpGetAsync<GBMResponse>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false); return IsValid(fallbackResponse) ? fallbackResponse : response; } return response; } /// <summary> /// Input container for the AV3 GetBestMatches operation. /// Encapsulates all required address fields, license key, and execution options. /// </summary> /// <param name="BusinessName">Company name to assist suite parsing (e.g., "Acme Corp").</param> /// <param name="Address">Primary street address (e.g., "123 Main St").</param> /// <param name="Address2">Secondary address information (e.g., "C/O John Smith").</param> /// <param name="City">City name; required if PostalCode is omitted.</param> /// <param name="State">State code or full name; required if PostalCode is omitted.</param> /// <param name="PostalCode">5- or 9-digit ZIP; required if City/State are omitted.</param> /// <param name="LicenseKey">Service Objects AV3 license key.</param> /// <param name="IsLive">True to use production endpoints (with fallback); false for trial endpoint only.</param> /// <param name="TimeoutSeconds">Timeout for HTTP requests in seconds (default: 15).</param> public record GetBestMatchesInput( string BusinessName, string Address, string Address2, string City, string State, string PostalCode, string LicenseKey, bool IsLive, int TimeoutSeconds = 15 ); // 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> /// Represents the response from the GetBestMatches operation, /// including a list of validated addresses, CASS status, and any error information. /// </summary> 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> /// Represents a single address candidate returned by the AV3 service, /// including parsed components, delivery indicators, and postal fragments. /// </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"; } } /// <summary> /// Represents error information returned by the AV3 service, /// including error type codes and descriptive messages. /// </summary> 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} "; } } /// <summary> /// Utility class providing synchronous and asynchronous HTTP GET methods /// and URL encoding for ServiceObjects API clients. /// </summary> public static class Helper { // Reuse a single HttpClient instance for performance and resource management private static readonly HttpClient HttpClient = new HttpClient(); /// <summary> /// Performs a synchronous HTTP GET request and deserializes the JSON response into the specified type. /// </summary> /// <typeparam name="T">The target type to deserialize the JSON response into.</typeparam> /// <param name="url">Full URL including query string.</param> /// <param name="timeoutSeconds">Request timeout in seconds.</param> /// <returns>Deserialized object of type T.</returns> public static T HttpGet<T>(string url, int timeoutSeconds) { HttpClient.Timeout = TimeSpan.FromSeconds(timeoutSeconds); using var httpResponse = HttpClient.GetAsync(url).GetAwaiter().GetResult(); httpResponse.EnsureSuccessStatusCode(); var stream = httpResponse.Content.ReadAsStream(); return JsonSerializer.Deserialize<T>(stream)!; } /// <summary> /// Performs an asynchronous HTTP GET request and deserializes the JSON response into the specified type. /// </summary> /// <typeparam name="T">The target type to deserialize the JSON response into.</typeparam> /// <param name="url">Full URL including query string.</param> /// <param name="timeoutSeconds">Request timeout in seconds.</param> /// <returns>A Task that resolves to a deserialized object of type T.</returns> public static async Task<T> HttpGetAsync<T>(string url, int timeoutSeconds) { 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)!; } /// <summary> /// URL-encodes the specified string, treating null as an empty string. /// </summary> /// <param name="value">The string to URL-encode.</param> /// <returns>URL-encoded representation of the input.</returns> public static string UrlEncode(string value) => HttpUtility.UrlEncode(value ?? string.Empty); }
Address Validation US 3 Python Code Snippet
'''
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?'
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 candidates or error info.
'''
# 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)
data = response.json()
# If API returned an error in JSON payload, trigger fallback
if 'Error' in data:
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
# TODO: Instrument alerting on repeated failures
if is_live:
try:
# Fallback to backup URL on network failure
response = requests.get(backup_url, params=params, timeout=10)
data = response.json()
if 'Error' in data:
raise RuntimeError(f"AV3 backup error: {data['Error']}")
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
'''
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 candidates or error info.
'''
# 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)
data = response.json()
# If API returned an error in JSON payload, trigger fallback
if 'Error' in data:
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
# TODO: Instrument alerting on repeated failures
if is_live:
try:
# Fallback to backup URL on network failure
response = requests.get(backup_url, params=params, timeout=10)
data = response.json()
if 'Error' in data:
raise RuntimeError(f"AV3 backup error: {data['Error']}")
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
''' 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 candidates or error info. ''' # 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) data = response.json() # If API returned an error in JSON payload, trigger fallback if 'Error' in data: 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 # TODO: Instrument alerting on repeated failures if is_live: try: # Fallback to backup URL on network failure response = requests.get(backup_url, params=params, timeout=10) data = response.json() if 'Error' in data: raise RuntimeError(f"AV3 backup error: {data['Error']}") 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
Address Validation 3 NodeJS REST Code Snippet
/**
* @module AV3AddressValidation
* @description
* Client module for the AV3 GetBestMatchesJson operation.
* Validates and standardizes U.S. addresses via Service Objects’ AV3 API,
* with built‑in production/trial endpoints and automatic fallback logic.
*/
import fetch from 'node-fetch';
// Base endpoints for AV3 service
const ENDPOINTS = {
};
/**
* Build a fully‑qualified URL for the GetBestMatchesJson endpoint.
* @param {object} params Key/value pairs of query parameters.
* @param {string} baseUrl Base URL (must end with '/').
* @returns {string} Fully‑qualified URL including encoded query string.
*/
function buildGetBestMatchesUrl(params, baseUrl) {
const url = new URL('GetBestMatchesJson', baseUrl);
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value ?? '');
});
return url.toString();
}
/**
* Perform an HTTP GET request and parse JSON response.
* @param {string} url URL to request.
* @returns {Promise<any>} Parsed JSON body.
* @throws {Error} On HTTP or parse errors.
*/
async function httpGetJson(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
return res.json();
}
/**
* @typedef {object} GetBestMatchesOptions
* @property {string} businessName Company name to assist suite parsing.
* @property {string} address Primary street address (e.g., '123 Main St').
* @property {string} [address2] Secondary address info (e.g., 'C/O John Smith').
* @property {string} [city] City; required if postalCode omitted.
* @property {string} [state] State code or full name; required if postalCode omitted.
* @property {string} [postalCode] 5‑ or 9‑digit ZIP; required if city/state omitted.
* @property {string} licenseKey Valid AV3 license key.
* @property {'production'|'trial'} [environment='production']
* 'production' for live+backup, 'trial' for trial only.
* @property {number} [timeoutMs=15000]
* Request timeout in milliseconds.
*/
/**
* Retrieve standardized address candidates via AV3 GetBestMatches.
* Automatically falls back from production to backup on error TypeCode '3'.
* @param {GetBestMatchesOptions} opts Operation settings.
* @returns {Promise<object>} Service response JSON.
* @throws {Error} On unrecoverable HTTP or service errors.
*/
export async function getBestMatches(opts) {
const {
businessName,
address,
address2 = '',
city = '',
state = '',
postalCode = '',
licenseKey,
environment = 'production',
timeoutMs = 15000
} = opts;
// Assemble query parameters matching API names
const params = {
BusinessName: businessName,
Address: address,
Address2: address2,
City: city,
State: state,
PostalCode: postalCode,
LicenseKey: licenseKey
};
// Choose primary URL based on environment
const baseUrl = environment === 'trial' ? ENDPOINTS.trial : ENDPOINTS.production;
const primaryUrl = buildGetBestMatchesUrl(params, baseUrl);
// Helper to call with timeout
const call = url => Promise.race([
httpGetJson(url),
new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout')), timeoutMs))
]);
try {
let result = await call(primaryUrl);
// Fallback if production + service-level fatal error
if (environment === 'production' && result.Error?.TypeCode === '3') {
const backupUrl = buildGetBestMatchesUrl(params, ENDPOINTS.backup);
result = await call(backupUrl);
}
return result;
} catch (err) {
// On network or HTTP failure, retry backup for production
if (environment === 'production') {
const backupUrl = buildGetBestMatchesUrl(params, ENDPOINTS.backup);
return call(backupUrl);
}
throw err;
}
}
/**
* @module AV3AddressValidation
* @description
* Client module for the AV3 GetBestMatchesJson operation.
* Validates and standardizes U.S. addresses via Service Objects’ AV3 API,
* with built‑in production/trial endpoints and automatic fallback logic.
*/
import fetch from 'node-fetch';
// Base endpoints for AV3 service
const ENDPOINTS = {
production: 'https://ws.serviceobjects.com/av3/api.svc/',
backup: 'https://wsbackup.serviceobjects.com/av3/api.svc/',
trial: 'https://trial.serviceobjects.com/av3/api.svc/'
};
/**
* Build a fully‑qualified URL for the GetBestMatchesJson endpoint.
* @param {object} params Key/value pairs of query parameters.
* @param {string} baseUrl Base URL (must end with '/').
* @returns {string} Fully‑qualified URL including encoded query string.
*/
function buildGetBestMatchesUrl(params, baseUrl) {
const url = new URL('GetBestMatchesJson', baseUrl);
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value ?? '');
});
return url.toString();
}
/**
* Perform an HTTP GET request and parse JSON response.
* @param {string} url URL to request.
* @returns {Promise<any>} Parsed JSON body.
* @throws {Error} On HTTP or parse errors.
*/
async function httpGetJson(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
return res.json();
}
/**
* @typedef {object} GetBestMatchesOptions
* @property {string} businessName Company name to assist suite parsing.
* @property {string} address Primary street address (e.g., '123 Main St').
* @property {string} [address2] Secondary address info (e.g., 'C/O John Smith').
* @property {string} [city] City; required if postalCode omitted.
* @property {string} [state] State code or full name; required if postalCode omitted.
* @property {string} [postalCode] 5‑ or 9‑digit ZIP; required if city/state omitted.
* @property {string} licenseKey Valid AV3 license key.
* @property {'production'|'trial'} [environment='production']
* 'production' for live+backup, 'trial' for trial only.
* @property {number} [timeoutMs=15000]
* Request timeout in milliseconds.
*/
/**
* Retrieve standardized address candidates via AV3 GetBestMatches.
* Automatically falls back from production to backup on error TypeCode '3'.
* @param {GetBestMatchesOptions} opts Operation settings.
* @returns {Promise<object>} Service response JSON.
* @throws {Error} On unrecoverable HTTP or service errors.
*/
export async function getBestMatches(opts) {
const {
businessName,
address,
address2 = '',
city = '',
state = '',
postalCode = '',
licenseKey,
environment = 'production',
timeoutMs = 15000
} = opts;
// Assemble query parameters matching API names
const params = {
BusinessName: businessName,
Address: address,
Address2: address2,
City: city,
State: state,
PostalCode: postalCode,
LicenseKey: licenseKey
};
// Choose primary URL based on environment
const baseUrl = environment === 'trial' ? ENDPOINTS.trial : ENDPOINTS.production;
const primaryUrl = buildGetBestMatchesUrl(params, baseUrl);
// Helper to call with timeout
const call = url => Promise.race([
httpGetJson(url),
new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout')), timeoutMs))
]);
try {
let result = await call(primaryUrl);
// Fallback if production + service-level fatal error
if (environment === 'production' && result.Error?.TypeCode === '3') {
const backupUrl = buildGetBestMatchesUrl(params, ENDPOINTS.backup);
result = await call(backupUrl);
}
return result;
} catch (err) {
// On network or HTTP failure, retry backup for production
if (environment === 'production') {
const backupUrl = buildGetBestMatchesUrl(params, ENDPOINTS.backup);
return call(backupUrl);
}
throw err;
}
}
/** * @module AV3AddressValidation * @description * Client module for the AV3 GetBestMatchesJson operation. * Validates and standardizes U.S. addresses via Service Objects’ AV3 API, * with built‑in production/trial endpoints and automatic fallback logic. */ import fetch from 'node-fetch'; // Base endpoints for AV3 service const ENDPOINTS = { production: 'https://ws.serviceobjects.com/av3/api.svc/', backup: 'https://wsbackup.serviceobjects.com/av3/api.svc/', trial: 'https://trial.serviceobjects.com/av3/api.svc/' }; /** * Build a fully‑qualified URL for the GetBestMatchesJson endpoint. * @param {object} params Key/value pairs of query parameters. * @param {string} baseUrl Base URL (must end with '/'). * @returns {string} Fully‑qualified URL including encoded query string. */ function buildGetBestMatchesUrl(params, baseUrl) { const url = new URL('GetBestMatchesJson', baseUrl); Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, value ?? ''); }); return url.toString(); } /** * Perform an HTTP GET request and parse JSON response. * @param {string} url URL to request. * @returns {Promise<any>} Parsed JSON body. * @throws {Error} On HTTP or parse errors. */ async function httpGetJson(url) { const res = await fetch(url); if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`); return res.json(); } /** * @typedef {object} GetBestMatchesOptions * @property {string} businessName Company name to assist suite parsing. * @property {string} address Primary street address (e.g., '123 Main St'). * @property {string} [address2] Secondary address info (e.g., 'C/O John Smith'). * @property {string} [city] City; required if postalCode omitted. * @property {string} [state] State code or full name; required if postalCode omitted. * @property {string} [postalCode] 5‑ or 9‑digit ZIP; required if city/state omitted. * @property {string} licenseKey Valid AV3 license key. * @property {'production'|'trial'} [environment='production'] * 'production' for live+backup, 'trial' for trial only. * @property {number} [timeoutMs=15000] * Request timeout in milliseconds. */ /** * Retrieve standardized address candidates via AV3 GetBestMatches. * Automatically falls back from production to backup on error TypeCode '3'. * @param {GetBestMatchesOptions} opts Operation settings. * @returns {Promise<object>} Service response JSON. * @throws {Error} On unrecoverable HTTP or service errors. */ export async function getBestMatches(opts) { const { businessName, address, address2 = '', city = '', state = '', postalCode = '', licenseKey, environment = 'production', timeoutMs = 15000 } = opts; // Assemble query parameters matching API names const params = { BusinessName: businessName, Address: address, Address2: address2, City: city, State: state, PostalCode: postalCode, LicenseKey: licenseKey }; // Choose primary URL based on environment const baseUrl = environment === 'trial' ? ENDPOINTS.trial : ENDPOINTS.production; const primaryUrl = buildGetBestMatchesUrl(params, baseUrl); // Helper to call with timeout const call = url => Promise.race([ httpGetJson(url), new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout')), timeoutMs)) ]); try { let result = await call(primaryUrl); // Fallback if production + service-level fatal error if (environment === 'production' && result.Error?.TypeCode === '3') { const backupUrl = buildGetBestMatchesUrl(params, ENDPOINTS.backup); result = await call(backupUrl); } return result; } catch (err) { // On network or HTTP failure, retry backup for production if (environment === 'production') { const backupUrl = buildGetBestMatchesUrl(params, ENDPOINTS.backup); return call(backupUrl); } throw err; } }