Projects

A project is one of your client's products. Create it from a URL and we fill in what the site tells us; then add a logo, 1 to 5 screenshots and a contact email.

A draft costs nothing and you can delete it. Once launched, a project stays: it cannot be deleted, and its contact_email can no longer change.

List projects

GET
/projects

Returns your projects in the key's mode, newest first. Filter by status, find one by your own external_id, or search names and URLs with q.

Query Parameters

status?string

Only projects in this status.

Value in"draft""in_progress""action_required""on_hold""completed"
external_id?string

Only the project with this external_id. Matches the whole value, case-sensitive.

Lengthlength <= 100
q?string

Only projects whose name or URL contains this text, ignoring case. 2 to 100 characters.

Length2 <= length <= 100
cursor?string

next_cursor from the previous page of the same list, as you received it. Leave it out for the first page.

Lengthlength <= 500
limit?integer

How many items to return, 1 to 100. Defaults to 20.

Range1 <= value <= 100
Default20

Response Body

One page of projects.

application/json
  1. response

One page of a list. Projects, newest first.

object*string

Always list.

data*array<>

The items on this page.

has_more*boolean

true when there is another page.

next_cursor*|

Send it as cursor for the next page. null when has_more is false.

Lengthlength <= 500
curl "https://api.submitator.com/v1/projects" \  -H "Authorization: Bearer $SUBMITATOR_API_KEY"

{  "object": "list",  "data": [    {      "object": "project",      "id": "prj_1Nc7Vd3Kq8Zr5Tx2Wm9LbH",      "livemode": false,      "status": "in_progress",      "hold_reason": null,      "action": null,      "url": "https://quotafy.test",      "name": "Quotafy",      "maker_name": "Tom Okafor",      "contact_email": "sales@quotafy.test",      "tagline": "Quotes your clients sign in one click",      "short_description": "Quotafy turns a price list into quotes that clients accept and sign online, with reminders and a deposit link.",      "long_description": null,      "category": "saas",      "pricing_model": "paid",      "price_amount_cents": 2900,      "price_period": "monthly",      "launch_date": "2026-08-20",      "twitter_url": "https://x.com/quotafy",      "competitors": [        {          "name": "Bidsheet",          "url": "https://bidsheet.test"        }      ],      "tags": [        "quotes",        "proposals",        "e-signature"      ],      "audience": {        "b2b": true,        "ai": false,        "directory": false      },      "promo_code": "QUOTE30",      "promo_description": "30% off the first 3 months",      "logo": {        "object": "image",        "id": "img_1Qa8Kx3Wm7Lt2Vb9Rz5NcE",        "status": "ready",        "url": "https://feed.example.net/a/Qf2Lm8Wx.k5Tp3",        "error": null      },      "screenshots": [        {          "object": "image",          "id": "img_4Qb2Kw9Lx5Mt8Vb3Rz7NdG",          "status": "ready",          "url": "https://feed.example.net/a/Qg9Nt4Vb.z7Kr2",          "error": null        }      ],      "autofill": {        "status": "complete",        "name_source": "input",        "filled": [],        "image_candidates": []      },      "readiness": {        "ready": true,        "missing": []      },      "domain_rating": 12,      "badge": {        "status": "not_checked",        "checked_at": null      },      "auto_replace": true,      "excluded_directories": [        "dir_3Vb8Qm2Xk6Lt9Wr4Nc1ZpG"      ],      "allowance": {        "total": 100,        "used": 72,        "reserved": 20,        "available": 8      },      "progress": {        "total": 92,        "pending": 0,        "in_progress": 20,        "action_required": 0,        "submitted": 33,        "live": 39,        "not_accepted": 0,        "cancelled": 0      },      "external_id": "crm_8907",      "metadata": {},      "created_at": "2026-10-05T09:40:18Z",      "updated_at": "2026-10-08T13:31:52Z",      "launched_at": "2026-10-05T10:12:07Z"    },    {      "object": "project",      "id": "prj_3Ac9Kx4Wq7Lt2Vb8Rz5NmD",      "livemode": false,      "status": "completed",      "hold_reason": null,      "action": null,      "url": "https://acme.test",      "name": "Acme CRM",      "maker_name": "Lena Park",      "contact_email": "hello@acme.test",      "tagline": "The CRM for teams of five",      "short_description": "Acme CRM keeps contacts, deals and follow-ups in one shared list, with a weekly digest for the whole team.",      "long_description": null,      "category": "saas",      "pricing_model": "freemium",      "price_amount_cents": 900,      "price_period": "monthly",      "launch_date": null,      "twitter_url": null,      "competitors": [],      "tags": [        "crm",        "sales"      ],      "audience": {        "b2b": true,        "ai": false,        "directory": false      },      "promo_code": null,      "promo_description": null,      "logo": {        "object": "image",        "id": "img_3Ad7Kx2Wq8Lt4Vb9Rz1NmH",        "status": "ready",        "url": "https://feed.example.net/a/Ac6Rk1Wn.p4Lx8",        "error": null      },      "screenshots": [        {          "object": "image",          "id": "img_0Ae5Kw1Lq9Xt3Vb7Rz2McJ",          "status": "ready",          "url": "https://feed.example.net/a/Ad3Mq7Zt.v9Kc1",          "error": null        }      ],      "autofill": {        "status": "complete",        "name_source": "site",        "filled": [          "name",          "tagline"        ],        "image_candidates": []      },      "readiness": {        "ready": true,        "missing": []      },      "domain_rating": 51,      "badge": {        "status": "installed",        "checked_at": "2026-10-03T15:08:30Z"      },      "auto_replace": false,      "excluded_directories": [],      "allowance": {        "total": 100,        "used": 85,        "reserved": 0,        "available": 15      },      "progress": {        "total": 88,        "pending": 0,        "in_progress": 0,        "action_required": 0,        "submitted": 3,        "live": 82,        "not_accepted": 2,        "cancelled": 1      },      "external_id": "crm_8650",      "metadata": {},      "created_at": "2026-10-03T15:01:44Z",      "updated_at": "2026-10-07T18:22:47Z",      "launched_at": "2026-10-03T15:12:09Z"    }  ],  "has_more": true,  "next_cursor": "cur_Pq7Lx2Wm9Kt4Vb8R"}

Create a project

POST
/projects

Creates a draft for one of your client's products. Only url is required: we read the site and fill in the name, tagline and short description you leave out. Images given as logo_url and screenshot_urls import in the background, so they show importing first. A draft costs nothing.

Header Parameters

Idempotency-Key?string

A unique string, such as a UUID, that makes a retry safe. A success is kept for 24 hours: a repeat with the same key and body returns it again with Idempotent-Replayed: true, and the same key with another body returns 422 idempotency_key_reused. A repeat while the first request runs returns 409 idempotency_key_in_use. An error changes nothing, so a repeat after one runs again.

Length1 <= length <= 255

Request Body

application/json
  1. body

A new project. Only url is required; autofill fills in the name, tagline and short description you leave out. Add contact_email, a logo and at least 1 screenshot before you launch.

url*string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name?string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name?|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email?|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline?|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description?|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description?|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category?|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model?|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents?|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period?|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date?|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url?|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors?array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags?array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience?

Marks that open the directories which accept only some kinds of products.

promo_code?|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description?|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
logo_url?string

A public URL of the logo, imported in the background: PNG, JPEG, GIF or WebP, up to 5 MB. logo.status shows importing until it is done.

Formaturi
Lengthlength <= 2048
screenshot_urls?array<>

1 to 5 public URLs of screenshots, imported in the background in this order. Directories that take a single screenshot use the first.

Items1 <= items <= 5
external_id?|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata?

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
excluded_directories?array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

auto_replace?boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

Response Body

The new draft, as created from the from_a_url request.

application/json
  1. response

One of your client's products, from draft to the last listing.

object*string

Always project.

id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$
livemode*boolean

true for a live project, false for a test project.

status*string

Where the project stands. When more than one fits, the first in this list wins:

  • on_hold: work is paused; hold_reason says why.
  • action_required: you or your client need to act; action says what.
  • in_progress: listings are moving and nobody needs to act.
  • completed: no listing is pending, in_progress or action_required, and auto-replace has nothing to add. Listings in submitted can still go live.
  • draft: not launched yet.

The console and reports show these as Draft, In progress, Needs you, On hold and Completed.

Value in"draft""in_progress""action_required""on_hold""completed"
hold_reason*|

Why work is paused, when status is on_hold: review while we review the project, which clears without action from you, or paused when we paused it. null otherwise.

Value in"review""paused"null
action*|

What to do, when status is action_required. null otherwise.

url*string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name*string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name*|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email*|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline*|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description*|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description*|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category*|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model*|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents*|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period*|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date*|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url*|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors*array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags*array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience*

Marks that open the directories which accept only some kinds of products.

promo_code*|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description*|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
logo*|

The logo, or null when the project has none.

screenshots*array<>

0 to 5 screenshots, in the order directories use them.

Itemsitems <= 5
autofill*

What we read from the client's site to fill in the project.

readiness*

Whether the project can launch. GET /projects/{project_id}/readiness shows the full preview.

domain_rating*|

The client's site's Domain Rating from Ahrefs, 0 to 100. null until it is measured, which starts after your first purchase.

Range0 <= value <= 100
badge*

The badge on the client's site, as of the last check. GET /projects/{project_id}/badge has the install kit.

auto_replace*boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

excluded_directories*array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

allowance*|

How the launch's 100 directories are spent. null before launch.

progress*|

How many listings are in each status. null before launch.

external_id*|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata*

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
created_at*string

When the project was created.

Formatdate-time
updated_at*string

When a field, the status or a listing count last changed.

Formatdate-time
launched_at*|

When the project launched. null for a draft.

Formatdate-time
curl -X POST "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","external_id":"crm_8812","logo_url":"https://ledgerly.test/press/logo.png","screenshot_urls":["https://ledgerly.test/press/dashboard.png"]}'
{  "object": "project",  "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP",  "livemode": false,  "status": "draft",  "hold_reason": null,  "action": null,  "url": "https://ledgerly.test",  "name": "Ledgerly",  "maker_name": null,  "contact_email": "founder@ledgerly.test",  "tagline": null,  "short_description": null,  "long_description": null,  "category": null,  "pricing_model": null,  "price_amount_cents": null,  "price_period": null,  "launch_date": null,  "twitter_url": null,  "competitors": [],  "tags": [],  "audience": {    "b2b": false,    "ai": false,    "directory": false  },  "promo_code": null,  "promo_description": null,  "logo": {    "object": "image",    "id": null,    "status": "importing",    "url": null,    "error": null  },  "screenshots": [    {      "object": "image",      "id": null,      "status": "importing",      "url": null,      "error": null    }  ],  "autofill": {    "status": "running",    "name_source": "domain",    "filled": [],    "image_candidates": []  },  "readiness": {    "ready": false,    "missing": [      "logo",      "screenshots"    ]  },  "domain_rating": null,  "badge": {    "status": "not_checked",    "checked_at": null  },  "auto_replace": true,  "excluded_directories": [],  "allowance": null,  "progress": null,  "external_id": "crm_8812",  "metadata": {},  "created_at": "2026-10-03T13:58:12Z",  "updated_at": "2026-10-03T13:58:12Z",  "launched_at": null}

Read a project

GET
/projects/{project_id}

Returns the project with its status, readiness, allowance and listing counts. Poll it, or follow project.* events, to learn about changes.

Path Parameters

project_id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$

Response Body

The project.

application/json
  1. response

One of your client's products, from draft to the last listing.

object*string

Always project.

id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$
livemode*boolean

true for a live project, false for a test project.

status*string

Where the project stands. When more than one fits, the first in this list wins:

  • on_hold: work is paused; hold_reason says why.
  • action_required: you or your client need to act; action says what.
  • in_progress: listings are moving and nobody needs to act.
  • completed: no listing is pending, in_progress or action_required, and auto-replace has nothing to add. Listings in submitted can still go live.
  • draft: not launched yet.

The console and reports show these as Draft, In progress, Needs you, On hold and Completed.

Value in"draft""in_progress""action_required""on_hold""completed"
hold_reason*|

Why work is paused, when status is on_hold: review while we review the project, which clears without action from you, or paused when we paused it. null otherwise.

Value in"review""paused"null
action*|

What to do, when status is action_required. null otherwise.

url*string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name*string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name*|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email*|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline*|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description*|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description*|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category*|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model*|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents*|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period*|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date*|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url*|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors*array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags*array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience*

Marks that open the directories which accept only some kinds of products.

promo_code*|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description*|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
logo*|

The logo, or null when the project has none.

screenshots*array<>

0 to 5 screenshots, in the order directories use them.

Itemsitems <= 5
autofill*

What we read from the client's site to fill in the project.

readiness*

Whether the project can launch. GET /projects/{project_id}/readiness shows the full preview.

domain_rating*|

The client's site's Domain Rating from Ahrefs, 0 to 100. null until it is measured, which starts after your first purchase.

Range0 <= value <= 100
badge*

The badge on the client's site, as of the last check. GET /projects/{project_id}/badge has the install kit.

auto_replace*boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

excluded_directories*array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

allowance*|

How the launch's 100 directories are spent. null before launch.

progress*|

How many listings are in each status. null before launch.

external_id*|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata*

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
created_at*string

When the project was created.

Formatdate-time
updated_at*string

When a field, the status or a listing count last changed.

Formatdate-time
launched_at*|

When the project launched. null for a draft.

Formatdate-time

Typical errors

curl "https://api.submitator.com/v1/projects/prj_4QzX1m9Lr2Vb7Nc8Tk3HwP" \  -H "Authorization: Bearer $SUBMITATOR_API_KEY"
{  "object": "project",  "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP",  "livemode": false,  "status": "action_required",  "hold_reason": null,  "action": {    "code": "install_badge",    "waiting_on": "client",    "client_message": "Please add the directory badges block to https://ledgerly.test. 9 directories list Ledgerly once their badges show on the site."  },  "url": "https://ledgerly.test",  "name": "Ledgerly",  "maker_name": "Maya Chen",  "contact_email": "founder@ledgerly.test",  "tagline": "Double-entry bookkeeping for solo founders",  "short_description": "Ledgerly keeps the books for one-person companies: bank sync, invoices and a year-end pack for your accountant.",  "long_description": null,  "category": "saas",  "pricing_model": "freemium",  "price_amount_cents": 1200,  "price_period": "monthly",  "launch_date": null,  "twitter_url": null,  "competitors": [],  "tags": [    "accounting",    "bookkeeping",    "invoicing"  ],  "audience": {    "b2b": false,    "ai": false,    "directory": false  },  "promo_code": null,  "promo_description": null,  "logo": {    "object": "image",    "id": "img_5Mx2Kq8Wt3Lr9Vb7Nz1HdQ",    "status": "ready",    "url": "https://feed.example.net/a/Lq7Wx2Km.r9Tz4",    "error": null  },  "screenshots": [    {      "object": "image",      "id": "img_2Kr9Lx3Wq7Mt5Vb8Nz1HcF",      "status": "ready",      "url": "https://feed.example.net/a/Sv3Nk8Qp.m2Wx7",      "error": null    }  ],  "autofill": {    "status": "complete",    "name_source": "site",    "filled": [      "name",      "tagline",      "short_description"    ],    "image_candidates": [      {        "url": "https://ledgerly.test/apple-touch-icon.png",        "suggested_as": "logo",        "width": 180,        "height": 180      },      {        "url": "https://ledgerly.test/og-image.png",        "suggested_as": "screenshot",        "width": 1200,        "height": 630      }    ]  },  "readiness": {    "ready": true,    "missing": []  },  "domain_rating": 34,  "badge": {    "status": "not_found",    "checked_at": "2026-10-08T09:12:44Z"  },  "auto_replace": true,  "excluded_directories": [],  "allowance": {    "total": 100,    "used": 78,    "reserved": 14,    "available": 8  },  "progress": {    "total": 96,    "pending": 0,    "in_progress": 5,    "action_required": 9,    "submitted": 8,    "live": 70,    "not_accepted": 3,    "cancelled": 1  },  "external_id": "crm_8812",  "metadata": {    "account_owner": "Priya",    "crm_stage": "onboarding"  },  "created_at": "2026-10-03T13:58:12Z",  "updated_at": "2026-10-08T14:17:01Z",  "launched_at": "2026-10-03T14:05:40Z"}

Update a project

PATCH
/projects/{project_id}

Changes the fields you send and leaves the others as they are. Send null or an empty string to clear a field. After launch contact_email can no longer change; other edits apply to listings that have not reached their directory yet.

Path Parameters

project_id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$

Request Body

application/json
  1. body

The fields to change. Fields you leave out keep their value; null or an empty string clears a field. Change images with the logo and screenshot operations.

Properties1 <= properties
url?string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name?string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name?|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email?|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline?|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description?|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description?|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category?|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model?|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents?|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period?|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date?|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url?|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors?array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags?array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience?

Marks that open the directories which accept only some kinds of products.

promo_code?|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description?|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
external_id?|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata?

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
excluded_directories?array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

auto_replace?boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

Response Body

The project after the change.

application/json
  1. response

One of your client's products, from draft to the last listing.

object*string

Always project.

id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$
livemode*boolean

true for a live project, false for a test project.

status*string

Where the project stands. When more than one fits, the first in this list wins:

  • on_hold: work is paused; hold_reason says why.
  • action_required: you or your client need to act; action says what.
  • in_progress: listings are moving and nobody needs to act.
  • completed: no listing is pending, in_progress or action_required, and auto-replace has nothing to add. Listings in submitted can still go live.
  • draft: not launched yet.

The console and reports show these as Draft, In progress, Needs you, On hold and Completed.

Value in"draft""in_progress""action_required""on_hold""completed"
hold_reason*|

Why work is paused, when status is on_hold: review while we review the project, which clears without action from you, or paused when we paused it. null otherwise.

Value in"review""paused"null
action*|

What to do, when status is action_required. null otherwise.

url*string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name*string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name*|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email*|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline*|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description*|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description*|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category*|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model*|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents*|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period*|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date*|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url*|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors*array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags*array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience*

Marks that open the directories which accept only some kinds of products.

promo_code*|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description*|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
logo*|

The logo, or null when the project has none.

screenshots*array<>

0 to 5 screenshots, in the order directories use them.

Itemsitems <= 5
autofill*

What we read from the client's site to fill in the project.

readiness*

Whether the project can launch. GET /projects/{project_id}/readiness shows the full preview.

domain_rating*|

The client's site's Domain Rating from Ahrefs, 0 to 100. null until it is measured, which starts after your first purchase.

Range0 <= value <= 100
badge*

The badge on the client's site, as of the last check. GET /projects/{project_id}/badge has the install kit.

auto_replace*boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

excluded_directories*array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

allowance*|

How the launch's 100 directories are spent. null before launch.

progress*|

How many listings are in each status. null before launch.

external_id*|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata*

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
created_at*string

When the project was created.

Formatdate-time
updated_at*string

When a field, the status or a listing count last changed.

Formatdate-time
launched_at*|

When the project launched. null for a draft.

Formatdate-time
curl -X PATCH "https://api.submitator.com/v1/projects/prj_4QzX1m9Lr2Vb7Nc8Tk3HwP" \  -H "Authorization: Bearer $SUBMITATOR_API_KEY" \  -H "Content-Type: application/json" \  -d '{"maker_name":"Maya Chen","category":"saas","pricing_model":"freemium","price_amount_cents":1200,"price_period":"monthly","tags":["accounting","bookkeeping","invoicing"],"metadata":{"account_owner":"Priya","crm_stage":"onboarding"}}'
{  "object": "project",  "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP",  "livemode": false,  "status": "draft",  "hold_reason": null,  "action": null,  "url": "https://ledgerly.test",  "name": "Ledgerly",  "maker_name": "Maya Chen",  "contact_email": "founder@ledgerly.test",  "tagline": "Double-entry bookkeeping for solo founders",  "short_description": "Ledgerly keeps the books for one-person companies: bank sync, invoices and a year-end pack for your accountant.",  "long_description": null,  "category": "saas",  "pricing_model": "freemium",  "price_amount_cents": 1200,  "price_period": "monthly",  "launch_date": null,  "twitter_url": null,  "competitors": [],  "tags": [    "accounting",    "bookkeeping",    "invoicing"  ],  "audience": {    "b2b": false,    "ai": false,    "directory": false  },  "promo_code": null,  "promo_description": null,  "logo": {    "object": "image",    "id": "img_5Mx2Kq8Wt3Lr9Vb7Nz1HdQ",    "status": "ready",    "url": "https://feed.example.net/a/Lq7Wx2Km.r9Tz4",    "error": null  },  "screenshots": [    {      "object": "image",      "id": "img_2Kr9Lx3Wq7Mt5Vb8Nz1HcF",      "status": "ready",      "url": "https://feed.example.net/a/Sv3Nk8Qp.m2Wx7",      "error": null    }  ],  "autofill": {    "status": "complete",    "name_source": "site",    "filled": [      "name",      "tagline",      "short_description"    ],    "image_candidates": [      {        "url": "https://ledgerly.test/apple-touch-icon.png",        "suggested_as": "logo",        "width": 180,        "height": 180      },      {        "url": "https://ledgerly.test/og-image.png",        "suggested_as": "screenshot",        "width": 1200,        "height": 630      }    ]  },  "readiness": {    "ready": true,    "missing": []  },  "domain_rating": 34,  "badge": {    "status": "not_checked",    "checked_at": null  },  "auto_replace": true,  "excluded_directories": [],  "allowance": null,  "progress": null,  "external_id": "crm_8812",  "metadata": {    "account_owner": "Priya",    "crm_stage": "onboarding"  },  "created_at": "2026-10-03T13:58:12Z",  "updated_at": "2026-10-03T14:02:31Z",  "launched_at": null}

Delete a draft

DELETE
/projects/{project_id}

Deletes a project that has not launched, with its images. A launched project cannot be deleted: the call returns 409 already_launched and changes nothing.

Path Parameters

project_id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$

Response Body

The project is deleted.

application/json
  1. response

What was deleted.

object*string

The kind of object that was deleted.

Value in"project""webhook_endpoint"
id*string

The deleted object's id.

livemode*boolean

true in live mode, false in test mode.

deleted*boolean

Always true.

curl -X DELETE "https://api.submitator.com/v1/projects/prj_4QzX1m9Lr2Vb7Nc8Tk3HwP" \  -H "Authorization: Bearer $SUBMITATOR_API_KEY"
{  "object": "project",  "id": "prj_5Ob2Kx8Wq3Lt7Vb9Rz1NmE",  "livemode": false,  "deleted": true}
DELETE
/projects/{project_id}/logo

Removes the logo from a draft. A launched project must keep a logo: removing it returns 422 validation_failed, so replace it with PUT instead.

Path Parameters

project_id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$

Response Body

The project without a logo. readiness.missing lists logo again.

application/json
  1. response

One of your client's products, from draft to the last listing.

object*string

Always project.

id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$
livemode*boolean

true for a live project, false for a test project.

status*string

Where the project stands. When more than one fits, the first in this list wins:

  • on_hold: work is paused; hold_reason says why.
  • action_required: you or your client need to act; action says what.
  • in_progress: listings are moving and nobody needs to act.
  • completed: no listing is pending, in_progress or action_required, and auto-replace has nothing to add. Listings in submitted can still go live.
  • draft: not launched yet.

The console and reports show these as Draft, In progress, Needs you, On hold and Completed.

Value in"draft""in_progress""action_required""on_hold""completed"
hold_reason*|

Why work is paused, when status is on_hold: review while we review the project, which clears without action from you, or paused when we paused it. null otherwise.

Value in"review""paused"null
action*|

What to do, when status is action_required. null otherwise.

url*string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name*string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name*|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email*|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline*|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description*|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description*|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category*|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model*|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents*|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period*|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date*|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url*|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors*array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags*array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience*

Marks that open the directories which accept only some kinds of products.

promo_code*|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description*|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
logo*|

The logo, or null when the project has none.

screenshots*array<>

0 to 5 screenshots, in the order directories use them.

Itemsitems <= 5
autofill*

What we read from the client's site to fill in the project.

readiness*

Whether the project can launch. GET /projects/{project_id}/readiness shows the full preview.

domain_rating*|

The client's site's Domain Rating from Ahrefs, 0 to 100. null until it is measured, which starts after your first purchase.

Range0 <= value <= 100
badge*

The badge on the client's site, as of the last check. GET /projects/{project_id}/badge has the install kit.

auto_replace*boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

excluded_directories*array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

allowance*|

How the launch's 100 directories are spent. null before launch.

progress*|

How many listings are in each status. null before launch.

external_id*|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata*

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
created_at*string

When the project was created.

Formatdate-time
updated_at*string

When a field, the status or a listing count last changed.

Formatdate-time
launched_at*|

When the project launched. null for a draft.

Formatdate-time
curl -X DELETE "https://api.submitator.com/v1/projects/prj_4QzX1m9Lr2Vb7Nc8Tk3HwP/logo" \  -H "Authorization: Bearer $SUBMITATOR_API_KEY"
{  "object": "project",  "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP",  "livemode": false,  "status": "draft",  "hold_reason": null,  "action": null,  "url": "https://ledgerly.test",  "name": "Ledgerly",  "maker_name": null,  "contact_email": "founder@ledgerly.test",  "tagline": "Double-entry bookkeeping for solo founders",  "short_description": "Ledgerly keeps the books for one-person companies: bank sync, invoices and a year-end pack for your accountant.",  "long_description": null,  "category": null,  "pricing_model": null,  "price_amount_cents": null,  "price_period": null,  "launch_date": null,  "twitter_url": null,  "competitors": [],  "tags": [],  "audience": {    "b2b": false,    "ai": false,    "directory": false  },  "promo_code": null,  "promo_description": null,  "logo": null,  "screenshots": [    {      "object": "image",      "id": "img_2Kr9Lx3Wq7Mt5Vb8Nz1HcF",      "status": "ready",      "url": "https://feed.example.net/a/Sv3Nk8Qp.m2Wx7",      "error": null    }  ],  "autofill": {    "status": "complete",    "name_source": "site",    "filled": [      "name",      "tagline",      "short_description"    ],    "image_candidates": [      {        "url": "https://ledgerly.test/apple-touch-icon.png",        "suggested_as": "logo",        "width": 180,        "height": 180      },      {        "url": "https://ledgerly.test/og-image.png",        "suggested_as": "screenshot",        "width": 1200,        "height": 630      }    ]  },  "readiness": {    "ready": false,    "missing": [      "logo"    ]  },  "domain_rating": 34,  "badge": {    "status": "not_checked",    "checked_at": null  },  "auto_replace": true,  "excluded_directories": [],  "allowance": null,  "progress": null,  "external_id": "crm_8812",  "metadata": {},  "created_at": "2026-10-03T13:58:12Z",  "updated_at": "2026-10-03T14:00:55Z",  "launched_at": null}
PUT
/projects/{project_id}/logo

Uploads the logo as multipart/form-data with a file field, or imports it from url in a JSON body. PNG, JPEG, GIF or WebP up to 5 MB; it replaces the current logo. Imports from a URL count toward a limit of 60 per hour.

Path Parameters

project_id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$

Request Body

  1. body

An image file, sent as multipart/form-data.

file*file

A PNG, JPEG, GIF or WebP file of up to 5 MB.

Formatbinary

Response Body

The project, with the new logo in logo.

application/json
  1. response

One of your client's products, from draft to the last listing.

object*string

Always project.

id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$
livemode*boolean

true for a live project, false for a test project.

status*string

Where the project stands. When more than one fits, the first in this list wins:

  • on_hold: work is paused; hold_reason says why.
  • action_required: you or your client need to act; action says what.
  • in_progress: listings are moving and nobody needs to act.
  • completed: no listing is pending, in_progress or action_required, and auto-replace has nothing to add. Listings in submitted can still go live.
  • draft: not launched yet.

The console and reports show these as Draft, In progress, Needs you, On hold and Completed.

Value in"draft""in_progress""action_required""on_hold""completed"
hold_reason*|

Why work is paused, when status is on_hold: review while we review the project, which clears without action from you, or paused when we paused it. null otherwise.

Value in"review""paused"null
action*|

What to do, when status is action_required. null otherwise.

url*string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name*string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name*|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email*|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline*|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description*|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description*|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category*|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model*|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents*|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period*|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date*|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url*|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors*array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags*array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience*

Marks that open the directories which accept only some kinds of products.

promo_code*|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description*|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
logo*|

The logo, or null when the project has none.

screenshots*array<>

0 to 5 screenshots, in the order directories use them.

Itemsitems <= 5
autofill*

What we read from the client's site to fill in the project.

readiness*

Whether the project can launch. GET /projects/{project_id}/readiness shows the full preview.

domain_rating*|

The client's site's Domain Rating from Ahrefs, 0 to 100. null until it is measured, which starts after your first purchase.

Range0 <= value <= 100
badge*

The badge on the client's site, as of the last check. GET /projects/{project_id}/badge has the install kit.

auto_replace*boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

excluded_directories*array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

allowance*|

How the launch's 100 directories are spent. null before launch.

progress*|

How many listings are in each status. null before launch.

external_id*|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata*

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
created_at*string

When the project was created.

Formatdate-time
updated_at*string

When a field, the status or a listing count last changed.

Formatdate-time
launched_at*|

When the project launched. null for a draft.

Formatdate-time
curl -X PUT "https://api.submitator.com/v1/projects/prj_4QzX1m9Lr2Vb7Nc8Tk3HwP/logo" \  -H "Authorization: Bearer $SUBMITATOR_API_KEY" \  -H "Content-Type: application/json" \  -d '{"url":"https://ledgerly.test/press/logo.png"}'
{  "object": "project",  "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP",  "livemode": false,  "status": "draft",  "hold_reason": null,  "action": null,  "url": "https://ledgerly.test",  "name": "Ledgerly",  "maker_name": null,  "contact_email": "founder@ledgerly.test",  "tagline": "Double-entry bookkeeping for solo founders",  "short_description": "Ledgerly keeps the books for one-person companies: bank sync, invoices and a year-end pack for your accountant.",  "long_description": null,  "category": null,  "pricing_model": null,  "price_amount_cents": null,  "price_period": null,  "launch_date": null,  "twitter_url": null,  "competitors": [],  "tags": [],  "audience": {    "b2b": false,    "ai": false,    "directory": false  },  "promo_code": null,  "promo_description": null,  "logo": {    "object": "image",    "id": "img_5Mx2Kq8Wt3Lr9Vb7Nz1HdQ",    "status": "ready",    "url": "https://feed.example.net/a/Lq7Wx2Km.r9Tz4",    "error": null  },  "screenshots": [    {      "object": "image",      "id": "img_2Kr9Lx3Wq7Mt5Vb8Nz1HcF",      "status": "ready",      "url": "https://feed.example.net/a/Sv3Nk8Qp.m2Wx7",      "error": null    }  ],  "autofill": {    "status": "complete",    "name_source": "site",    "filled": [      "name",      "tagline",      "short_description"    ],    "image_candidates": [      {        "url": "https://ledgerly.test/apple-touch-icon.png",        "suggested_as": "logo",        "width": 180,        "height": 180      },      {        "url": "https://ledgerly.test/og-image.png",        "suggested_as": "screenshot",        "width": 1200,        "height": 630      }    ]  },  "readiness": {    "ready": true,    "missing": []  },  "domain_rating": 34,  "badge": {    "status": "not_checked",    "checked_at": null  },  "auto_replace": true,  "excluded_directories": [],  "allowance": null,  "progress": null,  "external_id": "crm_8812",  "metadata": {},  "created_at": "2026-10-03T13:58:12Z",  "updated_at": "2026-10-03T14:01:20Z",  "launched_at": null}

Add a screenshot

POST
/projects/{project_id}/screenshots

Adds one screenshot, uploaded as file or imported from url. A project holds 1 to 5 screenshots, and directories that take a single screenshot use the first one. The new screenshot is the last item of screenshots in the response.

Path Parameters

project_id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$

Request Body

  1. body

An image file, sent as multipart/form-data.

file*file

A PNG, JPEG, GIF or WebP file of up to 5 MB.

Formatbinary

Response Body

The project, with the new screenshot last in screenshots.

application/json
  1. response

One of your client's products, from draft to the last listing.

object*string

Always project.

id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$
livemode*boolean

true for a live project, false for a test project.

status*string

Where the project stands. When more than one fits, the first in this list wins:

  • on_hold: work is paused; hold_reason says why.
  • action_required: you or your client need to act; action says what.
  • in_progress: listings are moving and nobody needs to act.
  • completed: no listing is pending, in_progress or action_required, and auto-replace has nothing to add. Listings in submitted can still go live.
  • draft: not launched yet.

The console and reports show these as Draft, In progress, Needs you, On hold and Completed.

Value in"draft""in_progress""action_required""on_hold""completed"
hold_reason*|

Why work is paused, when status is on_hold: review while we review the project, which clears without action from you, or paused when we paused it. null otherwise.

Value in"review""paused"null
action*|

What to do, when status is action_required. null otherwise.

url*string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name*string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name*|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email*|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline*|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description*|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description*|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category*|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model*|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents*|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period*|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date*|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url*|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors*array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags*array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience*

Marks that open the directories which accept only some kinds of products.

promo_code*|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description*|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
logo*|

The logo, or null when the project has none.

screenshots*array<>

0 to 5 screenshots, in the order directories use them.

Itemsitems <= 5
autofill*

What we read from the client's site to fill in the project.

readiness*

Whether the project can launch. GET /projects/{project_id}/readiness shows the full preview.

domain_rating*|

The client's site's Domain Rating from Ahrefs, 0 to 100. null until it is measured, which starts after your first purchase.

Range0 <= value <= 100
badge*

The badge on the client's site, as of the last check. GET /projects/{project_id}/badge has the install kit.

auto_replace*boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

excluded_directories*array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

allowance*|

How the launch's 100 directories are spent. null before launch.

progress*|

How many listings are in each status. null before launch.

external_id*|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata*

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
created_at*string

When the project was created.

Formatdate-time
updated_at*string

When a field, the status or a listing count last changed.

Formatdate-time
launched_at*|

When the project launched. null for a draft.

Formatdate-time
curl -X POST "https://api.submitator.com/v1/projects/prj_4QzX1m9Lr2Vb7Nc8Tk3HwP/screenshots" \  -H "Authorization: Bearer $SUBMITATOR_API_KEY" \  -H "Content-Type: application/json" \  -d '{"url":"https://ledgerly.test/press/reports-page.png"}'
{  "object": "project",  "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP",  "livemode": false,  "status": "draft",  "hold_reason": null,  "action": null,  "url": "https://ledgerly.test",  "name": "Ledgerly",  "maker_name": "Maya Chen",  "contact_email": "founder@ledgerly.test",  "tagline": "Double-entry bookkeeping for solo founders",  "short_description": "Ledgerly keeps the books for one-person companies: bank sync, invoices and a year-end pack for your accountant.",  "long_description": null,  "category": "saas",  "pricing_model": "freemium",  "price_amount_cents": 1200,  "price_period": "monthly",  "launch_date": null,  "twitter_url": null,  "competitors": [],  "tags": [    "accounting",    "bookkeeping",    "invoicing"  ],  "audience": {    "b2b": false,    "ai": false,    "directory": false  },  "promo_code": null,  "promo_description": null,  "logo": {    "object": "image",    "id": "img_5Mx2Kq8Wt3Lr9Vb7Nz1HdQ",    "status": "ready",    "url": "https://feed.example.net/a/Lq7Wx2Km.r9Tz4",    "error": null  },  "screenshots": [    {      "object": "image",      "id": "img_2Kr9Lx3Wq7Mt5Vb8Nz1HcF",      "status": "ready",      "url": "https://feed.example.net/a/Sv3Nk8Qp.m2Wx7",      "error": null    },    {      "object": "image",      "id": "img_6Wn3Kq8Lx2Rt7Vb9Mz4HcB",      "status": "ready",      "url": "https://feed.example.net/a/Tz5Kq1Wm.n8Lr3",      "error": null    }  ],  "autofill": {    "status": "complete",    "name_source": "site",    "filled": [      "name",      "tagline",      "short_description"    ],    "image_candidates": [      {        "url": "https://ledgerly.test/apple-touch-icon.png",        "suggested_as": "logo",        "width": 180,        "height": 180      },      {        "url": "https://ledgerly.test/og-image.png",        "suggested_as": "screenshot",        "width": 1200,        "height": 630      }    ]  },  "readiness": {    "ready": true,    "missing": []  },  "domain_rating": 34,  "badge": {    "status": "not_checked",    "checked_at": null  },  "auto_replace": true,  "excluded_directories": [],  "allowance": null,  "progress": null,  "external_id": "crm_8812",  "metadata": {    "account_owner": "Priya",    "crm_stage": "onboarding"  },  "created_at": "2026-10-03T13:58:12Z",  "updated_at": "2026-10-03T14:03:05Z",  "launched_at": null}

Remove a screenshot

DELETE
/projects/{project_id}/screenshots/{image_id}

Removes one screenshot. A launched project keeps at least 1, so removing its last screenshot returns 422 validation_failed.

Path Parameters

project_id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$
image_id*string

The screenshot's id, from screenshots[].id on the project.

Match^img_[0-9A-Za-z]{22}$

Response Body

The project without that screenshot.

application/json
  1. response

One of your client's products, from draft to the last listing.

object*string

Always project.

id*string

The project's id.

Match^prj_[0-9A-Za-z]{22}$
livemode*boolean

true for a live project, false for a test project.

status*string

Where the project stands. When more than one fits, the first in this list wins:

  • on_hold: work is paused; hold_reason says why.
  • action_required: you or your client need to act; action says what.
  • in_progress: listings are moving and nobody needs to act.
  • completed: no listing is pending, in_progress or action_required, and auto-replace has nothing to add. Listings in submitted can still go live.
  • draft: not launched yet.

The console and reports show these as Draft, In progress, Needs you, On hold and Completed.

Value in"draft""in_progress""action_required""on_hold""completed"
hold_reason*|

Why work is paused, when status is on_hold: review while we review the project, which clears without action from you, or paused when we paused it. null otherwise.

Value in"review""paused"null
action*|

What to do, when status is action_required. null otherwise.

url*string

Your client's site, http or https, up to 2,048 characters. Unique among your projects in the mode. Live mode accepts public addresses only: no IP addresses and no .test, .example, .invalid or .localhost hosts.

Formaturi
Lengthlength <= 2048
name*string

The product's name as directories show it, 2 to 100 characters. Until you set one, it comes from the domain, and once autofill finishes, from the site's title.

Length2 <= length <= 100
maker_name*|

The person directories list as the maker, up to 100 characters.

Lengthlength <= 100
contact_email*|

Required to launch. Some directories register the listing to this address and send their emails here. Unique among your projects in the mode, and fixed once the project launches.

Formatemail
Lengthlength <= 254
tagline*|

One line about the product, up to 100 characters.

Lengthlength <= 100
short_description*|

A short description, up to 500 characters. Most directories show this one.

Lengthlength <= 500
long_description*|

A longer description, up to 2,000 characters, for directories that take one.

Lengthlength <= 2000
category*|

The product's category. We map it to each directory's own list of categories.

Value in"saas""ai_tools""dev_tools""no_code""productivity""other"null
pricing_model*|

How the product is sold.

Value in"free""freemium""paid""open_source"null
price_amount_cents*|

The headline price in US cents, such as 1200 for $12. Leave it null for a free product.

Range0 <= value
price_period*|

What price_amount_cents pays for.

Value in"monthly""yearly""one_time"null
launch_date*|

The day the product went public, YYYY-MM-DD, for directories that ask for it. Not the day the project launched here: that is launched_at.

Formatdate
twitter_url*|

The product's profile on X, as an https://x.com/ or https://twitter.com/ URL.

Match^https://(x|twitter)\.com/.+
Formaturi
competitors*array<>

Up to 20 products this one is an alternative to. Some directories list a product only next to its competitors. Sending a list replaces the whole list.

Itemsitems <= 20
tags*array<>

Up to 10 keywords of up to 40 characters each. Sending a list replaces the whole list.

Itemsitems <= 10
audience*

Marks that open the directories which accept only some kinds of products.

promo_code*|

A discount code for the product, up to 50 characters. Some directories list only products with one.

Lengthlength <= 50
promo_description*|

What promo_code gives, up to 500 characters, such as 30% off the first 3 months.

Lengthlength <= 500
logo*|

The logo, or null when the project has none.

screenshots*array<>

0 to 5 screenshots, in the order directories use them.

Itemsitems <= 5
autofill*

What we read from the client's site to fill in the project.

readiness*

Whether the project can launch. GET /projects/{project_id}/readiness shows the full preview.

domain_rating*|

The client's site's Domain Rating from Ahrefs, 0 to 100. null until it is measured, which starts after your first purchase.

Range0 <= value <= 100
badge*

The badge on the client's site, as of the last check. GET /projects/{project_id}/badge has the install kit.

auto_replace*boolean

New projects start with true. When true, we add fitting directories from the free allowance for you: after a listing ends not_accepted or cancelled, after the badge passes verification, and when a new directory joins the catalog. A launch with include turns it off unless the launch sends auto_replace: true.

excluded_directories*array<>

Directories never to use for this project: auto launches, auto adds and auto-replace skip them. A launch with exclude adds its list here. Sending a list replaces the whole list.

allowance*|

How the launch's 100 directories are spent. null before launch.

progress*|

How many listings are in each status. null before launch.

external_id*|

Your own id for the project, up to 100 characters, such as the client's id in your CRM. Unique among your projects in the mode; find a project by it with GET /projects?external_id=.

Lengthlength <= 100
metadata*

Your own key-value data. We store it and return it, and use it for nothing else.

Propertiesproperties <= 20
created_at*string

When the project was created.

Formatdate-time
updated_at*string

When a field, the status or a listing count last changed.

Formatdate-time
launched_at*|

When the project launched. null for a draft.

Formatdate-time
curl -X DELETE "https://api.submitator.com/v1/projects/prj_4QzX1m9Lr2Vb7Nc8Tk3HwP/screenshots/img_6Wn3Kq8Lx2Rt7Vb9Mz4HcB" \  -H "Authorization: Bearer $SUBMITATOR_API_KEY"
{  "object": "project",  "id": "prj_4QzX1m9Lr2Vb7Nc8Tk3HwP",  "livemode": false,  "status": "draft",  "hold_reason": null,  "action": null,  "url": "https://ledgerly.test",  "name": "Ledgerly",  "maker_name": "Maya Chen",  "contact_email": "founder@ledgerly.test",  "tagline": "Double-entry bookkeeping for solo founders",  "short_description": "Ledgerly keeps the books for one-person companies: bank sync, invoices and a year-end pack for your accountant.",  "long_description": null,  "category": "saas",  "pricing_model": "freemium",  "price_amount_cents": 1200,  "price_period": "monthly",  "launch_date": null,  "twitter_url": null,  "competitors": [],  "tags": [    "accounting",    "bookkeeping",    "invoicing"  ],  "audience": {    "b2b": false,    "ai": false,    "directory": false  },  "promo_code": null,  "promo_description": null,  "logo": {    "object": "image",    "id": "img_5Mx2Kq8Wt3Lr9Vb7Nz1HdQ",    "status": "ready",    "url": "https://feed.example.net/a/Lq7Wx2Km.r9Tz4",    "error": null  },  "screenshots": [    {      "object": "image",      "id": "img_2Kr9Lx3Wq7Mt5Vb8Nz1HcF",      "status": "ready",      "url": "https://feed.example.net/a/Sv3Nk8Qp.m2Wx7",      "error": null    }  ],  "autofill": {    "status": "complete",    "name_source": "site",    "filled": [      "name",      "tagline",      "short_description"    ],    "image_candidates": [      {        "url": "https://ledgerly.test/apple-touch-icon.png",        "suggested_as": "logo",        "width": 180,        "height": 180      },      {        "url": "https://ledgerly.test/og-image.png",        "suggested_as": "screenshot",        "width": 1200,        "height": 630      }    ]  },  "readiness": {    "ready": true,    "missing": []  },  "domain_rating": 34,  "badge": {    "status": "not_checked",    "checked_at": null  },  "auto_replace": true,  "excluded_directories": [],  "allowance": null,  "progress": null,  "external_id": "crm_8812",  "metadata": {    "account_owner": "Priya",    "crm_stage": "onboarding"  },  "created_at": "2026-10-03T13:58:12Z",  "updated_at": "2026-10-03T14:03:40Z",  "launched_at": null}