# API reference

> The Rankfinity REST API at /api/v1. How to create a key, authenticate, read your projects and rankings, and start paid runs safely with a credit price check.

## What the API is

The API gives you the same data as the app, in your own tools. It is a small, versioned REST API under `https://app.rankfinity.app/api/v1`.

- **Reads** return what you see in the app: projects, keyword positions, rank history, AI Visibility summary, site audit summary, backlink summary, and your credit balance.
- **Writes** add keywords for free, and start two paid actions: a rank check and an AI Visibility run.
- **Money is credits.** The API never returns dollars and never names the data suppliers.

The machine-readable description (OpenAPI 3) is at `/api/v1/openapi.json`. It lists only the `/api/v1` endpoints.

## Create a key

1. Open **My account › API & feeds**.
2. Enter a name (for example "Looker dashboard") and choose the scope.
3. Click **Create key** and copy it. **The key is shown once.** Rankfinity stores only a hash, so a lost key cannot be shown again. Create a new one and revoke the old one.

| Scope | What it allows |
|---|---|
| `read` | Every GET endpoint. Every key has it. |
| `write` | Adding keywords, and starting rank checks and AI Visibility runs. |

Client accounts can create read keys only. Changes are made by the agency. You can have up to 10 active keys. Revoking a key takes effect immediately.

## Authenticate

Send the key as a bearer token on every request:

```
Authorization: Bearer rk_...
```

A missing, malformed, revoked or expired key returns `401`. A key that is valid but sent too often returns `429` with a `Retry-After` header.

## Rate limit

Each key has its own limit per minute (the operator sets it; the default is 60). Requests above it return `429`. Wait for `Retry-After` seconds and try again.

## Endpoints

Replace `{pid}` with the project id from `GET /api/v1/projects`.

| Method | Path | Scope | What it returns |
|---|---|---|---|
| GET | `/api/v1/credits` | read | Balance, plan, plan credits left, top-up credits. |
| GET | `/api/v1/projects` | read | Your projects (id, name, domain, keyword count, last run). |
| GET | `/api/v1/projects/{pid}/keywords?device=desktop&limit=50` | read | Latest position per keyword and device. |
| GET | `/api/v1/projects/{pid}/rank-history?keyword=...&device=...` | read | Position history for one keyword, oldest first. |
| GET | `/api/v1/projects/{pid}/aiv/summary` | read | AI Visibility from the latest finished run. |
| GET | `/api/v1/projects/{pid}/site-audit/summary` | read | Latest site audit: score, pages by status, failed checks. |
| GET | `/api/v1/projects/{pid}/backlinks/summary` | read | The saved backlink profile of the project's domain. |
| POST | `/api/v1/projects/{pid}/keywords` | write | Adds keywords. Free. Body: `{"keywords": ["..."]}`. |
| GET | `/api/v1/projects/{pid}/rank-checks/estimate` | read | The price of a rank check now. Spends nothing. |
| POST | `/api/v1/projects/{pid}/rank-checks` | write | Starts a rank check. Paid. See below. |
| GET | `/api/v1/projects/{pid}/aiv-runs/estimate` | read | The price of an AI Visibility run now. Spends nothing. |
| POST | `/api/v1/projects/{pid}/aiv-runs` | write | Starts an AI Visibility run. Paid. See below. |

A project that is not yours returns `404`, the same as a project that does not exist.

### Empty is not zero

A position with `"rank": null` and `"measured": false` means the check did not finish, not that the keyword is out of the top results. `"measured": true` with a `null` rank means the site was not found in the depth that was searched. A backlink or audit summary with `"available": false` means that step has not run yet, and the response says why.

## Paid calls: check the price first

Paid writes take `expected_credits`. That number must be the price you saw in the matching `estimate` call.

1. `GET /api/v1/projects/{pid}/rank-checks/estimate` returns `"credits": 42`.
2. `POST /api/v1/projects/{pid}/rank-checks` with body `{"expected_credits": 42}` starts the check and charges 42 credits.

If the price has changed in the meantime, nothing is charged and the call returns `409` with the new price. Check it and send the request again. If `expected_credits` is missing, the call returns `422`. If the balance is too low, it returns `402` with the amount needed.

## Example

```bash
KEY="rk_..."
curl -s -H "Authorization: Bearer $KEY" \
  https://app.rankfinity.app/api/v1/projects/12/keywords?limit=20

curl -s -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"expected_credits": 42}' \
  https://app.rankfinity.app/api/v1/projects/12/rank-checks
```

## Errors

| Code | Meaning |
|---|---|
| 400 | The request is not valid. The message says what to change. |
| 401 | The key is missing, unknown, revoked, or its owner is inactive. |
| 402 | Not enough credits. The body shows the amount needed. |
| 403 | The key is read-only for a write call, or the account is a client account. |
| 404 | The project is not found for this account. |
| 409 | The price changed. Nothing was charged. |
| 422 | A required field is missing (for example `expected_credits`). |
| 429 | Too many requests for this key. Wait for `Retry-After`. |
| 503 | The server could not finish. Try again in a few minutes. |

## Google Sheets and Looker Studio

For spreadsheets and dashboards that refresh by themselves, use the CSV feeds instead of the API. See **Feeds for Sheets and Looker Studio**.

