Consent Docs

iOS SDK

Add the PiedapConsent Swift package to an iOS or macOS app, create the client and look up every method, type and error.

Reference7 min readFor developers using the browser runtime and the server and mobile SDKs

PiedapConsent records consent from a native iOS or macOS app and keeps the answer on the device. It draws no banner: your app shows its own screen from the form the server sends.

Requirements#

  • iOS 13 or macOS 12, or later.
  • Swift 5.10 (Xcode 15.3) or later. The package depends on Foundation only.
  • A publishable key, pk_…. Its Allowed origins must list app: followed by the bundle id, such as app:in.example.kalpa.
  • A published form that a region rule sends your users to.

A test key answers any app. A live key answers only the apps on its list. See Mobile apps.

Install#

The package is not published to a public registry or served by the deployment. Your account contact provides the source.

In Xcode, choose File → Add Package Dependencies… → Add Local… and pick the consent-ios folder. Then link the PiedapConsent product. With a Package.swift, refer to the package by its folder name:

Package.swiftSwift
// swift-tools-version:5.10
import PackageDescription

let package = Package(
    name: "App",
    platforms: [.iOS(.v13), .macOS(.v12)],
    dependencies: [
        .package(path: "../consent-ios"),
    ],
    targets: [
        .target(
            name: "App",
            dependencies: [
                .product(name: "PiedapConsent", package: "consent-ios"),
            ]
        ),
    ]
)

Create the client#

PiedapConsent()#

Creates the client. Create one and keep it for the life of the app.

Parameter Type Required or default Description
apiUrl String Required The deployment address.
publishableKey String Required The publishable key, pk_….
applicationId String Required The bundle id. It must be the id listed on the key after app:.
store ConsentStore InMemoryConsentStore() Where state is kept. The default forgets everything when the app exits.
timeout TimeInterval 10 Request timeout in seconds.
sessionConfiguration URLSessionConfiguration .ephemeral The configuration of the URLSession the client uses.

Returns a PiedapConsent client.

Errors Throws ConsentAPIError with status 0 for a key that does not start with pk_, before any request is made.

Swift
import PiedapConsent

let consent = try PiedapConsent(
    apiUrl: "https://console.dpconsent.com/consent",
    publishableKey: "pk_…",
    applicationId: Bundle.main.bundleIdentifier!,
    store: UserDefaultsConsentStore()
)

Each request carries the key in X-Piedap-Key and the bundle id in X-Piedap-App. Redirects are never followed.

Properties#

Property Type Description
decisions [String: Bool] The stored decisions by purpose key. Empty until the user decides.
consentId String? The record's id, cns_…. nil before the first answer.
onChange (([String: Bool]) -> Void)? Called with the decisions after every save and withdrawal, off the main thread. The client holds one handler.
Swift
consent.onChange = { latest in applyConsent(latest) }

Methods#

The methods that call the network are async and throw. There is no offline queue and no retry: a failed save throws, and the answer is not stored. Reading never needs the network.

loadForm(locale:countryCode:)#

Fetches the form for this device.

Parameter Type Required or default Description
locale String Required A language tag, such as hi-IN.
countryCode String? nil A two-letter country code. With nil, the server works it out from the request.

Returns a ConsentForm. The country you pass picks the form only. The record takes its country and law from where the request comes from.

Errors Throws ConsentAPIError.

Swift
let form = try await consent.loadForm(
    locale: Locale.preferredLanguages.first ?? "en"
)
let formForIndia = try await consent.loadForm(
    locale: "hi-IN",
    countryCode: "IN"
)

shouldAsk(_:)#

Tells whether to show your consent screen for a form.

Parameter Type Required or default Description
form ConsentForm Required The form from loadForm.

Returns Bool. It is true:

  • before the first decision
  • when the published form has changed since the user answered, unless its consentModel is NOTICE_ONLY
  • when the server asks for consent again

It is false when the form is not available.

Errors None.

Swift
if consent.shouldAsk(form) {
    presentConsentScreen(form)
}

acceptAll(_:)#

Records that the user accepted every purpose. The record's interaction is ACCEPT_ALL.

Parameter Type Required or default Description
form ConsentForm Required The form the user answered.

Returns [String: Bool], the stored decisions by purpose key.

Errors Throws ConsentAPIError. The status is 0 for a form that is not available.

Swift
try await consent.acceptAll(form)

rejectAll(_:)#

Records that the user refused every optional purpose. The record's interaction is REJECT_ALL.

Parameter Type Required or default Description
form ConsentForm Required The form the user answered.

Returns [String: Bool], the stored decisions. Always-on purposes stay allowed.

Errors Throws ConsentAPIError. The status is 0 for a form that is not available.

Swift
try await consent.rejectAll(form)

savePreferences(_:choices:)#

Records the user's own choices. The record's interaction is SAVE_PREFERENCES.

Parameter Type Required or default Description
form ConsentForm Required The form the user answered.
choices [String: Bool] Required Purpose key to allowed. A purpose you leave out counts as declined.

Returns [String: Bool], the stored decisions. Always-on purposes stay allowed.

Errors Throws ConsentAPIError. The status is 0 for a form that is not available.

Swift
try await consent.savePreferences(
    form,
    choices: ["analytics": true, "marketing": false]
)

The three recording methods write records in the same way:

  • The first answer creates a record. A change of mind on the same form version updates it.
  • An answer to a new version, or to a request for fresh consent, creates a new record. So does a change to a record that was erased on the server.
  • The record is kept under a random id made for this install, with the type DEVICE_ID, unless you called identify.

granted(_:)#

Tells whether the stored decision allows a purpose.

Parameter Type Required or default Description
purposeKey String Required The purpose key.

Returns Bool. It is false for every purpose until the user decides. After loadForm finds no form for the user's location, it is true for every purpose, unless a decision is already stored.

Errors None.

Swift
if consent.granted("analytics") {
    Analytics.enable()
}

withdraw()#

Withdraws the record on the server and clears it from the device.

Takes no parameters.

Returns nothing. The next shouldAsk is true again.

Errors Throws ConsentAPIError. The stored decision then stays as it was.

Swift
try await consent.withdraw()

identify(subjectId:subjectType:proof:)#

Records consent created from now on under the signed-in person instead of the device.

Parameter Type Required or default Description
subjectId String Required The person's identifier, exactly the one the proof was made for.
subjectType SubjectType Required .userId, .email or .phone.
proof String Required The identity proof your server computed: 64 hex characters.

Returns nothing. The identity is kept in memory only, so call identify after sign-in on every launch.

Errors Throws ConsentAPIError with status 0 for an empty subjectId or a proof that is not 64 hex characters.

Swift
try consent.identify(
    subjectId: session.userId,
    subjectType: .userId,
    proof: session.consentProof
)

identify does not call the server. A proof the server cannot verify is refused when the next record is created, with status 403 and the code IDENTITY_PROOF_REQUIRED. A decision already recorded under the device id keeps being updated in its own record. Compute the proof with identityProof() or identity_proof().

clearIdentity()#

Records consent created from now on under the device again. Call it on sign-out.

Takes no parameters.

Returns nothing.

Errors None.

Swift
consent.clearIdentity()

Types#

ConsentForm#

A text field the form does not have is nil.

Field Type Description
available Bool false when no form is targeted at the user's location. There is nothing to ask.
title, body String? The wording of the notice, in the language asked for.
acceptAllLabel, rejectAllLabel, savePreferencesLabel String? Button wording.
privacyPolicyUrl String? The privacy policy link, if the form has one.
purposes [Purpose] The purposes to show.
consentModel String? OPT_IN, OPT_OUT or NOTICE_ONLY.
law String? The law that applies, such as DPDP.
key, contentHash String Which form this is.
version Int The form's version.
locale String? The language of the wording.
reconsentRequired Bool true when the server asks for a fresh answer.

Purpose#

Field Type Description
key String The purpose key.
name, description, category String? What to show for the purpose.
alwaysOn Bool The purpose cannot be switched off.
defaultGranted Bool The form's default for the purpose.

ConsentStore#

The protocol the client keeps its state through.

Member Description
value(forKey:) Returns the stored string, or nil.
set(_:forKey:) Stores a string.
removeValue(forKey:) Removes a key.
InMemoryConsentStore Built in. Keeps state in memory only.
UserDefaultsConsentStore Built in. Keeps state between launches. Pass defaults: to share state with app extensions.
Swift
let shared = UserDefaultsConsentStore(
    defaults: UserDefaults(suiteName: "group.in.example.kalpa")!
)

The store holds a random id for the install, the consent id, the latest decisions, the form and version they answer, and whether the last fetch found a form. Its keys start with piedap.consent..

SubjectType#

An enum with the cases .userId (USER_ID), .email (EMAIL) and .phone (PHONE).

Errors#

A failed request throws ConsentAPIError.

Field Type Description
status Int The HTTP status. 0 when the server was not reached, or when the SDK found the mistake before any request.
message String The server's message.
code String? The server's code, or nil.

Match on the status and the code, not on the message. A 401 means the key was not accepted. It is wrong, revoked or turned off, or it is a live key and the bundle id is not in its allowed origins. Every code is listed in Errors.

Swift
do {
    try await consent.acceptAll(form)
} catch let error as ConsentAPIError {
    print(error.status, error.code ?? "no code", error.message)
}

Not in this SDK#

  • A ready-made consent screen. Draw your own from the form.
  • Choices partner by partner. The SDK answers per purpose only.
  • Linked identifiers. Link them from your server with linkIdentifiers().
  • An offline queue or retries.
  • React Native and Flutter wrappers. Bridge the native SDK yourself, or call the API with the key in X-Piedap-Key and your app id in X-Piedap-App.
  • The complaint link, the rights request link, the privacy contact email and the list of languages. Read form.copy.complaintUrl, runtime.rightsUrl, runtime.contactEmail and form.locales from GET /sdk/v1/form.
  • Tracker blocking, Google Consent Mode, IAB TCF and GPP. Gate your own SDKs on granted.

↑↓ to move, Enter to open