Application identity
How an app proves it is your app, with App Attest for iOS or API keys for a server.
The Auth policy page has three levels, and Application identity is the first. It answers one question: is this request coming from my app? An iOS application answers with Apple App Attest. A server answers with an API key.
The level shows its state at a glance: Verified with App Attest, Team or bundle id missing, Verified with API keys, or No active API key.
iOS applications
Only builds signed with your team and bundle ID can call AI providers through this app.
- Apple Team ID: found in your Apple Developer account under Membership details.
- Bundle ID: on your target's Signing & Capabilities tab in Xcode.
Apple binds an App Attest key to one install of one app on one device, and the attestation carries a hash of your team ID and bundle ID. The gateway checks that hash, stores the key's public half, and from then on accepts assertions signed by that key: one at each token exchange and, unless the app verifies App Attest on the token exchange only, one on every request. The key never leaves the Secure Enclave.
Keys are per install. A signed-in user who uses your app on a phone and a tablet holds two keys, and a user who reinstalls the app gets a new one; registering a key never replaces another. Each install is counted on its own by the per-install limits.
iOS app environment
Apple runs App Attest in two environments and stamps which one into every attestation. An app accepts one or both, and at least one must stay on.
- Production: builds signed for TestFlight and the App Store. On by default.
- Development: builds run from Xcode, which attest in Apple's development environment. Off unless you turn it on.
A development-signed build is debuggable and runs on any device carrying your team's provisioning profile, which is why Development is off by default and why the console warns when you turn it on. Put the opt-in on a development bundle ID, never on the one you ship.
Removing an environment also stops the keys it already admitted: on their
next request when the app verifies every request, otherwise at their next
token exchange, within the hour. The refusal is 403 attest_failed, and the
app registers a fresh key in an environment the app still accepts.
Two things no setting changes. App Attest is unavailable in the Simulator,
because it needs the Secure Enclave. And a development build can be made to
attest in production instead by setting the
com.apple.developer.devicecheck.appattest-environment entitlement to
production, which is the better answer when a development build only needs
to reach a production-only app.
Verify App Attest on
When the gateway checks the install's App Attest assertion.
- Every request: each request carries a fresh App Attest assertion, so a gateway token copied off the network is useless without the device. Requires the AppAIGateway Swift package 1.4 or later. The default for new apps.
- Token exchange: the assertion is checked once per hourly token, and requests carry only the token.
Apps created before this setting was added were set to Token exchange, so
their existing installs keep working. Switch to Every request once your
clients are on the Swift package 1.4 or later: from then on a request without
a proof is refused with 401 assertion_required, which is what every build
on an older package receives. Request limits, budgets and usage are counted
per install either way. See
When App Attest is verified.
In the configuration
"authentication": {
"type": "apple_app_attest",
"app_attest": {
"team_id": "ABCDE12345",
"bundle_id": "com.example.app.dev",
"environments": ["production", "development"],
"verify_on": "every_request"
},
"end_user": { "source": "app_install" }
}environments may be omitted, which means ["production"]. verify_on is
every_request or token_exchange, and may be omitted, which means
every_request. end_user is
required on an iOS app and is covered under
User authentication.
Server applications
A server application proves itself with one of its API keys. The card lists every key with its Name, Prefix, Last used and Status.
New key asks for a Key name so you can tell keys apart, then shows the key once in a dialog titled Copy your new key now. The gateway stores only a hash, so there is no way to see the key again. Store it in your backend's secret manager.
Revoke stops a key within a minute. Create the replacement first, move your backend to it, then revoke the old one, and rotation costs no downtime.
Once revocation has propagated, bare-key requests using those keys fail with
401 auth_required. Gateway tokens issued from a revoked key also stop
working within a minute. The app's pages show a notice when no active key remains.
How the key is sent
Without signed-in users, your backend sends the key as the bearer credential on every request:
Authorization: Bearer agw_…With signed-in users, the key is exchanged together with the user's sign-in token for a gateway token instead, and bare keys are refused on requests. See Server applications.
In the configuration
"authentication": { "type": "api_key", "end_user": { "source": "none" } }end_user is required here too. none means the app has no end users; the
other sources are covered under
User authentication.
Keys are not part of the configuration. POST /v1/admin/apps/{app}/keys
creates one and returns key once; POST /v1/admin/apps/{app}/keys/{key}/revoke
revokes it.