# Quickstart

> Create an app, add the iOS SDK, and send a push to your own iPhone.

URL: https://docs.xulo.io/quickstart
iOS SDK version: 0.1.4

This guide takes a new app to its first push. You need a Xulo account, an Apple developer account, Xcode 16 or later, and a physical iPhone on iOS 15 or later.


### Create the app

Sign in at [xulo.io](https://xulo.io/dashboard) and choose **New app**. Enter the app name, pick the organization, and choose Apple iOS. On the next screen, enter the app's bundle id and choose **Create app**.

The app gets an App ID. It is a UUID and it never changes. See [Apps and keys](https://docs.xulo.io/concepts/apps-and-keys).

### Add your Apple key

Open **Settings → Platforms** for the app. Upload a `.p8` auth key with its Key ID and Team ID, or a `.p12` certificate with its password. Without an Apple key, a push stays a draft and is not sent.

[APNs setup](https://docs.xulo.io/ios/apns-setup) walks through both.

### Copy the App ID

Open **Settings → Keys & IDs** and copy the App ID. It goes in the app. It is not a secret and it cannot send.

### Add the package

In Xcode, choose **File → Add Package Dependencies** and enter:

```text
https://github.com/XuloAI/xulo-ios-sdk
```

Use version 0.1.4, up to the next major version. Add the `Xulo` product to the app target. Text push works with the app target alone. [Install](https://docs.xulo.io/ios/install) covers the extensions for images.

Turn on the **Push Notifications** capability for the app target.

### Start the SDK

Call `Xulo.start` when the app launches, pass Apple's device token through, and ask for permission.

```swift
import UIKit
import Xulo

final class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
    ) -> Bool {
        Xulo.logLevel = .verbose
        Xulo.start(appId: "YOUR_APP_ID")
        Xulo.requestPushPermission { granted in
            print("Push allowed: \(granted)")
        }
        return true
    }

    func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        Xulo.setDeviceToken(deviceToken)
    }

    func application(
        _ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error
    ) {
        Xulo.didFailToRegister()
    }
}
```

`Xulo.logLevel = .verbose` prints the SDK trace in Xcode. Leave it off in a live build. In SwiftUI, attach this delegate with `@UIApplicationDelegateAdaptor`. See [Initialize](https://docs.xulo.io/ios/initialize).

### Make your phone a test device

Run the app on your iPhone and allow notifications. Print `Xulo.subscriptionId` to see this install's Subscription ID.

In the dashboard, open **People**, find the install by its Subscription ID, open it, and choose **Add to test devices**.

### Send a push

In the dashboard, open **Push → New push** and write a message. Under **Audience**, choose **Test devices**, tick your phone and choose **Send to selected**. You stay on the page, and no draft is saved.

Or send from a terminal with a REST key from **Settings → Keys & IDs**:

```bash
curl https://api.xulo.io/api/v1/notifications \
  -H "Authorization: Key $XULO_REST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "app_id": "YOUR_APP_ID",
    "contents": {"en": "Hello from Xulo"},
    "included_segments": ["Test devices"]
  }'
```


> **Note:** A build from Xcode
>
> A build signed with a development profile gets a sandbox token. From SDK 0.1.4 the install reports that, and Xulo sends it through Apple's sandbox gate. If nothing arrives, read [Troubleshooting](https://docs.xulo.io/ios/troubleshooting#nothing-arrives-on-a-build-from-xcode).

## Next

- [Rich push](https://docs.xulo.io/ios/rich-push) adds images and the long-press view.
- [Deep links](https://docs.xulo.io/ios/deep-links) opens the right screen when a push is tapped.
- [Send notification](https://docs.xulo.io/api/send-notification) lists every field of the REST call.
