| Platform | Get a name | Check for spam |
|---|---|---|
| Android | CallerAPI.lookupLabel(phone) | CallerAPI.isSpam(phone) |
| iOS | try await CallerAPI.lookupLabel(phone) | try await CallerAPI.isSpam(phone) |
| Flutter | await CallerAPI.lookupLabel(phone) | await CallerAPI.isSpam(phone) |
| React Native | await CallerAPI.lookupLabel(phone) | await CallerAPI.isSpam(phone) |
+18883578668.com.callerapi.sdk.CallerAPI.| Method | Returns | Notes |
|---|---|---|
register(context, featureEnabled = true, config = CallerAPIConfig()) | Unit | Call once in Application.onCreate. Safe to call twice. |
cleanup() | Unit | Shuts the native core down. Rarely needed. |
register resolves each credential in this order: the value in config, then the manifest meta-data entry, then any value compiled into the native library.| Method | Returns | Blocking? |
|---|---|---|
suspend lookupLabel(phone) | String? | No, runs on the IO dispatcher |
suspend lookupLabelOrNull(phone) | String? | No. Same as lookupLabel |
suspend lookupLabelCode(phone) | Int | No. 0 means success |
isSpam(phone) | Boolean | Yes. Never call on the main thread |
spamCheckCode(phone) | Int | Yes. 1 spam, 0 clean, negative is an error |
cachedSpamVerdict(phone) | SpamVerdict | No. Reads the cache only, never the network |
prewarmAsync(phone) | Unit | No. Returns at once, fetches in the background |
lookupLabel returns null both for "not a business" and for a failed lookup. Use lookupLabelCode when you need to tell those apart.SpamVerdict is SPAM, CLEAN, or UNKNOWN. UNKNOWN means the cache has no answer, so treat the call as clean.cachedSpamVerdict and prewarmAsync on the call path. They are the only two that never block.| Method | Returns | Notes |
|---|---|---|
canShowIncomingCallerIdOverlay(context) | Boolean | Whether the user granted the overlay permission |
setIncomingCallerIdOverlayEnabled(enabled) | Unit | Turns the SDK name card off. Default is on. This does not give you the incoming number. To draw your own card, replace the screening service. See Android call screening |
setFeatureEnabled(enabled) | Unit | Reported for billing. Only enabled devices are billable |
setAnalyticsConsent(consented) | Unit | Blocked call counts are reported only after this |
suspend checkIn() | Boolean | Reports usage now, instead of waiting for the schedule |
CallerAPIVersion.VALUE holds the SDK version as a string.| Key | Required for |
|---|---|
com.callerapi.sdk.SDK_KEY | Lookups |
com.callerapi.sdk.SDK_SECRET | Lookups |
com.callerapi.sdk.CLIENT_KEY | Device counting and billing |
com.callerapi.sdk.API_BASE | Nothing. Only to point at another endpoint |
import CallerAPI.| Method | Returns | Notes |
|---|---|---|
configure(_ config: CallerAPIConfig, featureEnabled: Bool = true) | Void | Supplies credentials. Call once at launch |
configure(featureEnabled: Bool = true) | Void | Only for a build with credentials compiled in |
isSpam(_ phone: String) async throws | Bool | |
lookupLabel(_ phone: String) async throws | String? | nil means not a business |
checkIn() async | Bool | Reports usage now |
configure, the SDK reads CallerAPISDKKey and CallerAPISDKSecret from your Info.plist on the first lookup. That is how the Flutter and React Native bridges get their credentials.CallerAPIError with an Int32 code. See Errors.CallerAPI.CallDirectory, for blocking from a list downloaded in advance.| Method | Returns | Called from |
|---|---|---|
configure(_ config: Config) | Void | Your app and the extension |
sync() async throws | Void | Your app. Downloads the list |
scheduleDeferredSync(after: TimeInterval) | Void | Your app. Avoids syncing on a cold launch |
hasBlocklistBlob() throws | Bool | The extension, before load |
load(into: CXCallDirectoryExtensionContext) throws | Void | The extension |
hasBlocklistBlob before load. Until your app has synced once there is no list, and reporting that as an error makes iOS stop asking your extension.CallerAPI.BackgroundSync.register() registers the background task. Its identifier is CallerAPI.BackgroundSync.taskIdentifier, which is com.callerapi.checkin. Add that string to BGTaskSchedulerPermittedIdentifiers in your Info.plist.import CallerAPILiveCallerID.CallKit.framework, and a process without CallKit loaded cannot find it, so every call fails with Cocoa 4099. This is only your concern if you build from copied sources rather than from the package.| Method | Returns | Notes |
|---|---|---|
bootstrap(_ options: BootstrapOptions) | Void | Call once at launch. Does everything below |
waitUntilEnabled(forExtensionWithIdentifier:timeout:) async | Bool | Polls the status until it settles. Prefer this at launch |
isDisabled(forExtensionWithIdentifier:) | Bool | Raw status. Reads as disabled while CallDirectory is starting |
openSettings() async throws | Void | Opens the Settings screen with the toggle |
prewarmIfEnabled(extensionIdentifier:reason:clearCachedLookups:) async | Bool | Refreshes keys before a call arrives |
reprovision(forExtensionWithIdentifier:clearCachedLookups:) async throws | Void | Optional reset, then context, then PIR parameters |
reset(forExtensionWithIdentifier:) async throws | Void | Drops the system cache: tokens, keys, and cached lookups |
refreshExtensionContext(forExtensionWithIdentifier:) async throws | Void | Makes the system re-read your URLs and tier token. iOS 18.1+ |
refreshPIRParameters(forExtensionWithIdentifier:) async throws | Void | Lower level than prewarmIfEnabled |
isCallDirectoryUnavailable(_ error: Error) | Bool | The system is not ready yet, so retry |
isStaleExtensionConnection(_ error: Error) | Bool | First call after an in-place app update. Retry once |
configureMetering(clientKey:extensionIdentifier:endpoint:) | Void | Included in bootstrap |
sendHeartbeat() async throws | HeartbeatResult | Included in bootstrap |
sdkVersion | String |
bootstrap. It reports usage, registers the background refresh, and refreshes the extension at launch and on every return to the foreground.waitUntilEnabled rather than isDisabled. A single status read at launch returns Cocoa 4099 while CallDirectory starts, which looks exactly like "the user switched this off". waitUntilEnabled polls until the answer settles, and never launches the extension to find out. Never poll by calling reset or refreshExtensionContext: each one spawns a process, and a loop of them wedges the Settings toggle until the phone is rebooted.reprovision(clearCachedLookups: true) after your data changes. A lookup that returned no name is cached on the device for 24 hours, and refreshPIRParameters does not clear it, because it refreshes the dataset description rather than any cached answer.makeExtensionContext() returns the object iOS asks for. It is the only call your extension needs.| Method | Use when |
|---|---|
appGroupIdentifier | You give different users different data tiers |
setUserTierToken(_ token: Data) | Same. Needs an App Group on both targets |
setEndpoints(serviceURL:tokenIssuerURL:) | You point at staging, or at hosts reserved for you |
effectiveConfiguration | You want to read back what the SDK will use |
resetSharedOverrides() | You undo the two setters above |
manager | You need Apple's LiveCallerIDLookupManager directly |
import 'package:callerapi/callerapi.dart'; then call statics on CallerAPI.| Method | Returns | Platform |
|---|---|---|
lookupLabel(String phone) | Future<String?> | Both |
isSpam(String phone) | Future<bool> | Both |
checkIn() | Future<bool> | Both |
setFeatureEnabled(bool enabled) | Future<void> | Both |
setAnalyticsConsent(bool consented) | Future<void> | Android. Does nothing on iOS |
requestCallScreeningRole() | Future<bool> | Android |
isCallScreeningRoleGranted() | Future<bool> | Android |
requestOverlayPermission() | Future<bool> | Android |
isOverlayPermissionGranted() | Future<bool> | Android |
bootstrapFromInfoPlist() | Future<bool> | iOS |
bootstrap(CallerAPIBootstrapOptions options) | Future<bool> | iOS |
getLiveLookupStatus() | Future<LiveLookupStatus> | iOS |
openLiveLookupSettings() | Future<bool> | iOS |
prewarmLiveLookup({bool clearCachedLookups = false}) | Future<bool> | iOS |
resetLiveLookup() | Future<bool> | iOS |
syncCallDirectory() | Future<bool> | iOS |
false, so you do not need a platform check.initialize. The plugin starts the SDK and reads your credentials from the Android manifest and the iOS Info.plist.import CallerAPI from '@callerapi/react-native';. A default import, not a named one.resetLiveLookup(). Two differences:| Method | Flutter | React Native |
|---|---|---|
setFeatureEnabled | Future<void> | void. Do not await it |
setAnalyticsConsent | Future<void> | void. Do not await it |
export type LiveLookupStatus = {
enabled: boolean;
extensionId?: string;
serviceURL?: string;
tokenIssuerURL?: string;
error?: string;
};
export type CallerAPIBootstrapOptions = {
liveLookupExtensionId: string;
lcidClientKey: string;
lcidHeartbeatTaskId?: string;
appGroup?: string;
callDirectoryExtensionId?: string;
keychainAccessGroup?: string;
deferCallDirectorySyncSeconds?: number;
};pod install and rebuild.| Native function | Platform |
|---|---|
cachedSpamVerdict, prewarmAsync | Android |
spamCheckCode, lookupLabelCode | Android |
setIncomingCallerIdOverlayEnabled | Android |
CallerAPIConfig timeouts | Android and iOS |
setUserTierToken, setEndpoints | iOS |
Typed CallerAPIError codes | iOS |