- C#
- Python
- NodeJS
Phone Append 2 C# Rest Code Snippet
using System.Web;
namespace phone_append_2_dot_net.REST
{
/// <summary>
/// Provides functionality to call the ServiceObjects PhoneAppend (PA2) REST API's PhoneAppend endpoint,
/// retrieving phone number information for a contact based on provided inputs with fallback to a backup endpoint for reliability in live mode.
/// </summary>
public static class PhoneAppendClient
{
// Base URL constants: production, backup, and trial
private const string LiveBaseUrl = "https://sws.serviceobjects.com/pa2/api.svc/json/";
private const string BackupBaseUrl = "https://swsbackup.serviceobjects.com/pa2/api.svc/json/";
private const string TrialBaseUrl = "https://trial.serviceobjects.com/pa2/api.svc/json/";
/// <summary>
/// Synchronously calls the PhoneAppend REST endpoint to retrieve phone number information,
/// attempting the primary endpoint first and falling back to the backup if the response is invalid
/// (Error.TypeCode == "3") in live mode.
/// </summary>
/// <param name="input">The input parameters including full name, first name, last name, address, city, state, postal code, and license key.</param>
/// <returns>Deserialized <see cref="PA2Response"/> containing phone number data or an error.</returns>
public static PA2Response Invoke(PhoneAppendInput input)
{
// Use query string parameters so missing/optional fields don't break the URL
string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
PA2Response response = Helper.HttpGet<PA2Response>(url, input.TimeoutSeconds);
// Fallback on error in live mode
if (input.IsLive && !ValidResponse(response))
{
string fallbackUrl = BuildUrl(input, BackupBaseUrl);
PA2Response fallbackResponse = Helper.HttpGet<PA2Response>(fallbackUrl, input.TimeoutSeconds);
return fallbackResponse;
}
return response;
}
/// <summary>
/// Asynchronously calls the PhoneAppend REST endpoint to retrieve phone number information,
/// attempting the primary endpoint first and falling back to the backup if the response is invalid
/// (Error.TypeCode == "3") in live mode.
/// </summary>
/// <param name="input">The input parameters including full name, first name, last name, address, city, state, postal code, and license key.</param>
/// <returns>Deserialized <see cref="PA2Response"/> containing phone number data or an error.</returns>
public static async Task<PA2Response> InvokeAsync(PhoneAppendInput input)
{
// Use query string parameters so missing/optional fields don't break the URL
string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
PA2Response response = await Helper.HttpGetAsync<PA2Response>(url, input.TimeoutSeconds).ConfigureAwait(false);
// Fallback on error in live mode
if (input.IsLive && !ValidResponse(response))
{
string fallbackUrl = BuildUrl(input, BackupBaseUrl);
PA2Response fallbackResponse = await Helper.HttpGetAsync<PA2Response>(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
return fallbackResponse;
}
return response;
}
// Build the full request URL, including URL-encoded query string
private static string BuildUrl(PhoneAppendInput input, string baseUrl)
{
// Construct query string with URL-encoded parameters
string qs = $"PhoneAppendJson?" +
$"FullName={Helper.UrlEncode(input.FullName)}" +
$"&FirstName={Helper.UrlEncode(input.FirstName)}" +
$"&LastName={Helper.UrlEncode(input.LastName)}" +
$"&Address={Helper.UrlEncode(input.Address)}" +
$"&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 ValidResponse(PA2Response response)
{
return (response?.Error == null || response.Error.TypeCode != "3");
}
/// <summary>
/// Input parameters for the PhoneAppend API call. Represents a contact to retrieve phone number information.
/// </summary>
/// <param name="FullName">The full name of the contact. Optional if FirstName and LastName are provided.</param>
/// <param name="FirstName">The first name of the contact. Optional if FullName is provided.</param>
/// <param name="LastName">The last name of the contact. Optional if FullName is provided.</param>
/// <param name="Address">Address line of the contact. Optional.</param>
/// <param name="City">The city of the contact. Optional if postal code is provided.</param>
/// <param name="State">The state of the contact. Optional if postal code is provided.</param>
/// <param name="PostalCode">The postal code of the contact. Optional if city and state are provided.</param>
/// <param name="LicenseKey">The license key to authenticate the API request.</param>
/// <param name="IsLive">Indicates whether to use the live service (true) or trial service (false).</param>
/// <param name="TimeoutSeconds">Timeout duration for the API call, in seconds.</param>
public record PhoneAppendInput(
string FullName = "",
string FirstName = "",
string LastName = "",
string Address = "",
string City = "",
string State = "",
string PostalCode = "",
string LicenseKey = "",
bool IsLive = true,
int TimeoutSeconds = 15
);
}
}
using System.Runtime.Serialization;
[DataContract]
public class PA2Response
{
public PhoneInfo PhoneInfo { get; set; }
public Error Error { get; set; }
public override string ToString()
{
return $"PA2 PhoneInfo: {PhoneInfo} \nError: {Error}\n";
}
}
public class PhoneInfo
{
public string Phone { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string IsResidential { get; set; }
public string Certainty { get; set; }
public string LineType { get; set; }
public override string ToString()
{
return $"Phone: {Phone}\n" +
$"Name: {Name}\n" +
$"Address: {Address}\n" +
$"City: {City}\n" +
$"State: {State}\n" +
$"Postal Code: {PostalCode}\n" +
$"Is Residential: {IsResidential}\n" +
$"Certainty: {Certainty}\n" +
$"Line Type: {LineType}\n";
}
}
public class Error
{
public string Type { get; set; }
public string TypeCode { get; set; }
public string Desc { get; set; }
public string DescCode { get; set; }
public override string ToString()
{
return $"Type: {Type} " +
$"TypeCode: {TypeCode} " +
$"Desc: {Desc} " +
$"DescCode: {DescCode} ";
}
}
using System.Text.Json;
using System.Web;
namespace phone_append_2_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);
}
}
Phone Append 2 Python Code Snippet
from pa2_response import PA2Response, PhoneInfo, Error
import requests
from typing import Optional
# Endpoint URLs for ServiceObjects PhoneAppend API
primary_url = "https://sws.serviceobjects.com/pa2/api.svc/json/PhoneAppendJson?"
backup_url = "https://swsbackup.serviceobjects.com/pa2/api.svc/json/PhoneAppendJson?"
trial_url = "https://trial.serviceobjects.com/pa2/api.svc/json/PhoneAppendJson?"
def get_phone_append(
full_name: Optional[str] = None,
first_name: Optional[str] = None,
last_name: Optional[str] = None,
address: Optional[str] = None,
city: Optional[str] = None,
state: Optional[str] = None,
postal_code: Optional[str] = None,
license_key: Optional[str] = None,
is_live: bool = True
) -> PA2Response:
"""
Call ServiceObjects PhoneAppend API to retrieve a phone number for a given residential contact.
Parameters:
full_name: The full name of the contact. Optional if first_name and last_name are provided.
first_name: The first name of the contact. Optional if full_name is provided.
last_name: The last name of the contact. Optional if full_name is provided.
address: Address line of the contact. Optional.
city: The city of the contact. Optional.
state: The state of the contact. Optional.
postal_code: The postal code of the contact. Optional.
license_key: Your ServiceObjects license key.
is_live: Use live or trial servers.
Returns:
PA2Response: Parsed JSON response with phone information or error details.
Raises:
RuntimeError: If the API returns an error payload.
requests.RequestException: On network/HTTP failures (trial mode).
"""
params = {
"FullName": full_name,
"FirstName": first_name,
"LastName": last_name,
"Address": address,
"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
try:
# Attempt primary (or trial) endpoint
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 = data.get('Error')
if not (error is None or error.get('TypeCode') != "3"):
if is_live:
# Try backup URL
response = requests.get(backup_url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
# If still error, propagate exception
if 'Error' in data:
raise RuntimeError(f"PhoneAppend service error: {data['Error']}")
else:
# Trial mode error is terminal
raise RuntimeError(f"PhoneAppend trial error: {data['Error']}")
# Convert JSON response to PA2Response for structured access
error = Error(**data.get("Error", {})) if data.get("Error") else None
return PA2Response(
PhoneInfo=PhoneInfo(
Phone=data.get("PhoneInfo", {}).get("Phone"),
Name=data.get("PhoneInfo", {}).get("Name"),
Address=data.get("PhoneInfo", {}).get("Address"),
City=data.get("PhoneInfo", {}).get("City"),
State=data.get("PhoneInfo", {}).get("State"),
PostalCode=data.get("PhoneInfo", {}).get("PostalCode"),
IsResidential=data.get("PhoneInfo", {}).get("IsResidential"),
Certainty=data.get("PhoneInfo", {}).get("Certainty"),
LineType=data.get("PhoneInfo", {}).get("LineType"),
Debug=data.get("PhoneInfo", {}).get("Debug")
) if data.get("PhoneInfo") else None,
Error=error
)
except requests.RequestException as req_exc:
# Network or HTTP-level error occurred
if is_live:
try:
# Fallback to backup URL
response = requests.get(backup_url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if "Error" in data:
raise RuntimeError(f"PhoneAppend backup error: {data['Error']}") from req_exc
error = Error(**data.get("Error", {})) if data.get("Error") else None
return PA2Response(
PhoneInfo=PhoneInfo(
Phone=data.get("PhoneInfo", {}).get("Phone"),
Name=data.get("PhoneInfo", {}).get("Name"),
Address=data.get("PhoneInfo", {}).get("Address"),
City=data.get("PhoneInfo", {}).get("City"),
State=data.get("PhoneInfo", {}).get("State"),
PostalCode=data.get("PhoneInfo", {}).get("PostalCode"),
IsResidential=data.get("PhoneInfo", {}).get("IsResidential"),
Certainty=data.get("PhoneInfo", {}).get("Certainty"),
LineType=data.get("PhoneInfo", {}).get("LineType"),
Debug=data.get("PhoneInfo", {}).get("Debug")
) if data.get("PhoneInfo") else None,
Error=error
)
except Exception as backup_exc:
raise RuntimeError("PhoneAppend service unreachable on both endpoints") from backup_exc
else:
raise RuntimeError(f"PhoneAppend trial error: {str(req_exc)}") from req_exc
from dataclasses import dataclass
from typing import Optional, List
# Input parameters for the PhoneAppend API call.
@dataclass
class PhoneAppendInput:
FullName: Optional[str] = None
FirstName: Optional[str] = None
LastName: Optional[str] = None
Address: Optional[str] = None
City: Optional[str] = None
State: Optional[str] = None
PostalCode: Optional[str] = None
IsBusiness: Optional[str] = None
LicenseKey: Optional[str] = None
def __str__(self) -> str:
return (f"PhoneAppendInput: FullName={self.FullName}, FirstName={self.FirstName}, "
f"LastName={self.LastName}, Address={self.Address}, City={self.City}, "
f"State={self.State}, PostalCode={self.PostalCode}, "
f"IsBusiness={self.IsBusiness}, LicenseKey={self.LicenseKey}")
# Error object for API responses.
@dataclass
class Error:
Type: Optional[str] = None
TypeCode: Optional[str] = None
Desc: Optional[str] = None
DescCode: Optional[str] = None
def __str__(self) -> str:
return (f"Error: Type={self.Type}, TypeCode={self.TypeCode}, "
f"Desc={self.Desc}, DescCode={self.DescCode}")
# Phone information for API responses.
@dataclass
class PhoneInfo:
Phone: Optional[str] = None
Name: Optional[str] = None
Address: Optional[str] = None
City: Optional[str] = None
State: Optional[str] = None
PostalCode: Optional[str] = None
IsResidential: Optional[str] = None
Certainty: Optional[str] = None
LineType: Optional[str] = None
Debug: Optional[str] = None
def __str__(self) -> str:
return (f"PhoneInfo: Phone={self.Phone}, Name={self.Name}, "
f"Address={self.Address}, City={self.City}, State={self.State}, "
f"PostalCode={self.PostalCode}, IsResidential={self.IsResidential}, "
f"Certainty={self.Certainty}, LineType={self.LineType}, Debug={self.Debug}")
# Response from PhoneAppend API, containing phone information and potential error.
@dataclass
class PA2Response:
PhoneInfo: Optional['PhoneInfo'] = None
Error: Optional['Error'] = None
def __str__(self) -> str:
phone_info = str(self.PhoneInfo) if self.PhoneInfo else 'None'
error = str(self.Error) if self.Error else 'None'
return f"PA2Response: PhoneInfo={phone_info}, Error={error}"
Phone Append 2 NodeJS Code Snippet
import axios from 'axios';
import querystring from 'querystring';
import { PA2Response } from './pa2_response.js';
/**
* @constant
* @type {string}
* @description The base URL for the live ServiceObjects PhoneAppend2 (PA2) API service.
*/
const LiveBaseUrl = 'https://sws.serviceobjects.com/pa2/api.svc/json/';
/**
* @constant
* @type {string}
* @description The base URL for the backup ServiceObjects PhoneAppend2 (PA2) API service.
*/
const BackupBaseUrl = 'https://swsbackup.serviceobjects.com/pa2/api.svc/json/';
/**
* @constant
* @type {string}
* @description The base URL for the trial ServiceObjects PhoneAppend2 (PA2) API service.
*/
const TrialBaseUrl = 'https://trial.serviceobjects.com/pa2/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 PhoneAppendJson 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}PhoneAppendJson?${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<PA2Response>">A promise that resolves to a PA2Response 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 PA2Response(response.data);
} catch (error) {
throw new Error(`HTTP request failed: ${error.message}`);
}
};
/**
* <summary>
* Provides functionality to call the ServiceObjects PhoneAppend2 (PA2) API's PhoneAppendJson endpoint,
* retrieving phone number information for a contact based on provided inputs with fallback to a backup endpoint for reliability in live mode.
* </summary>
*/
const PhoneAppendClient = {
/**
* <summary>
* Asynchronously invokes the PhoneAppendJson 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} FullName - The full name of the contact. Optional if FirstName and LastName are provided.
* @param {string} FirstName - The first name of the contact. Optional if FullName is provided.
* @param {string} LastName - The last name of the contact. Optional if FullName is provided.
* @param {string} Address - Address line of the contact. Optional.
* @param {string} City - The city of the contact. Optional if postal code is provided.
* @param {string} State - The state of the contact. Optional if postal code is provided.
* @param {string} PostalCode - The postal code of the contact. Optional if city and state are provided.
* @param {string} LicenseKey - Your license key to use the service.
* @param {boolean} isLive - Value to determine whether to use the live or trial servers.
* @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service.
* @returns {Promise<PA2Response>} - A promise that resolves to a PA2Response object.
*/
async invokeAsync(FullName, FirstName, LastName, Address, City, State, PostalCode, LicenseKey, isLive = true, timeoutSeconds = 15) {
const params = {
FullName,
FirstName,
LastName,
Address,
City,
State,
PostalCode,
LicenseKey
};
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 PhoneAppendJson API endpoint by wrapping the async call
* and awaiting its result immediately.
* </summary>
* @param {string} FullName - The full name of the contact. Optional if FirstName and LastName are provided.
* @param {string} FirstName - The first name of the contact. Optional if FullName is provided.
* @param {string} LastName - The last name of the contact. Optional if FullName is provided.
* @param {string} Address - Address line of the contact. Optional.
* @param {string} City - The city of the contact. Optional if postal code is provided.
* @param {string} State - The state of the contact. Optional if postal code is provided.
* @param {string} PostalCode - The postal code of the contact. Optional if city and state are provided.
* @param {string} LicenseKey - Your license key to use the service.
* @param {boolean} isLive - Value to determine whether to use the live or trial servers.
* @param {number} timeoutSeconds - Timeout, in seconds, for the call to the service.
* @returns {PA2Response} - A PA2Response object with phone number details or an error.
*/
invoke(FullName, FirstName, LastName, Address, City, State, PostalCode, LicenseKey, isLive = true, timeoutSeconds = 15) {
return (async () => await this.invokeAsync(
FullName, FirstName, LastName, Address, City, State, PostalCode, LicenseKey, isLive, timeoutSeconds
))();
}
};
export { PhoneAppendClient, PA2Response };
/**
* Response from PhoneAppend API, containing phone information and potential error.
*/
export class PA2Response {
constructor(data = {}) {
this.PhoneInfo = data.PhoneInfo ? new PhoneInfo(data.PhoneInfo) : null;
this.Error = data.Error ? new Error(data.Error) : null;
}
toString() {
return `PA2Response: PhoneInfo = ${this.PhoneInfo ? this.PhoneInfo.toString() : 'null'}, Error = ${this.Error ? this.Error.toString() : 'null'}`;
}
}
/**
* Phone information for API responses.
*/
export class PhoneInfo {
constructor(data = {}) {
this.Phone = data.Phone;
this.Name = data.Name;
this.Address = data.Address;
this.City = data.City;
this.State = data.State;
this.PostalCode = data.PostalCode;
this.IsResidential = data.IsResidential;
this.Certainty = data.Certainty;
this.LineType = data.LineType;
}
toString() {
return `PhoneInfo: Phone = ${this.Phone}, Name = ${this.Name}, Address = ${this.Address}, City = ${this.City}, State = ${this.State}, PostalCode = ${this.PostalCode}, IsResidential = ${this.IsResidential}, Certainty = ${this.Certainty}, LineType = ${this.LineType}`;
}
}
/**
* Error object for API responses.
*/
export class Error {
constructor(data = {}) {
this.Type = data.Type;
this.TypeCode = data.TypeCode;
this.Desc = data.Desc;
this.DescCode = data.DescCode;
}
toString() {
return `Error: Type = ${this.Type}, TypeCode = ${this.TypeCode}, Desc = ${this.Desc}, DescCode = ${this.DescCode}`;
}
}
export default PA2Response;