Phone Validation International C# Rest Code Snippet

using System.Text.Json;
using System.Web;

namespace phone_validation_international_dot_net.REST
{
    /// <summary>
    /// Provides functionality to call the ServiceObjects Phone Validation International (PVI) REST API's GetPhoneDetails endpoint,
    /// retrieving comprehensive carrier and exchange information for a given phone number with fallback to a backup endpoint for reliability in live mode.
    /// </summary>
    public class GetPhoneDetailsClient
    {
        // Base URL constants: production, backup, and trial
        private const string LiveBaseUrl = "https://sws.serviceobjects.com/PVI/";
        private const string BackupBaseUrl = "https://swsbackup.serviceobjects.com/PVI/";
        private const string TrialBaseUrl = "https://trial.serviceobjects.com/PVI/";

        /// <summary>
        /// Synchronously calls the GetPhoneDetails REST endpoint to retrieve phone details,
        /// attempting the primary endpoint first and falling back to the backup if the response is invalid
        /// (Error.Status == "500") in live mode.
        /// </summary>
        /// <param name="input">The input parameters including phone number, country, options, and authentication ID.</param>
        /// <returns>Deserialized <see cref="PVIResponse"/> containing phone details or an error.</returns>
        public static PVIResponseWrapper Invoke(GetPhoneDetailsInput input)
        {
            // Use query string parameters so missing/optional fields don't break the URL
            string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            string jsonResponse = Helper.HttpGet(url, input.TimeoutSeconds);
            bool IsValid = true;
            PVIResponseWrapper response = new();
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };
            if (jsonResponse.Replace(" ", "").Contains("\"status\":4"))
            {
                response.ProblemDetails = JsonSerializer.Deserialize<ProblemDetails>(jsonResponse, options);
            }
            else if (jsonResponse.Replace(" ", "").Contains("\"status\":5"))
            {
                IsValid = false;
            }
            else
            {
                response.PhoneDetails = JsonSerializer.Deserialize<PhoneDetails>(jsonResponse, options);
            }


            // Fallback on error in live mode 
            if (input.IsLive && !IsValid)
            {
                string fallbackUrl = BuildUrl(input, BackupBaseUrl);
                string fallbackJsonResponse = Helper.HttpGet(fallbackUrl, input.TimeoutSeconds);
                response = new();
                if (fallbackJsonResponse.Replace(" ", "").Contains("\"status\":4") || fallbackJsonResponse.Replace(" ", "").Contains("\"status\":5"))
                {
                    response.ProblemDetails = JsonSerializer.Deserialize<ProblemDetails>(jsonResponse, options);
                }
                else
                {
                    response.PhoneDetails = JsonSerializer.Deserialize<PhoneDetails>(jsonResponse, options);
                }
                return response;
            }
            return response;
        }

        /// <summary>
        /// Asynchronously calls the GetPhoneDetails REST endpoint to retrieve phone details,
        /// attempting the primary endpoint first and falling back to the backup if the response is invalid
        /// (Error.Status == "500") in live mode.
        /// </summary>
        /// <param name="input">The input parameters including phone number, country, options, and authentication ID.</param>
        /// <returns>Deserialized <see cref="PVIResponse"/> containing phone details or an error.</returns>
        public static async Task<PVIResponseWrapper> InvokeAsync(GetPhoneDetailsInput input)
        {
            // Use query string parameters so missing/optional fields don't break the URL
            string url = BuildUrl(input, input.IsLive ? LiveBaseUrl : TrialBaseUrl);
            string jsonResponse = await Helper.HttpGetAsync(url, input.TimeoutSeconds).ConfigureAwait(false);
            bool IsValid = true;
            PVIResponseWrapper response = new();
            var options = new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };
            if (jsonResponse.Replace(" ", "").Contains("\"status\":4"))
            {
                response.ProblemDetails = JsonSerializer.Deserialize<ProblemDetails>(jsonResponse, options);
            }
            else if (jsonResponse.Replace(" ", "").Contains("\"status\":5"))
            {
                IsValid = false;
            }
            else
            {
                response.PhoneDetails = JsonSerializer.Deserialize<PhoneDetails>(jsonResponse, options);
            }
            if (input.IsLive && !IsValid)
            {
                string fallbackUrl = BuildUrl(input, BackupBaseUrl);
                string fallbackJsonResponse = await Helper.HttpGetAsync(fallbackUrl, input.TimeoutSeconds).ConfigureAwait(false);
                response = new();
                if (fallbackJsonResponse.Replace(" ", "").Contains("\"status\":4") || fallbackJsonResponse.Replace(" ", "").Contains("\"status\":5"))
                {
                    response.ProblemDetails = JsonSerializer.Deserialize<ProblemDetails>(jsonResponse, options);
                }
                else
                {
                    response.PhoneDetails = JsonSerializer.Deserialize<PhoneDetails>(jsonResponse, options);
                }
                return response;
            }

            return response;
        }

        // Build the full request URL, including URL-encoded query string
        public static string BuildUrl(GetPhoneDetailsInput input, string baseUrl)
        {
            // Construct query string with URL-encoded parameters
            string qs = $"GetPhoneDetails?" +
                        $"Phone={Helper.UrlEncode(input.Phone)}" +
                        $"&Country={Helper.UrlEncode(input.Country)}" +
                        $"&Options={Helper.UrlEncode(input.Options)}" +
                        $"&AuthID={Helper.UrlEncode(input.AuthID)}";
            return baseUrl + qs;
        }

        /// <summary>
        /// Input parameters for the GetPhoneDetails API call. Represents a phone number to retrieve details for.
        /// </summary>
        /// <param name="Phone">The phone number to validate (variable digit length).</param>
        /// <param name="Country">The location of the phone number (1-3 digit Country Calling Code, ISO2, ISO3, or Country Name).</param>
        /// <param name="Options">Comma-separated list of optional parameters. Optional.</param>
        /// <param name="AuthID">The authentication ID provided by Service Objects.</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 GetPhoneDetailsInput(
            string Phone = "",
            string Country = "",
            string Options = "",
            string AuthID = "",
            bool IsLive = true,
            int TimeoutSeconds = 15
        );
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace phone_validation_international_dot_net.REST
{
    public class PVIResponseWrapper
    {
        public PhoneDetails? PhoneDetails;
        public ProblemDetails? ProblemDetails;
        public PVIResponseWrapper() { }
    }
}


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

namespace phone_validation_international_dot_net.REST
{
    public static class Helper
    {
        private static readonly HttpClient _client = new HttpClient();

        public static async Task<string> HttpGetAsync(string url, int timeoutSeconds)
        {

            _client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
            HttpResponseMessage response = await _client.GetAsync(url).ConfigureAwait(false);
            string jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
            return jsonResponse;
        }

        public static string HttpGet(string url, int timeoutSeconds)
        {
            _client.Timeout = TimeSpan.FromSeconds(timeoutSeconds);
            HttpResponseMessage response = _client.GetAsync(url).GetAwaiter().GetResult();
            string jsonResponse = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
            return jsonResponse;
        }

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

Phone Validation International Python Rest Code Snippet

from pvi_response import PVIResponse, TimeZone, ProblemDetails, ServiceProvider
import requests
import json

# Endpoint URLs for ServiceObjects Phone Validation International (PVI) API
primary_url = "https://sws.serviceobjects.com/PVI/GetPhoneDetails?"
backup_url = "https://swsbackup.serviceobjects.com/PVI/GetPhoneDetails?"
trial_url = "https://trial.serviceobjects.com/PVI/GetPhoneDetails?"

def get_phone_details(
    phone: str,
    country: str = "",
    options: str = "",
    auth_id: str = "",
    is_live: bool = True,
    timeout_seconds: int = 15
) -> PVIResponse:
    """
    Calls the ServiceObjects Phone Validation International (PVI) API's GetPhoneDetails endpoint
    to retrieve phone details (e.g., carrier, line type, time zones) for a given phone number.
    Validates input parameters and returns a ProblemDetails response if invalid. Uses a backup endpoint for reliability in live mode.

    Args:
        phone (str): The phone number to validate (e.g., "+12025550123").
        country (str, optional): The country code or name (e.g., "US"). Optional.
        options (str, optional): Optional. Reserved for future use.
        auth_id (str): Required. Authentication ID provided by Service Objects.
        is_live (bool, optional): Option to use live service (true) or trial service (false). Defaults to True.
        timeout_seconds (int, optional): Timeout in seconds for the HTTP request. Defaults to 15.

    Returns:
        PVIResponse: Parsed JSON response with phone details or a ProblemDetails if validation fails or the API call fails.

    Raises:
        RuntimeError: If the API returns an error payload with status "500".
        requests.RequestException: On network/HTTP failures (trial mode).
    """
    params = {
        "Phone": phone,
        "Country": country,
        "Options": options,
        "AuthID": auth_id
    }

    url = primary_url if is_live else trial_url

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

        # If API returned an error in JSON payload, trigger fallback
        problem_details = data.get('ProblemDetails')
        if problem_details:
            if is_live:
                # Try backup URL
                response = requests.get(backup_url, params=params, timeout=timeout_seconds)
                response.raise_for_status()
                data = response.json()
                problem_details = data.get('ProblemDetails')
                if problem_details:
                    return PVIResponse(ProblemDetails=ProblemDetails(**problem_details))
            else:
                # Trial mode error is terminal
                return PVIResponse(ProblemDetails=ProblemDetails(**problem_details))

        # Convert JSON response to PVIResponse for structured access
        #problem_details = ProblemDetails(**data.get("ProblemDetails", {})) if data.get("ProblemDetails") else None

        return PVIResponse(
            Score=data.get("score"),
            PhoneIn=data.get("phoneIn"),
            CountryCode=data.get("countryCode"),
            FormatNational=data.get("formatNational"),
            FormatInternational=data.get("formatInternational"),
            FormatE164=data.get("formatE164"),
            Extension=data.get("extension"),
            Locality=data.get("locality"),
            AdminArea=data.get("adminArea"),
            AdminAreaAbbr=data.get("adminAreaAbbr"),
            Country=data.get("country"),
            CountryISO2=data.get("countryISO2"),
            CountryISO3=data.get("countryISO3"),
            Latitude=data.get("latitude"),
            Longitude=data.get("longitude"),
            LatLongMatchLevel=data.get("latLongMatchLevel"),
            TimeZones=[
                TimeZone(
                    ZoneName=zone.get("zoneName"),
                    ZoneAbbr=zone.get("zoneAbbr"),
                    CountryISO3=zone.get("countryISO3"),
                    UtcOffset=zone.get("utcOffset")
                )
                for zone in data.get("timeZones", [])
            ] if "timeZones" in data else [],
            LineType=data.get("lineType"),
            SmsAddress=data.get("smsAddress"),
            ValidPhone=data.get("validPhone"),
            ValidPhoneLength=data.get("validPhoneLength"),
            Notes=data.get("notes", []),
            Warnings=data.get("warnings", []),
            CurrentProvider=ServiceProvider(
                ProviderID=data.get("currentProvider", {}).get("providerID"),
                ProviderName=data.get("currentProvider", {}).get("providerName"),
                CountryISO3=data.get("currentProvider", {}).get("countryISO3")
            ) if data.get("currentProvider") else None,
            PreviousProvider=ServiceProvider(
                ProviderID=data.get("previousProvider", {}).get("providerID"),
                ProviderName=data.get("previousProvider", {}).get("providerName"),
                CountryISO3=data.get("previousProvider", {}).get("countryISO3")
            ) if data.get("previousProvider") else None,
            OriginalProvider=ServiceProvider(
                ProviderID=data.get("originalProvider", {}).get("providerID"),
                ProviderName=data.get("originalProvider", {}).get("providerName"),
                CountryISO3=data.get("originalProvider", {}).get("countryISO3")
            ) if data.get("originalProvider") else None,
            LastPortedDate=data.get("lastPortedDate")
        )

    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=timeout_seconds)
                response.raise_for_status()
                data = response.json()
                problem_details = data.get('ProblemDetails')
                if problem_details:
                    return PVIResponse(ProblemDetails=ProblemDetails(**problem_details))

                #problem_details = ProblemDetails(**data.get("ProblemDetails", {})) if data.get("ProblemDetails") else None

                return PVIResponse(
                    Score=data.get("score"),
                    PhoneIn=data.get("phoneIn"),
                    CountryCode=data.get("countryCode"),
                    FormatNational=data.get("formatNational"),
                    FormatInternational=data.get("formatInternational"),
                    FormatE164=data.get("formatE164"),
                    Extension=data.get("extension"),
                    Locality=data.get("locality"),
                    AdminArea=data.get("adminArea"),
                    AdminAreaAbbr=data.get("adminAreaAbbr"),
                    Country=data.get("country"),
                    CountryISO2=data.get("countryISO2"),
                    CountryISO3=data.get("countryISO3"),
                    Latitude=data.get("latitude"),
                    Longitude=data.get("longitude"),
                    LatLongMatchLevel=data.get("latLongMatchLevel"),
                    TimeZones=[
                        TimeZone(
                            ZoneName=zone.get("zoneName"),
                            ZoneAbbr=zone.get("zoneAbbr"),
                            CountryISO3=zone.get("countryISO3"),
                            UtcOffset=zone.get("utcOffset")
                        )
                        for zone in data.get("timeZones", [])
                    ] if "timeZones" in data else [],
                    LineType=data.get("lineType"),
                    SmsAddress=data.get("smsAddress"),
                    ValidPhone=data.get("validPhone"),
                    ValidPhoneLength=data.get("validPhoneLength"),
                    Notes=data.get("notes", []),
                    Warnings=data.get("warnings", []),
                    CurrentProvider=ServiceProvider(
                        ProviderID=data.get("currentProvider", {}).get("providerID"),
                        ProviderName=data.get("currentProvider", {}).get("providerName"),
                        CountryISO3=data.get("currentProvider", {}).get("countryISO3")
                    ) if data.get("currentProvider") else None,
                    PreviousProvider=ServiceProvider(
                        ProviderID=data.get("previousProvider", {}).get("providerID"),
                        ProviderName=data.get("previousProvider", {}).get("providerName"),
                        CountryISO3=data.get("previousProvider", {}).get("countryISO3")
                    ) if data.get("previousProvider") else None,
                    OriginalProvider=ServiceProvider(
                        ProviderID=data.get("originalProvider", {}).get("providerID"),
                        ProviderName=data.get("originalProvider", {}).get("providerName"),
                        CountryISO3=data.get("originalProvider", {}).get("countryISO3")
                    ) if data.get("originalProvider") else None,
                    LastPortedDate=data.get("lastPortedDate")
                )
            except Exception as backup_exc:
                data = json.loads(backup_exc.response.content)
                problem_details = ProblemDetails(
                    Type=data["type"],
                    Title=data["title"],
                    Status=data["status"]
                )
                return PVIResponse(
                    ProblemDetails=problem_details
                )
        else:
            data = json.loads(req_exc.response.content)
            problem_details = ProblemDetails(
                Type=data["type"],
                Title=data["title"],
                Status=data["status"]
            )
            return PVIResponse(
                ProblemDetails=problem_details
            )


"""
Response classes for Phone Validation International (PVI) API.
"""

from dataclasses import dataclass
from typing import Optional, List

@dataclass
class TimeZone:
    ZoneName: Optional[str] = None
    ZoneAbbr: Optional[str] = None
    CountryISO3: Optional[str] = None
    UtcOffset: Optional[str] = None

    def __str__(self) -> str:

        return (f"TimeZone: ZoneName={self.ZoneName}, ZoneAbbr={self.ZoneAbbr}, "
                f"CountryISO3={self.CountryISO3}, UtcOffset={self.UtcOffset}")

@dataclass
class ServiceProvider:
    ProviderID: Optional[str] = None
    ProviderName: Optional[str] = None
    CountryISO3: Optional[str] = None

    def __str__(self) -> str:
       
        return (f"ServiceProvider: ProviderID={self.ProviderID}, ProviderName={self.ProviderName}, "
                f"CountryISO3={self.CountryISO3}")

@dataclass
class ProblemDetails:
    Type: Optional[str] = None
    Title: Optional[str] = None
    Status: Optional[str] = None
    Detail: Optional[str] = None

    def __str__(self) -> str:
       
        return (f"ProblemDetails: Type={self.Type}, Title={self.Title}, "
                f"Status={self.Status}, Detail={self.Detail}")

@dataclass
class PVIResponse:
    Score: Optional[str] = None
    PhoneIn: Optional[str] = None
    CountryCode: Optional[str] = None
    FormatNational: Optional[str] = None
    FormatInternational: Optional[str] = None
    FormatE164: Optional[str] = None
    Extension: Optional[str] = None
    Locality: Optional[str] = None
    AdminArea: Optional[str] = None
    AdminAreaAbbr: Optional[str] = None
    Country: Optional[str] = None
    CountryISO2: Optional[str] = None
    CountryISO3: Optional[str] = None
    Latitude: Optional[float] = None
    Longitude: Optional[float] = None
    LatLongMatchLevel: Optional[str] = None
    TimeZones: Optional[List['TimeZone']] = None
    LineType: Optional[str] = None
    SmsAddress: Optional[str] = None
    ValidPhone: Optional[bool] = None
    ValidPhoneLength: Optional[bool] = None
    Notes: Optional[List[str]] = None
    Warnings: Optional[List[str]] = None
    CurrentProvider: Optional['ServiceProvider'] = None
    PreviousProvider: Optional['ServiceProvider'] = None
    OriginalProvider: Optional['ServiceProvider'] = None
    LastPortedDate: Optional[str] = None
    ProblemDetails: Optional['ProblemDetails'] = None

    def __post_init__(self):
        if self.TimeZones is None:
            self.TimeZones = []
        if self.Notes is None:
            self.Notes = []
        if self.Warnings is None:
            self.Warnings = []

    def __str__(self) -> str:
        """
        Convert the phone details response to a string representation.
        """
        time_zones_string = ', '.join(str(zone) for zone in self.TimeZones) if self.TimeZones else 'None'
        notes_string = f"[{', '.join(self.Notes)}]" if self.Notes else 'None'
        warnings_string = f"[{', '.join(self.Warnings)}]" if self.Warnings else 'None'
        current_provider = str(self.CurrentProvider) if self.CurrentProvider else 'None'
        previous_provider = str(self.PreviousProvider) if self.PreviousProvider else 'None'
        original_provider = str(self.OriginalProvider) if self.OriginalProvider else 'None'
        problem_details = str(self.ProblemDetails) if self.ProblemDetails else 'None'
        return (f"PVIResponse: Score={self.Score}, PhoneIn={self.PhoneIn}, CountryCode={self.CountryCode}, "
                f"FormatNational={self.FormatNational}, FormatInternational={self.FormatInternational}, "
                f"FormatE164={self.FormatE164}, Extension={self.Extension}, Locality={self.Locality}, "
                f"AdminArea={self.AdminArea}, AdminAreaAbbr={self.AdminAreaAbbr}, Country={self.Country}, "
                f"CountryISO2={self.CountryISO2}, CountryISO3={self.CountryISO3}, Latitude={self.Latitude}, "
                f"Longitude={self.Longitude}, LatLongMatchLevel={self.LatLongMatchLevel}, "
                f"LineType={self.LineType}, SmsAddress={self.SmsAddress}, ValidPhone={self.ValidPhone}, "
                f"ValidPhoneLength={self.ValidPhoneLength}, LastPortedDate={self.LastPortedDate}, "
                f"TimeZones=[{time_zones_string}], Notes={notes_string}, Warnings={warnings_string}, "
                f"CurrentProvider={current_provider}, PreviousProvider={previous_provider}, "
                f"OriginalProvider={original_provider}, ProblemDetails={problem_details}")

Phone Validation International NodeJS Rest Code Snippet

import axios from 'axios';
import querystring from 'querystring';
import { PVIResponse } from './pvi_response.js';

/**
 * @constant
 * @type {string}
 * @description The base URL for the live ServiceObjects Phone Validation International (PVI) API service.
 */
const LiveBaseUrl = 'https://sws.serviceobjects.com/PVI/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the backup ServiceObjects Phone Validation International (PVI) API service.
 */
const BackupBaseUrl = 'https://swsbackup.serviceobjects.com/PVI/';

/**
 * @constant
 * @type {string}
 * @description The base URL for the trial ServiceObjects Phone Validation International (PVI) API service.
 */
const TrialBaseUrl = 'https://trial.serviceobjects.com/PVI/';

/**
 * Checks if a response from the API is valid by verifying that it either has no ProblemDetails object
 * or the ProblemDetails.Status is not equal to 500.
 * @param {PVIResponse} response - The API response object to validate.
 * @returns {boolean} True if the response is valid, false otherwise.
 */
const isValid = (response) => !response?.ProblemDetails || response.ProblemDetails.Status !== 500;

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

/**
 * Performs an HTTP GET request to the specified URL with a given timeout.
 * @param {string} url - The URL to send the GET request to.
 * @param {number} timeoutSeconds - The timeout duration in seconds for the request.
 * @returns {Promise<PVIResponse>} A promise that resolves to a PVIResponse object containing the API response data.
 * @throws {Error} If the HTTP request fails, with a message detailing the error.
 */
export const httpGet = async (url, timeoutSeconds) => {
    let result = new PVIResponse();
    try {
        const response = await axios.get(url, { timeout: timeoutSeconds * 1000 });

        if (response.status === 200) {
            result.Score = response.data.score ?? null;
            result.PhoneIn = response.data.phoneIn ?? null;
            result.CountryCode = response.data.countryCode ?? null;
            result.FormatNational = response.data.formatNational ?? null;
            result.FormatInternational = response.data.formatInternational ?? null;
            result.FormatE164 = response.data.formatE164 ?? null;
            result.Extension = response.data.extension ?? null;
            result.Locality = response.data.locality ?? null;
            result.AdminArea = response.data.adminArea ?? null;
            result.AdminAreaAbbr = response.data.adminAreaAbbr ?? null;
            result.Country = response.data.country ?? null;
            result.CountryISO2 = response.data.countryISO2 ?? null;
            result.CountryISO3 = response.data.countryISO3 ?? null;
            result.Latitude = response.data.latitude ?? null;
            result.Longitude = response.data.longitude ?? null;
            result.LatLongMatchLevel = response.data.latLongMatchLevel ?? null;
            result.TimeZones = response.data.timeZones ??  [];
            result.LineType = response.data.lineType ?? null;
            result.SmsAddress = response.data.smsAddress ?? null;
            result.ValidPhone = response.data.validPhone ?? null;
            result.ValidPhoneLength = response.data.validPhoneLength ?? null;
            result.Notes = response.data.notes ?? null;
            result.Warnings = response.data.warnings ?? null;
            result.CurrentProvider = response.data.currentProvider ?? null;
            result.PreviousProvider = response.data.previousProvider ?? null;
            result.OriginalProvider = response.data.originalProvider ?? null;
            result.LastPortedDate = response.data.lastPortedDate ?? null;
            result.ProblemDetails = null;
        } else {
            result.ProblemDetails = {
                Title: "Service Objects Error",
                Status: response.status,
                Detail: response.statusText
            };
            result.Score = null;
            result.PhoneIn = null;
            result.CountryCode = null;
            result.FormatNational = null;
            result.FormatInternational = null;
            result.FormatE164 = null;
            result.Extension = null;
            result.Locality = null;
            result.AdminArea = null;
            result.AdminAreaAbbr = null;
            result.Country = null;
            result.CountryISO2 = null;
            result.CountryISO3 = null;
            result.Latitude = null;
            result.Longitude = null;
            result.LatLongMatchLevel = null;
            result.TimeZones = [];
            result.LineType = null;
            result.SmsAddress = null;
            result.ValidPhone = null;
            result.ValidPhoneLength = null;
            result.Notes = null;
            result.Warnings = null;
            result.CurrentProvider = null;
            result.PreviousProvider = null;
            result.OriginalProvider = null;
            result.LastPortedDate = null;
        }
    } catch (error) {
        result.ProblemDetails = {
            type: error.response.data.type,
            title: error.response.data.title,
            status: error.response.data.status,
            detail: error.response.data.detail
        };
        result.Score = null;
        result.PhoneIn = null;
        result.CountryCode = null;
        result.FormatNational = null;
        result.FormatInternational = null;
        result.FormatE164 = null;
        result.Extension = null;
        result.Locality = null;
        result.AdminArea = null;
        result.AdminAreaAbbr = null;
        result.Country = null;
        result.CountryISO2 = null;
        result.CountryISO3 = null;
        result.Latitude = null;
        result.Longitude = null;
        result.LatLongMatchLevel = null;
        result.TimeZones = [];
        result.LineType = null;
        result.SmsAddress = null;
        result.ValidPhone = null;
        result.ValidPhoneLength = null;
        result.Notes = null;
        result.Warnings = null;
        result.CurrentProvider = null;
        result.PreviousProvider = null;
        result.OriginalProvider = null;
        result.LastPortedDate = null;
    }
    return result;
};

/**
 * Provides functionality to call the ServiceObjects Phone Validation International (PVI) API's GetPhoneDetails endpoint,
 * retrieving phone details (e.g., carrier, line type, time zones) for a given phone number
 * with fallback to a backup endpoint for reliability in live mode.
 */
const GetPhoneDetailsClient = {
    /**
     * Asynchronously invokes the GetPhoneDetails API endpoint, attempting the primary endpoint
     * first and falling back to the backup if the response is invalid (ProblemDetails.Status == 500) in live mode.
     * @param {string} Phone - The phone number to validate (e.g., "+12025550123").
     * @param {string} [Country] - The country code or name (e.g., "US"). Optional.
     * @param {string} [Options] - Optional. Reserved for future use.
     * @param {string} AuthID - Your license key to use the service.
     * @param {boolean} [isLive=true] - Value to determine whether to use the live or trial servers.
     * @param {number} [timeoutSeconds=15] - Timeout, in seconds, for the call to the service.
     * @returns {Promise<PVIResponse>} A promise that resolves to a PVIResponse object.
     */
    async invokeAsync(Phone, Country, Options, AuthID, isLive = true, timeoutSeconds = 15) {
        const params = {
            Phone,
            Country,
            Options,
            AuthID
        };

        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;
    },

    /**
     * Synchronously invokes the GetPhoneDetails API endpoint by wrapping the async call
     * and awaiting its result immediately. Note: This method should be used cautiously
     * in Node.js as it blocks the event loop.
     * @param {string} Phone - The phone number to validate (e.g., "+12025550123").
     * @param {string} [Country] - The country code or name (e.g., "US"). Optional.
     * @param {string} [Options] - Optional. Reserved for future use.
     * @param {string} AuthID - Your license key to use the service.
     * @param {boolean} [isLive=true] - Value to determine whether to use the live or trial servers.
     * @param {number} [timeoutSeconds=15] - Timeout, in seconds, for the call to the service.
     * @returns {PVIResponse} A PVIResponse object with phone details or an error.
     */
    invoke(Phone, Country, Options, AuthID, isLive = true, timeoutSeconds = 15) {
        return (async () => await this.invokeAsync(
            Phone, Country, Options, AuthID, isLive, timeoutSeconds
        ))();
    }
};

export { GetPhoneDetailsClient, PVIResponse };


/**
 * Phone details response component for PVI API.
 */
export class PVIResponse {
  constructor(data = {}) {
    this.Score = data.score;
    this.PhoneIn = data.phoneIn;
    this.CountryCode = data.countryCode;
    this.FormatNational = data.formatNational;
    this.FormatInternational = data.formatInternational;
    this.FormatE164 = data.formatE164;
    this.Extension = data.extension;
    this.Locality = data.locality;
    this.AdminArea = data.adminArea;
    this.AdminAreaAbbr = data.adminAreaAbbr;
    this.Country = data.country;
    this.CountryISO2 = data.countryISO2;
    this.CountryISO3 = data.countryISO3;
    this.Latitude = data.latitude;
    this.Longitude = data.longitude;
    this.LatLongMatchLevel = data.latLongMatchLevel;
    this.TimeZones = (data.timeZones || []).map(zone => new TimeZone(zone));
    this.LineType = data.lineType;
    this.SmsAddress = data.smsAddress;
    this.ValidPhone = data.validPhone;
    this.ValidPhoneLength = data.validPhoneLength;
    this.Notes = data.notes;
    this.Warnings = data.warnings;
    this.CurrentProvider = data.currentProvider;
    this.PreviousProvider = data.previousProvider;
    this.OriginalProvider = data.originalProvider;
    this.LastPortedDate = data.lastPortedDate;
  }

  toString() {
    const timeZonesString = this.TimeZones.length
      ? this.TimeZones.map(zone => zone.toString()).join(", ")
      : "null";
    const notesString = this.Notes ? `[${this.Notes.join(", ")}]` : "null";
    const warningsString = this.Warnings ? `[${this.Warnings.join(", ")}]` : "null";
    return `Score: ${this.Score}, PhoneIn: ${this.PhoneIn}, CountryCode: ${this.CountryCode}, FormatNational: ${this.FormatNational}, ` +
      `FormatInternational: ${this.FormatInternational}, FormatE164: ${this.FormatE164}, Extension: ${this.Extension}, ` +
      `Locality: ${this.Locality}, AdminArea: ${this.AdminArea}, AdminAreaAbbr: ${this.AdminAreaAbbr}, ` +
      `Country: ${this.Country}, CountryISO2: ${this.CountryISO2}, CountryISO3: ${this.CountryISO3}, ` +
      `Latitude: ${this.Latitude}, Longitude: ${this.Longitude}, LatLongMatchLevel: ${this.LatLongMatchLevel}, ` +
      `LineType: ${this.LineType}, SmsAddress: ${this.SmsAddress}, ValidPhone: ${this.ValidPhone}, ` +
      `ValidPhoneLength: ${this.ValidPhoneLength}, LastPortedDate: ${this.LastPortedDate}, ` +
      `TimeZones: [${timeZonesString}], Notes: ${notesString}, Warnings: ${warningsString}, ` +
      `CurrentProvider: ${this.CurrentProvider ? this.CurrentProvider.toString() : "null"}, ` +
      `PreviousProvider: ${this.PreviousProvider ? this.PreviousProvider.toString() : "null"}, ` +
      `OriginalProvider: ${this.OriginalProvider ? this.OriginalProvider.toString() : "null"}`;
  }
}

/**
 * Problem details component for PVI API errors.
 */
export class ProblemDetails {
    constructor({ type = null, title = null, status = null, detail = null } = {}) {
        this.type = type;
        this.title = title;
        this.status = status;
        this.detail = detail;
    }

  toString() {
    return `Type: ${this.type}, Title: ${this.title}, Status: ${this.status}, Detail: ${this.detail}`;
  }
}

/**
 * Time zone information for a phone number.
 */
export class TimeZone {
  constructor(data = {}) {
    this.ZoneName = data.zoneName;
    this.ZoneAbbr = data.zoneAbbr;
    this.CountryISO3 = data.countryISO3;
    this.UtcOffset = data.utcOffset;
  }

  toString() {
    return `ZoneName: ${this.ZoneName}, ZoneAbbr: ${this.ZoneAbbr}, CountryISO3: ${this.CountryISO3}, UtcOffset: ${this.UtcOffset}`;
  }
}