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;