# Product API quickstart

Find a contact in the unified audience, read their recent events and send a direct SMS — end to end in under five minutes.

**Language:** en
**Audience:** developer
**TLDR:** Authenticate with a Product API token as a Bearer header against https://api.instasent.com/v1/project/{project}, scoped to PROJECT_AUDIENCE_READ, PROJECT_AUDIENCE_DATA_BASIC/EVENTS and PROJECT_DIRECT_WRITE. Verify it with GET /v1/project/{project} (200 = live token), then resolve a contact via GET /audience/search/phone/{phone} (or /search/email/{email}, /user/{userId}) to get the audience id used for events and direct-send calls.
**Docs index (every page):** https://docs.instasent.com/llms.txt
**This zone's index:** https://docs.instasent.com/developers/product-api/llms-full.txt
**This page:** https://docs.instasent.com/developers/product-api/quickstart/ (HTML) · https://docs.instasent.com/developers/product-api/quickstart.md (Markdown)

This walkthrough exercises the three things every Product API integration eventually does: locate a contact, inspect their timeline, and trigger a direct message. Budget five minutes.

## Before you start

#### 1. Have a project with data

Sign in to the [dashboard](https://dashboard.instasent.com) and pick a project that already has at least one audience contact (either pushed via Ingest or imported via a data source). If your project is empty, walk through the [Ingest Quickstart](/developers/ingest-api/quickstart) first.

#### 2. Create a Product API token

Open **Project settings** → **API tokens** and create a token with at least these scopes:

- `PROJECT_AUDIENCE_READ`
- `PROJECT_AUDIENCE_DATA_BASIC` (to see phone and email on responses)
- `PROJECT_AUDIENCE_DATA_EVENTS` (to read events)
- `PROJECT_DIRECT_WRITE` (to send direct SMS)

#### 3. Export credentials

```bash
export INSTASENT_PROJECT="proj_xxx"
export INSTASENT_TOKEN="eyJhbGciOi..."
export BASE="https://api.instasent.com/v1/project/$INSTASENT_PROJECT"
```

## 1. Verify the token

`GET /v1/project/{project}` returns the project's metadata and is the cheapest probe for credentials.

```bash
curl "$BASE" \
  -H "Authorization: Bearer $INSTASENT_TOKEN"
```

A `200` confirms the token is live and the project UID resolves. A `401` means the token is wrong; a `404` means the project UID is wrong or not visible to the token.

## 2. Find an audience contact

The audience exposes three search helpers for the common identifier types — user id, phone and email. They return the unified audience contact so you can then read events or send messages.

```bash
curl "$BASE/audience/search/phone/%2B34600000000" \
  -H "Authorization: Bearer $INSTASENT_TOKEN"
```

Response (trimmed):

```json
{
  "entity": {
    "id": "uQTuHNBKLdwTxzGldW5pocUNqzyz-066",
    "_user_id": "USER-123",
    "_first_name": "Ada",
    "_email": "ada@example.com",
    "_phone_mobile": "+34600000000"
  }
}
```

Copy the returned `id` — that is the **audience contact id** you need for the next two calls.

```bash
export AUDIENCE_ID="uQTuHNBKLdwTxzGldW5pocUNqzyz-066"
```

> **Tip**: Phone numbers in the path must be URL-encoded (`+` → `%2B`). If you store user ids instead, use `/audience/user/{userId}`; for email use `/audience/search/email/{userEmail}`.

## 3. Read the contact's recent events

```bash
curl "$BASE/audience/$AUDIENCE_ID/events" \
  -H "Authorization: Bearer $INSTASENT_TOKEN"
```

You get the last events for that contact — purchases, message deliveries, clicks, custom events. Use this to confirm your Ingest pipeline is landing and to drive application logic that depends on the customer timeline.

## 4. Send a direct SMS

The direct SMS endpoint takes the sender and audience id in the path and the text in the body. Use `"default"` as the sender id to fall back to the project's default sender.

```bash
curl -X POST "$BASE/channel/sms/sms/direct/default/$AUDIENCE_ID" \
  -H "Authorization: Bearer $INSTASENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Hi Ada — your order is on its way. Track it at {{short:https://track.example.com/O-987}}"
  }'
```

A `201 Created` returns the SMS entity with its `id`, `status` (`enqueued` initially), `encoding`, `messagesCount`, `pricePerSms` and the `audienceId` the message was dispatched to. Status transitions are pushed to your DLR webhook the same way transactional traffic is — see [Transactional DLRs](/developers/transactional-api/http/dlrs).

> **Warning**: The direct SMS endpoint supports `{{short:URL}}` for automatic link-shortening and `{{unsubscribe}}` for an opt-out link. It is designed for individual triggered messages, not bulk campaigns — those belong in a campaign or automation.

## 5. (Optional) send by phone instead of audience id

If the contact does not yet exist and your project has **outbound auto-creation** enabled, you can send straight to a phone number and let the API create the audience contact for you:

```bash
curl -X POST "$BASE/channel/sms/sms/direct/default/%2B34600000000" \
  -H "Authorization: Bearer $INSTASENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "text": "Welcome to Example Co." }'
```

The response's `metadata.autoCreated` is `true` when a new audience contact was created for the message. If auto-creation is disabled and the phone number is not found, the call returns `404`.

## 6. Check what's left to configure

Two read-only endpoints answer "is this project ready to send, and what still needs my attention?" — handy for onboarding screens or a pre-flight check before you start sending.

`GET /v1/project/{project}/readiness` returns the project-wide setup report: an `operational` flag, a `percent` complete, and a list of `steps` (import contacts, set up channels, complete the legal profile, add funds for prepaid projects). Each step carries a stable `key`, plus `completed`, `isBlocker` and `disabled` flags.

```bash
curl "$BASE/readiness" \
  -H "Authorization: Bearer $INSTASENT_TOKEN"
```

Response (trimmed):

```json
{
  "entity": {
    "scope": "project",
    "channel": null,
    "percent": 75,
    "operational": false,
    "steps": [
      { "key": "import", "completed": true, "isBlocker": false, "disabled": false },
      { "key": "channels", "completed": true, "isBlocker": true, "disabled": false },
      { "key": "legal_profile", "completed": false, "isBlocker": true, "disabled": false }
    ],
    "warnings": []
  }
}
```

`GET /v1/project/{project}/readiness/channel/{channel}` narrows the report to a single channel (`sms` or `rcs`; any other value returns `404`). On top of channel-specific steps (such as `add_sender` for SMS or `add_agent` for RCS) it surfaces `warnings` — things you cannot fix by clicking a button, like senders sitting in regulatory review. Each warning has a `key`, `severity`, `count` and `rows[]`, where every row points at the affected register (`senderId`, `senderName`, `country`, `effectiveStatus`).

The channel report also carries a `reach` object — the positive counterpart of `warnings`, answering "which countries can I send to right now on this channel?". It lists `reachableCountries` (the union of ISO 3166-1 alpha-2 codes across the channel's active senders, deduplicated and sorted) and a per-sender `senders` breakdown keyed by sender alias (the `from` for SMS, the agent name for RCS; senders that reach no country are omitted). `reach` uses the accepted carrier status — the same set as each sender's `acceptedCountries` — not the regulation-aware `effectiveStatus`, so it answers "can I send to country X?" in a single call. On the project (home) scope `reach` is `null`.

```bash
curl "$BASE/readiness/channel/sms" \
  -H "Authorization: Bearer $INSTASENT_TOKEN"
```

Response (trimmed):

```json
{
  "entity": {
    "scope": "channel",
    "channel": "sms",
    "steps": [
      { "key": "add_sender", "completed": true, "isBlocker": true, "disabled": false }
    ],
    "warnings": [],
    "reach": {
      "reachableCountries": ["ES", "FR", "PT"],
      "senders": {
        "ACME": ["ES", "PT"],
        "INFO": ["FR"]
      }
    }
  }
}
```

> **Tip**: The report is recomputed on every call — it is a live view of the project, never a cached "done" flag. Poll it to drive setup checklists, or read it once before a send to confirm the channel is `operational`.

## 7. Count your reachable audience

Readiness tells you the channel is set up; a count tells you whether there is anyone to send to. `POST /v1/project/{project}/audience/count` returns only a total, and `filterCompliance` makes that total mean *contacts an SMS campaign under this consent policy would reach* instead of *contacts in the project*.

```bash
curl -X POST "$BASE/audience/count" \
  -H "Authorization: Bearer $INSTASENT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "filterCompliance": { "sms": "opt-out" } }'
```

Response:

```json
{
  "metadata": {
    "totalHits": 452
  }
}
```

Swap the policy for `basic` or `opt-in` to compare what each one reaches, add a `root` to count a segment or a set of conditions, and read the project's own policy from `generalConfig.channelSms.defaultCompliancePolicy` in step 1's response. The full key is in [Counting who can receive](/developers/product-api/audience/query-filter#counting-who-can-receive-filtercompliance).

## What to read next

- [Guide](/developers/product-api/guide) - Mental model, entities and how the unified audience is built.
- [Audience query filter](/developers/product-api/audience/query-filter) - Search and segment the unified audience.
- [Authentication](/developers/product-api/authentication) - Token types, scopes and rotation.
- [Full API Reference](/developers/product-api/reference) - Every endpoint, every parameter.

---

This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context.
