# Project readiness

A single GET returns whether a project can operate, how far its setup has come and which steps are still open — recomputed on every call, with stable step keys and no cached completion flag.

**Language:** en
**Audience:** developer
**TLDR:** GET /v1/project/{project}/readiness returns the project-level setup report: percent, operational, and an ordered steps[] where each step carries a stable key plus completed, isBlocker, disabled, skipped and skippable. operational is true when no blocker step is incomplete. The report is recomputed on every call — a completed step re-opens if its condition regresses — and the home scope always returns warnings: [] and reach: null. What has broken rather than what is missing is a separate endpoint, GET /project/{project}/attention.
**Search keywords:** readiness, setup, onboarding, checklist, is the project ready, can I send, operational, blocker, percent complete, setup progress, what's left to configure, steps, step key, legal profile, funds, balance, import contacts, project type, api_sms, standard project, pre-flight check, skipped step
**Related pages:** /developers/product-api/project-setup/channel-readiness, /developers/product-api/project-setup/attention, /developers/product-api/quickstart
**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/project-setup/readiness/ (HTML) · https://docs.instasent.com/developers/product-api/project-setup/readiness.md (Markdown)

Before a project can send anything it has to hold a few things: contacts, a sender, a legal profile, and available balance. The readiness endpoints answer that in one call, so an onboarding screen, an installer or a pre-flight check does not have to ask five different endpoints and infer the answer.

There are two scopes. This page covers the **project (home)** report — the setup of the project as a whole. The [channel report](/developers/product-api/project-setup/channel-readiness) narrows the same shape to SMS or RCS and adds the countries that channel can reach.

> **Note**: Readiness answers **what is left to configure**. Its counterpart, [attention](/developers/product-api/project-setup/attention), answers **what has broken, or is about to**. One fact has one owner and they never report the same thing twice: absence is always readiness, so a project that just finished signup has a full checklist and no alerts at all.

[`GET /v1/project/{project}/readiness` - The project-level setup report: what is done, what is left, and whether the project can operate.](/developers/product-api/reference)

Any Product API token with access to the project can read it; no extra scope is required.

## It is a live report, not a saved state

There is no "onboarding finished" flag behind this. Every call recomputes the report from the project's current data, which has one consequence worth designing for: **a completed step re-opens when its condition regresses.** A project that spends its balance sees `funds` go back to `completed: false`, and `operational` follows it. Treat the response as the current answer, never as a milestone you can persist and stop asking about.

The report carries `computedAt` for exactly that reason. It is always "now" at the moment of the response — but a client that fetches once and renders for the next ten minutes holds an answer that is ten minutes old, and needs to know it.

## The report

```bash
curl "https://api.instasent.com/v1/project/$INSTASENT_PROJECT/readiness" \
  -H "Authorization: Bearer $INSTASENT_TOKEN"
```

```json
{
  "entity": {
    "scope": "project",
    "projectType": "standard",
    "channel": null,
    "percent": 29,
    "operational": false,
    "steps": [
      { "key": "import", "completed": true, "isBlocker": true, "disabled": false, "skipped": false, "skippable": true },
      { "key": "channels", "completed": true, "isBlocker": true, "disabled": false, "skipped": false, "skippable": true },
      { "key": "legal-profile", "completed": false, "isBlocker": true, "disabled": false, "skipped": false, "skippable": true },
      { "key": "funds", "completed": false, "isBlocker": true, "disabled": false, "skipped": false, "skippable": true },
      { "key": "campaign", "completed": false, "isBlocker": false, "disabled": false, "skipped": false, "skippable": true },
      { "key": "automation", "completed": false, "isBlocker": false, "disabled": false, "skipped": false, "skippable": true },
      { "key": "brand", "completed": false, "isBlocker": false, "disabled": false, "skipped": false, "skippable": true }
    ],
    "warnings": [],
    "reach": null,
    "computedAt": "2026-09-04T16:20:31Z"
  }
}
```

- `scope` — `string`
  `project` here, `channel` on the channel report. Both scopes return the same object, so a client can render either with one renderer.
- `projectType` — `string`
  `standard` or `api_sms`. It decides which steps the project gets — see [the step catalogue](#the-step-catalogue).
- `channel` — `string | null`
  Always `null` on this scope.
- `percent` — `integer`
  Completed steps over total steps, `0`–`100`. `100` when there are no steps at all.
- `operational` — `boolean`
  `true` when no blocker step is left incomplete — the project can work. Non-blocker steps do not hold it back, so a project can be `operational` at 60%.
- `steps` — `object[]`
  The setup milestones, in display order. Order is significant: render them as they arrive.
- `warnings` — `object[]`
  Always `[]` on this scope. The one warning that exists — `recommended-registrations` — is per-channel and lives on the [channel report](/developers/product-api/project-setup/channel-readiness#warnings). What has actually broken is a different endpoint: [attention](/developers/product-api/project-setup/attention).
- `reach` — `object | null`
  Always `null` on this scope. Send reach is a property of a channel, not of a project.
- `computedAt` — `string`
  When the backend built this report, UTC ISO-8601.

## A step

- `key` — `string`
  Stable identifier, kebab-case. **The API ships no copy**: the key is the contract and the label is yours to write and translate.
- `completed` — `boolean`
  Whether the condition behind the step holds right now.
- `isBlocker` — `boolean`
  When `true`, the project cannot operate until the step is completed. The incomplete blockers are exactly what `operational: false` is telling you about.
- `disabled` — `boolean`
  When `true`, a prerequisite is missing and the user cannot act on the step yet — a campaign cannot be created before there are contacts. Render it, but don't offer the action.
- `skipped` — `boolean`
  Whether the user hid this step from the dashboard checklist. **A preference, not a fact**: the backend reports it and honours none of it, so `completed`, `isBlocker`, `disabled`, `percent` and `operational` are all computed as if it were `false`. Each client decides what to do with it — hide the row, grey it out, or ignore the field entirely.
  
  Unlike a dismissed [attention item](/developers/product-api/project-setup/attention#the-dismiss-field), a skip is stored on the platform and shared by everyone working on the project.
- `skippable` — `boolean`
  Whether a user may hide this step at all. `true` for every step today; it exists so that the day one becomes mandatory, the decision is declared once here and every surface obeys it.
  
  It outranks a stored skip: on a step that is not `skippable`, `skipped` is always `false` even if a preference was saved before it stopped being skippable. Nothing ever claims "you hid this" about something that can no longer be hidden, so there is no stale preference for you to clean up.

## The step catalogue

The step set depends on `projectType`, and `funds` only appears where the account needs available balance. Both lists are ordered as returned.

**`standard`** — a project operated from the dashboard:

| Key             | Blocker                       | Completed when                                                                                      |
| --------------- | ----------------------------- | --------------------------------------------------------------------------------------------------- |
| `import`        | yes                           | The project has at least one data source.                                                           |
| `channels`      | only while there is no sender | There is an active sender **and** no channel the customer asked for at signup is still without one. |
| `legal-profile` | yes                           | The organization has filled in the legal profile that sender registrations are built from.          |
| `funds`         | yes                           | The available balance is above the minimum.                                                         |
| `campaign`      | no                            | At least one campaign exists. Disabled until `import` is done.                                      |
| `automation`    | no                            | At least one automation exists. Disabled until `import` is done.                                    |
| `brand`         | no                            | The project has a brand with its basic details filled in.                                           |

**`api_sms`** — a project used as a sending API rather than from the panel:

| Key            | Blocker                       | Completed when                                                                           |
| -------------- | ----------------------------- | ---------------------------------------------------------------------------------------- |
| `token`        | yes                           | The organization has an API SMS token.                                                   |
| `channels`     | only while there is no sender | Same condition as above.                                                                 |
| `funds`        | yes                           | The available balance is above the minimum.                                              |
| `send-message` | no                            | A message went out on SMS or RCS in the last 30 days. Disabled while there is no sender. |

Two of these deserve a note:

- **`channels` is stricter to complete than to unblock.** One active sender means the project *can* send, so the step stops being a blocker there. But if the customer asked for RCS at signup and only SMS is up, the step stays incomplete — the percentage reflects the work left without declaring a working project inoperative. Read `isBlocker` from the response rather than assuming it.
- **`send-message` is a rolling window on purpose.** It re-opens after 30 days without traffic, so the checklist doubles as a drift cue on an integration that has gone quiet.

## Using it

Two integrations cover most cases:

#### 1. Drive a setup checklist

Render `steps` in order, map each `key` to your own copy, and use `percent` for the progress bar. Poll it after the user acts — the report is the only thing that decides a step is done.

#### 2. Pre-flight a send

Read `operational` before you start pushing traffic. When it is `false`, the incomplete `isBlocker` steps are the reason, and they name what to fix.

Readiness answers whether the project *can* send. Whether there is anyone to send *to* is a different question — [count your audience](/developers/product-api/audience/counting-and-retrieval#counting) for that.

---

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.
