Skip to main content

Dedicated IP

A Dedicated IP (DIP) is an address reserved for one subscription. In the SDK it is a KapeServerLocation whose dip is set.

Listing Dedicated IPs​

There are two kinds of Dedicated IP, and KapeLocationService.fetchDedicatedIPs() lists both in one call, so the app does not need to work out which kind an account has:

KapeDedicatedIPKindWhat it isLicense
.anonymousUnlocked on the device with a backup password (dipSetupRegisterBackupPassword, dipSetupAssignLocation)CategoryDedicatedIp
.simpleReserved on the account. Needs no setup and carries real coordinatesCategorySimpleDedicatedIp
let locations = KapeLocationService(handle: handle)
for dip in try await locations.fetchDedicatedIPs() {
// dip.dip?.kind is .anonymous or .simple; dip.dip?.ip is the address.
}
  • A kind your license does not grant contributes nothing. The call does not throw for it.
  • An account with no Dedicated IP gets an empty list, not an error.
  • When both kinds report the same address, it appears once, as .anonymous.
  • A Simple Dedicated IP's id and dip.id are its reservation ID.
  • The call throws KapeVPNError.simpleDipFetchFailed when the backend cannot list Simple Dedicated IPs, even if Anonymous ones loaded.
  • fetchDipConnectionStatuses() reports each Simple Dedicated IP as .unlocked(location:), after the per-subscription Anonymous statuses.

Connecting​

Pass any location from fetchDedicatedIPs() to KapeVPNManager.connect(to:), the same call used for regular locations. Switching between a regular location and either kind works while connected.

try await manager.connect(to: dip)
AnonymousSimple
LicenseCategoryDedicatedIp and VpnProtocolLightwayCategorySimpleDedicatedIp
ProtocolsLightway onlyThose its endpoints serve (Lightway today), with the same transport rules as a regular location
HTTPS proxyNot appliedApplied
RenewalRenewed by the SDK while connectedNot needed

An unlicensed Dedicated IP throws KapeVPNError.dipNotEntitled (or KapeFeatureNotLicensedError for Lightway) before anything is persisted. Two failures surface from the tunnel instead:

  • A protocol the Dedicated IP does not serve: PacketTunnelError.dipProtocolNotSupported. .automatic always works.
  • A Simple Dedicated IP that is no longer reserved: PacketTunnelError.dipUnavailable.

Like any configuration failure, the tunnel keeps retrying with backoff and the status stays .connecting. The error becomes connectionError once the attempt ends, for example when the app disconnects.

Reserving a Simple Dedicated IP​

A Simple Dedicated IP needs no setup: pick a location and reserve it. Both calls need CategorySimpleDedicatedIp.

let catalog = try await locations.fetchSimpleDedicatedIPLocations()
// Catalog entries: `id` is a location ID, `dip?.ip` is nil. Locations with nothing left are omitted.

do {
let reservation = try await locations.reserveSimpleDedicatedIP(at: catalog[0])
// reservation.ip now appears in fetchDedicatedIPs()
} catch let error as KapeSimpleDedicatedIPReservationError {
switch error {
case .notEntitled: hideFeature()
case .slotsExhausted, .noCapacity, .entitlementEnded: show(error.localizedDescription)
case .invalidLocation: refreshCatalog()
case .temporarilyUnavailable: retryLater()
}
}

Without the license, or with no active subscription, fetchSimpleDedicatedIPLocations() returns an empty list and reserveSimpleDedicatedIP(at:) throws .notEntitled without calling the backend. Any other failure throws KapeError.

How many can be reserved​

let slots = try await locations.fetchSimpleDedicatedIPSlots()
// slots.total: slots the subscription grants. slots.reserved: addresses already reserved.
// slots.available: total - reserved, never negative.
  • total == 0 means the subscription is not entitled. The same zeros come back without the license or an active subscription; the call does not throw for them.
  • It reads the cached subscription token, so it makes no network call while the token is valid. A change made by billing or support appears once the token refreshes; call KapeSdkHandle.refreshEntitlements() to force it. A successful reservation refreshes it.
  • A reservation the token does not list yet is not counted in reserved, so available can be too high. .slotsExhausted from reserveSimpleDedicatedIP(at:) stays the authoritative answer.
  • It throws KapeVPNError.simpleDipFetchFailed when the token's entitlements cannot be read.