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:
KapeDedicatedIPKind | What it is | License |
|---|---|---|
.anonymous | Unlocked on the device with a backup password (dipSetupRegisterBackupPassword, dipSetupAssignLocation) | CategoryDedicatedIp |
.simple | Reserved on the account. Needs no setup and carries real coordinates | CategorySimpleDedicatedIp |
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
idanddip.idare its reservation ID. - The call throws
KapeVPNError.simpleDipFetchFailedwhen 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)
| Anonymous | Simple | |
|---|---|---|
| License | CategoryDedicatedIp and VpnProtocolLightway | CategorySimpleDedicatedIp |
| Protocols | Lightway only | Those its endpoints serve (Lightway today), with the same transport rules as a regular location |
| HTTPS proxy | Not applied | Applied |
| Renewal | Renewed by the SDK while connected | Not 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..automaticalways 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 == 0means 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, soavailablecan be too high..slotsExhaustedfromreserveSimpleDedicatedIP(at:)stays the authoritative answer. - It throws
KapeVPNError.simpleDipFetchFailedwhen the token's entitlements cannot be read.