# Control App Access to Your Connected Apple Data

Canonical: https://figment.so/apple-connect-security

When you connect an Apple Account through Figment, you are not handing each developer app the keys to your whole iCloud. You are issuing scoped app grants — each separate from the underlying connection — and each can be revoked independently without touching other grants or the connection itself. This page maps the security boundary between your connection, your credentials, and every app that uses them.

**Two distinct layers.** Figment operates its own consent and grant layer: developers request scopes, users approve them through Figment's connection screen, and the developer app receives a scoped bearer token. Apple separately provides native authorization for Mail, Calendar, and Contacts, and Sign in with Apple for identity authentication. These are distinct systems. Figment issues the grant; it is not issued inside Apple's own authorization infrastructure.

## Authorization by Design

[RFC 9700](https://www.rfc-editor.org/rfc/rfc9700) (OAuth 2.0 Security Best Current Practice, Section 2.1.1) requires public clients to use PKCE (Proof Key for Code Exchange). The developer directs the user to `https://figment.so/apple-connect/authorize` with `response_type=code`, `client_id`, the developer's own exact registered `redirect_uri`, `scope`, a fresh unguessable `state`, `code_challenge`, and `code_challenge_method=S256`.

After approval, Figment redirects the browser back to the registered callback URL with `code` and `state` as query parameters. Validate `state` before proceeding — a missing or replayed value means the callback must be discarded. Exact redirect URI matching is required (RFC 9700, Sections 2.1 and 4.1.3); pattern matching is not accepted. The developer registers their own exact HTTPS callback path; `/apple-connect/callback` is Figment's own first-party sandbox URL.

The token exchange (`POST /api/apple-connect/oauth/token` with `grant_type=authorization_code`) returns an opaque **access token** that expires after 900 seconds and an opaque **refresh token** that is a separate, rotating credential. Store the new refresh token returned with each refresh and use `grant_type=refresh_token` per grant. Request only the minimum scopes your integration actually uses.

## Minimum Scope, Maximum Control

RFC 9700 Section 2.3 states that tokens should carry the minimum necessary privileges and be audience-restricted to a specific resource server. Figment enforces this at the grant level. There are exactly two iMessage scopes:

- **`messages:read`** — read conversation history and run message searches
- **`messages:send`** — post to an existing, specifically authorized conversation

History access does not imply send authorization. A conversation appearing in `messages:read` results is not automatically writable; the send grant must explicitly include that exact existing conversation. An app that only needs to surface messages has no reason to hold `messages:send`.

## Security Model at a Glance

The diagram below maps the user connection, per-app scope grants, secrets boundary, and recommended app logging.

```
╔══════════════════════════════════════════════════════════════╗
║  APPLE ACCOUNT CONNECTION  (user-owned)                     ║
║  Sign-in · trusted-device approval · iCloud sync           ║
║                                                             ║
║  Possible states per service:                               ║
║    connecting · needs_approval · syncing · ready           ║
║    offline · reconnect_required · revoked                  ║
╠══════════════════════════════════════════════════════════════╣
║  APP GRANTS LAYER                                           ║
║                                                             ║
║  ┌──────────────────────┐  ┌──────────────────────────────┐ ║
║  │ App grant A          │  │ App grant B                  │ ║
║  │ messages:read        │  │ messages:read                │ ║
║  │                      │  │ messages:send                │ ║
║  └──────────────────────┘  └──────────────────────────────┘ ║
║                                                             ║
║  Revoke grant A  ────────── only grant A stops             ║
║  Disconnect Apple ─── all grants + queued operations stop  ║
╠══════════════════════════════════════════════════════════════╣
║  SECRETS BOUNDARY                                           ║
║  Developer app receives:        scoped bearer token         ║
║  Developer app NEVER receives:  Apple password              ║
╠══════════════════════════════════════════════════════════════╣
║  AUDIT LAYER  (integration design)                          ║
║  Recommend logging:   service · action · outcome            ║
║  Recommend omitting:  passwords · codes · message bodies    ║
╚══════════════════════════════════════════════════════════════╝
```

## Secrets That Never Cross the Line

The developer app never receives your Apple password. What it receives is a scoped bearer token tied to its specific grant — an opaque, short-lived credential. To revoke an app's access, remove its grant at [figment.so/apple-connect/account](https://figment.so/apple-connect/account). There is no public single-token revocation UI; one access token expiring is not grant revocation.

Apple's own app-specific password model illustrates the same principle at the platform level: third-party apps can access iCloud Mail, Calendar, and Contacts without ever seeing the primary Apple Account password. Up to 25 active app-specific passwords can coexist, each revocable independently; changing the main account password revokes all of them automatically ([Apple: Use app-specific passwords](https://support.apple.com/en-us/102654)).

RFC 9700 Section 2.4 explains why credential-forwarding approaches are off the table: "The resource owner password credentials grant MUST NOT be used." A consent-screen flow — where credentials stay at the authorization server and the client receives only a scoped token — is precisely the architecture RFC 9700 recommends.

## Connection Status Is Per Service

A connected Apple Account is not automatically a connected service. Each connector tracks its own state independently. Use the status endpoint when you need the current picture:

```
GET https://figment.so/api/apple-connect/v1/connections/con_...
Authorization: Bearer ACCESS_TOKEN
```

The response shape is `{"connection":{"id":"con_...","mode":"live","services":{...}}}`, where each service entry in `services` is an object with `state`, `reason`, and `observedAt`; only granted services appear. A connector is not ready merely because the account is signed in — it must have cleared its own approval and sync steps. A `ready` state is a setup indicator, not a guarantee of current send permission or successful delivery. Non-ready services reject operations. The endpoint enforces authorization; non-2xx responses can indicate a non-ready or revoked connection. Handle errors in your request path. Use [figment.so/apple-connect/account](https://figment.so/apple-connect/account) for connection setup and status.

## Idempotency and Send Safety

Each outbound message request must carry an `Idempotency-Key` header. Generate one stable key per intended action **before** the first request; reuse it only for that exact action and identical body:

```
POST .../messages/send
Idempotency-Key: YOUR_STABLE_KEY
```

A body hash alone is not a sufficient key: two separately intended identical messages must carry distinct keys. Retrieve the outcome with the same key on the receipt endpoint. Receipts distinguish `submitted`, `delivered`, `read`, `failed`, `unknown`, and `simulated` (sandbox only). An `unknown` outcome means poll the same receipt again under the **same key** — never resend under a new key. A `failed` receipt is the recorded final state; repeating the same key returns that failure, it does not retry.

## Revoke One App or Revoke Everything

Revoking a single app grant stops that app's access. The underlying Apple Account connection stays active and other grants are unaffected; service availability for other apps is separate from grant state.

Disconnecting the Apple Account connection is a broader operation: it stops every grant and every queued operation tied to that connection. Use it when you need a clean break — for example, when offboarding a user or responding to unauthorized activity.

For services using Apple's native third-party authorization, users can manage access at [account.apple.com](https://account.apple.com) under Sign-In and Security → Account Data Sharing ([Apple: Authorize a third-party app to access iCloud](https://support.apple.com/en-us/121539)). Manage Figment grants at [figment.so/apple-connect/account](https://figment.so/apple-connect/account).

## Minimal Audit Logging (Integration Design)

For sensitive integrations, structure your application's audit records to capture service, action, and outcome — and to deliberately exclude passwords, verification codes, and message bodies. This is an integration design practice, not a description of a Figment audit API or a universal storage guarantee. The goal is for your own logging layer to record what happened without retaining the content of what was sent or received.

---

**Pricing.** The developer pays $29 USD/month per active live connection. Sandbox access is free and includes synthetic data. The first hosted checkout covers one live connection. Access starts after setup.

[**Connect Apple Account**](https://figment.so/apple-connect/developers) to issue your first scoped grant. Register your application and manage your developer profile at [figment.so/apple-connect/developers](https://figment.so/apple-connect/developers).
