Overview
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.
| GET | /ping | Check your key |
| GET | /account | Read your account |
| GET | /openapi.json | Download this reference |
| 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 |
| 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 |
| GET | /projects/{project_id}/badge | Get the badge install kit |
| POST | /projects/{project_id}/badge/verify | Verify the badge |
| POST | /projects/{project_id}/reports | Build a report |
| GET | /reports/{report_id} | Read a report |
| GET | /reports/{report_id}/file | Download a report file |
| GET | /events | List events |
| GET | /events/{event_id} | Read an event |
| 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 |
| GET | /directories | List directories |
| GET | /directories/{directory_id} | Read a directory |
| POST | /test_helpers/listings/{listing_id}/advance | Move a test listing forward |
| POST | /test_helpers/account/launches | Set your test launches |
| 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 |
| 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:
{ "object": "project", "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP", "livemode": true }A list wraps its items in the same envelope every time:
{
"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:
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.
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 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 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:
{
"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 explains each field, and Error codes 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.
curl https://api.submitator.com/v1/openapi.json -o submitator-v1.json