# Overview

URL: https://submitator.com/docs/api

> Every route of the v1 API on one screen, and the rules all of them share.

The base URL is `https://api.submitator.com/v1`. Every route below takes your key in `Authorization: Bearer $SUBMITATOR_API_KEY`, except `GET /openapi.json`.

## Routes

Each path links to its full reference: parameters, the request and its responses, and the errors it returns.

**Basics**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/ping` | Check your key |
| GET | `/account` | Read your account |
| GET | `/openapi.json` | Download this reference |

**Projects**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/projects` | List projects |
| POST | `/projects` | Create a project |
| GET | `/projects/{project_id}` | Read a project |
| PATCH | `/projects/{project_id}` | Update a project |
| DELETE | `/projects/{project_id}` | Delete a draft |
| PUT | `/projects/{project_id}/logo` | Set the logo |
| DELETE | `/projects/{project_id}/logo` | Remove the logo |
| POST | `/projects/{project_id}/screenshots` | Add a screenshot |
| DELETE | `/projects/{project_id}/screenshots/{image_id}` | Remove a screenshot |

**Launch**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/projects/{project_id}/readiness` | Preview a launch |
| POST | `/projects/{project_id}/launch` | Launch a project |
| GET | `/projects/{project_id}/listings` | List a project's listings |
| POST | `/projects/{project_id}/listings` | Add directories |
| GET | `/listings/{listing_id}` | Read a listing |

**Badge**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/projects/{project_id}/badge` | Get the badge install kit |
| POST | `/projects/{project_id}/badge/verify` | Verify the badge |

**Reports**

| Method | Path | What it does |
| --- | --- | --- |
| POST | `/projects/{project_id}/reports` | Build a report |
| GET | `/reports/{report_id}` | Read a report |
| GET | `/reports/{report_id}/file` | Download a report file |

**Events**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/events` | List events |
| GET | `/events/{event_id}` | Read an event |

**Webhooks**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/webhook_endpoints` | List webhook endpoints |
| POST | `/webhook_endpoints` | Add a webhook endpoint |
| GET | `/webhook_endpoints/{webhook_endpoint_id}` | Read a webhook endpoint |
| PATCH | `/webhook_endpoints/{webhook_endpoint_id}` | Update a webhook endpoint |
| DELETE | `/webhook_endpoints/{webhook_endpoint_id}` | Delete a webhook endpoint |
| POST | `/webhook_endpoints/{webhook_endpoint_id}/rotate_secret` | Roll the signing secret |
| POST | `/webhook_endpoints/{webhook_endpoint_id}/test` | Send a test event |
| GET | `/webhook_endpoints/{webhook_endpoint_id}/deliveries` | List deliveries |
| POST | `/webhook_deliveries/{webhook_delivery_id}/retry` | Retry a delivery |

**Directories**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/directories` | List directories |
| GET | `/directories/{directory_id}` | Read a directory |

**Test mode**

| Method | Path | What it does |
| --- | --- | --- |
| POST | `/test_helpers/listings/{listing_id}/advance` | Move a test listing forward |
| POST | `/test_helpers/account/launches` | Set your test launches |

**Share links**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/projects/{project_id}/share_link` | Read the share link |
| POST | `/projects/{project_id}/share_link` | Create a share link |
| DELETE | `/projects/{project_id}/share_link` | Turn the share link off |

**Logins**

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/projects/{project_id}/logins` | List a project's logins |
| GET | `/listings/{listing_id}/emails` | List the emails a login received |

## Requests

Send JSON with `Content-Type: application/json`. Image uploads also take `multipart/form-data` with a `file` field; any other body type returns 415 `unsupported_media_type`.

A parameter the operation does not list returns 400 `unknown_parameter` and changes nothing. An empty string counts as `null`.

## Objects and lists

Every object names its type in `object` and its mode in `livemode`:

```json
{ "object": "project", "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP", "livemode": true }
```

A list wraps its items in the same envelope every time:

```json
{
  "object": "list",
  "data": [{ "object": "listing", "id": "lst_7Hk2Qn4Rt9Vx1Bm6Cz3LdK" }],
  "has_more": true,
  "next_cursor": "cur_8Tq2Xn5Wb1Lz7Pd3"
}
```

## Ids

An id is a type prefix and 22 letters and digits, such as `prj_4QzX1m9Lr2Vb7Nc8Tk3HwP`. Treat ids as opaque strings: compare them whole and store them as text.

| Prefix | Object |
| --- | --- |
| `prj_` | Project |
| `lst_` | Listing |
| `dir_` | Directory |
| `img_` | Image: a logo or a screenshot |
| `rep_` | Report |
| `evt_` | Event |
| `we_` | Webhook endpoint |
| `wd_` | Webhook delivery |
| `key_` | API key, as `GET /ping` shows it |

Keep your own reference in `external_id`: up to 100 characters, unique in your account, and searchable with `GET /projects?external_id=crm_8812`. `metadata` holds up to 20 more keys of your own.

## Pagination

Lists return up to `limit` items: 1 to 100, and 20 when you leave it out. When `has_more` is `true`, send `next_cursor` back as `cursor` to read the next page:

```bash
curl "https://api.submitator.com/v1/projects?limit=50&cursor=cur_8Tq2Xn5Wb1Lz7Pd3" \
  -H "Authorization: Bearer $SUBMITATOR_API_KEY"
```

Cursors are opaque and belong to the list that issued them: a cursor from another list returns 400 `invalid_cursor`. The events feed always returns a `next_cursor`, so you can poll it for what is new.

## Retry a POST safely

Send an `Idempotency-Key` header, such as a UUID, with a `POST` you may send twice: creating a project, launching, adding directories, building a report or adding a webhook endpoint. A repeat with the same key and the same body within 24 hours returns the first response again, with `Idempotent-Replayed: true`, and changes nothing twice.

```bash
curl https://api.submitator.com/v1/projects \
  -H "Authorization: Bearer $SUBMITATOR_API_KEY" \
  -H "Idempotency-Key: 4f9d2c1e-ledgerly-create" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://ledgerly.test", "contact_email": "founder@ledgerly.test"}'
```

The same key with another body returns 422 `idempotency_key_reused`. A repeat while the first request still runs returns 409 `idempotency_key_in_use`: wait 1 second and send it again.

## Test mode

A `sbm_test_` key runs every route below on test data, where listings move on their own and nothing is spent. The routes under `/test_helpers` work only there and return 403 `test_mode_only` with a live key. [Test mode](https://submitator.com/docs/test-mode) says what is simulated.

## Rate limits

Each account and mode can make 3,600 requests per UTC hour, or 600 before your first purchase. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`, and [Rate limits](https://submitator.com/docs/rate-limits) lists the operations with limits of their own.

## Request ids

Every response carries an `X-Request-Id` header, and error bodies repeat it as `error.request_id`. Log it with each call and quote it when you write to support.

## Time

Times are ISO 8601 in UTC with a `Z`, such as `2026-10-08T14:00:00Z`. Listing times are rounded down to the hour, and an event's `created_at` is exact to the second.

Dates without a time, such as `launch_date`, are `YYYY-MM-DD`.

## Errors

Every 4xx and 5xx response has the same body, with a stable `code` to branch on:

```json
{
  "error": {
    "code": "not_found",
    "message": "No project prj_4QzX1m9Lr2Vb7Nc8Tk3HwQ in live mode. Check the id and the key's mode.",
    "param": "project_id",
    "doc_url": "https://submitator.com/docs/errors#not_found",
    "request_id": "0133b7c4-6a56-4793-8ada-5f7c570eefa3"
  }
}
```

[Error format](https://submitator.com/docs/error-format) explains each field, and [Error codes](https://submitator.com/docs/errors) lists every code.

## The OpenAPI document

`GET /openapi.json` returns this reference as an OpenAPI 3.1 document with the released operations. It needs no key and can be cached for 1 hour: generate a client from it, or load it into your API tool.

```bash
curl https://api.submitator.com/v1/openapi.json -o submitator-v1.json
```
