Sites
Create the sites that SHIP hosts for your projects, read their status and their production deploys, and add custom domains to them.
application/json- body
orgId?string1 <= lengthcancelAtPeriodEnd*booleanThe hosting
application/json- response
hosting*curl -X PATCH 'https://api.letsship.ai/v1/billing/sites-hosting' \ -H 'Content-Type: application/json' \ -d '{ "cancelAtPeriodEnd": true}'{ "hosting": { "status": "active", "quantity": 0, "currentPeriodEnd": 0, "cancelAtPeriodEnd": true }}Templates
application/json- response
data*array<>curl -X GET 'https://api.letsship.ai/v1/templates'{ "data": [ { "id": "string", "kind": "website", "name": "string", "description": "string", "repository": "string", "templateUrl": "string", "useTemplateUrl": "string", "scratchOnly": true } ]}limit?integerItems on one page, 1 to 100 (default 50).
1 <= value <= 10050cursor?stringnextCursor from the previous page. Leave it out for the first page.
1 <= lengthSites
application/json- response
data*array<>nextCursor*string|nullplan*|null|nullaudience?ListAudiencefounder: every project in scope is one SHIP provisioned, so a console speaks of changes and Publish. developer: at least one project is a repository of the caller's own, or there is no project.
"founder""developer"curl -X GET 'https://api.letsship.ai/v1/sites'{ "data": [ { "id": "string", "projectId": "string", "kind": "website", "slug": "string", "url": "string", "templateId": "string", "status": "active", "suspendedUntil": 0, "badgeRequired": true, "badgeRequiredByPlan": true, "liveDeployId": "string", "lastDeploy": { "id": "string", "status": "building", "startedAt": 0, "finishedAt": 0 }, "provisionedBy": "ship", "createdAt": 0, "updatedAt": 0 } ], "nextCursor": "string", "plan": { "tier": "subscription", "maxSites": 0, "sitesHeld": 0, "customDomains": true, "badgeRequired": true, "monthlyPageViewCap": 0, "badgeRequiredReason": "string", "hosting": { "status": "active", "quantity": 0, "currentPeriodEnd": 0, "cancelAtPeriodEnd": true }, "hostingOffer": { "priceUsd": 1, "interval": "month", "purchase": "available" }, "pendingMove": { "id": "string", "cause": "subscription_ended", "fromTier": "subscription", "effectiveAt": 0, "sitesToEnd": [ { "id": "string", "slug": "string" } ], "badgeTurnsOn": true } }, "audience": "founder"}application/json- body
projectId*string1 <= lengthslug*string1 <= length <= 100templateId*string1 <= lengthCreated
application/json- response
id*stringprojectId*string|nullkind*SiteKind"website""blog""docs""waitlist"slug*stringurl*stringtemplateId*stringstatus*SiteStatus"active""suspended""deleting"suspendedUntil*integer|nullbadgeRequired*booleanbadgeRequiredByPlan*booleanliveDeployId*string|nulllastDeploy*|nullprovisionedBy?ProjectProvisionedBy"ship""owner"createdAt*integerupdatedAt*integercurl -X POST 'https://api.letsship.ai/v1/sites' \ -H 'Content-Type: application/json' \ -d '{ "projectId": "string", "slug": "string", "templateId": "string"}'{ "id": "string", "projectId": "string", "kind": "website", "slug": "string", "url": "string", "templateId": "string", "status": "active", "suspendedUntil": 0, "badgeRequired": true, "badgeRequiredByPlan": true, "liveDeployId": "string", "lastDeploy": { "id": "string", "status": "building", "startedAt": 0, "finishedAt": 0 }, "provisionedBy": "ship", "createdAt": 0, "updatedAt": 0}siteId*string1 <= lengthSite
application/json- response
id*stringprojectId*string|nullkind*SiteKind"website""blog""docs""waitlist"slug*stringurl*stringtemplateId*stringstatus*SiteStatus"active""suspended""deleting"suspendedUntil*integer|nullbadgeRequired*booleanbadgeRequiredByPlan*booleanliveDeployId*string|nulllastDeploy*|nullprovisionedBy?ProjectProvisionedBy"ship""owner"createdAt*integerupdatedAt*integercurl -X GET 'https://api.letsship.ai/v1/sites/string'{ "id": "string", "projectId": "string", "kind": "website", "slug": "string", "url": "string", "templateId": "string", "status": "active", "suspendedUntil": 0, "badgeRequired": true, "badgeRequiredByPlan": true, "liveDeployId": "string", "lastDeploy": { "id": "string", "status": "building", "startedAt": 0, "finishedAt": 0 }, "provisionedBy": "ship", "createdAt": 0, "updatedAt": 0}siteId*string1 <= lengthapplication/json- body
badge*booleanUpdated
application/json- response
site*takesEffect*"next_production_deploy""next_production_deploy"notice*stringcurl -X PATCH 'https://api.letsship.ai/v1/sites/string' \ -H 'Content-Type: application/json' \ -d '{ "badge": true}'{ "site": { "id": "string", "projectId": "string", "kind": "website", "slug": "string", "url": "string", "templateId": "string", "status": "active", "suspendedUntil": 0, "badgeRequired": true, "badgeRequiredByPlan": true, "liveDeployId": "string", "lastDeploy": { "id": "string", "status": "building", "startedAt": 0, "finishedAt": 0 }, "provisionedBy": "ship", "createdAt": 0, "updatedAt": 0 }, "takesEffect": "next_production_deploy", "notice": "string"}siteId*string1 <= lengthAccepted
application/json- response
siteId*stringaction*string"delete""rollback""publish_retry"deployId*string|nullcurl -X DELETE 'https://api.letsship.ai/v1/sites/string'{ "siteId": "string", "action": "delete", "deployId": "string"}siteId*string1 <= lengthlimit?integerItems on one page, 1 to 100 (default 50).
1 <= value <= 10050cursor?stringnextCursor from the previous page. Leave it out for the first page.
1 <= lengthDeploys
application/json- response
data*array<>nextCursor*string|nullcurl -X GET 'https://api.letsship.ai/v1/sites/string/deploys'{ "data": [ { "id": "string", "commitSha": "string", "change": { "issueId": "string", "title": "string" }, "sequence": 0, "status": "building", "cause": "string", "outputHash": "string", "fileCount": 0, "startedAt": 0, "finishedAt": 0 } ], "nextCursor": "string"}Starters
application/json- response
data*array<>brandRights*logo*curl -X GET 'https://api.letsship.ai/v1/templates/starters'{ "data": [ { "kind": "website", "templateId": "string", "title": "string", "criteria": [ "string" ] } ], "brandRights": { "text": "string", "version": "string" }, "logo": { "maxBytes": 0, "contentTypes": [ "image/png" ] }}set?stringall: the six preset looks. kits: the three brand kits the site form offers a founder.
"all""all""kits"Presets
application/json- response
data*array<>curl -X GET 'https://api.letsship.ai/v1/templates/presets'{ "data": [ { "id": "string", "name": "string", "primaryColor": "string", "accentColor": "string", "displayFont": "string", "bodyFont": "string", "monoFont": "string", "mark": "string" } ]}siteId*string1 <= lengthidempotency-key?stringSending the same key again for the same site and deploy opens one rollback deploy. Use a new key for new work.
1 <= length <= 128application/json- body
deployId*string1 <= lengthAccepted
application/json- response
siteId*stringaction*string"delete""rollback""publish_retry"deployId*string|nullcurl -X POST 'https://api.letsship.ai/v1/sites/string/rollback' \ -H 'Content-Type: application/json' \ -d '{ "deployId": "string"}'{ "siteId": "string", "action": "delete", "deployId": "string"}siteId*string1 <= lengthidempotency-key?stringSending the same key again for the same site starts one publish. Use a new key to try again after that publish ends.
1 <= length <= 128Accepted
application/json- response
siteId*stringaction*string"delete""rollback""publish_retry"deployId*string|nullcurl -X POST 'https://api.letsship.ai/v1/sites/string/publish/retry'{ "siteId": "string", "action": "delete", "deployId": "string"}siteId*string1 <= lengthdays?integerHow many days to show, today included: 1 to 90 (default 30).
1 <= value <= 9030Usage
application/json- response
siteId*stringdays*integerdaily*array<>totalPageViews*integermonthlyPageViewCap*integer|nullcurrentMonth*curl -X GET 'https://api.letsship.ai/v1/sites/string/usage'{ "siteId": "string", "days": 0, "daily": [ { "day": "string", "pageViews": 0 } ], "totalPageViews": 0, "monthlyPageViewCap": 0, "currentMonth": { "month": "string", "pageViews": 0 }}siteId*string1 <= lengthDomains
application/json- response
data*array<>maxPerSite*integer1 <= valuecurl -X GET 'https://api.letsship.ai/v1/sites/string/domains'{ "data": [ { "hostname": "string", "siteId": "string", "status": "pending", "cause": "proof_missing", "detail": "string", "records": [ { "type": "TXT", "name": "string", "value": "string" } ], "createdAt": 0, "checkedAt": 0, "activatedAt": 0 } ], "maxPerSite": 1}siteId*string1 <= lengthapplication/json- body
hostname*string1 <= length <= 300Added
application/json- response
hostname*stringsiteId*stringstatus*SiteDomainStatuspending: SHIP waits for the DNS proof or the certificate. active: the domain serves the site. failed: the proof went away or the certificate failed. removing: SHIP removes the domain.
"pending""active""failed""removing"cause*|null|nulldetail*string|nullrecords*array<>createdAt*integercheckedAt*integer|nullactivatedAt*integer|nullcurl -X POST 'https://api.letsship.ai/v1/sites/string/domains' \ -H 'Content-Type: application/json' \ -d '{ "hostname": "string"}'{ "hostname": "string", "siteId": "string", "status": "pending", "cause": "proof_missing", "detail": "string", "records": [ { "type": "TXT", "name": "string", "value": "string" } ], "createdAt": 0, "checkedAt": 0, "activatedAt": 0}siteId*string1 <= lengthhostname*string1 <= length <= 300Accepted
application/json- response
domain*action*string"check""remove"curl -X POST 'https://api.letsship.ai/v1/sites/string/domains/string/verify'{ "domain": { "hostname": "string", "siteId": "string", "status": "pending", "cause": "proof_missing", "detail": "string", "records": [ { "type": "TXT", "name": "string", "value": "string" } ], "createdAt": 0, "checkedAt": 0, "activatedAt": 0 }, "action": "check"}siteId*string1 <= lengthhostname*string1 <= length <= 300Accepted
application/json- response
domain*action*string"check""remove"curl -X DELETE 'https://api.letsship.ai/v1/sites/string/domains/string'{ "domain": { "hostname": "string", "siteId": "string", "status": "pending", "cause": "proof_missing", "detail": "string", "records": [ { "type": "TXT", "name": "string", "value": "string" } ], "createdAt": 0, "checkedAt": 0, "activatedAt": 0 }, "action": "check"}siteId*string1 <= lengthlimit?integerAddresses on one page, 1 to 100 (default 50).
1 <= value <= 10050cursor?stringThe nextCursor of the previous page. Leave it out for the first page.
1 <= lengthWaitlist
application/json- response
data*array<>nextCursor*string|nullcurl -X GET 'https://api.letsship.ai/v1/sites/string/waitlist'{ "data": [ { "id": 0, "email": "string", "sourcePath": "string", "createdAt": 0 } ], "nextCursor": "string"}siteId*string1 <= lengthThe CSV file, as an attachment
response?stringtext/csvcurl -X GET 'https://api.letsship.ai/v1/sites/string/waitlist.csv'"string"siteId*string1 <= lengthapplication/json- body
email*stringThe address to remove. Upper and lower case do not matter, as on the waitlist itself.
1 <= length <= 254entryId?integerThe id of the entry, from the list. With it, the API removes the entry only if it still holds this address.
1 <= valueThe ids of the removed entries
application/json- response
entryIds*array<integer>1 <= itemscurl -X POST 'https://api.letsship.ai/v1/sites/string/waitlist/remove' \ -H 'Content-Type: application/json' \ -d '{ "email": "string"}'{ "entryIds": [ 0 ]}siteId*string1 <= lengthlimit?integerExports to list, newest first, 1 to 20 (default 10).
1 <= value <= 2010The exports
application/json- response
data*array<>curl -X GET 'https://api.letsship.ai/v1/sites/string/database/exports'{ "data": [ { "id": "string", "siteId": "string", "status": "running", "createdAt": 0, "completedAt": 0, "expiresAt": 0, "filename": "string", "pollAfterSeconds": 0, "failure": { "category": "auth", "title": "string", "description": "string", "retryable": true, "code": "string" } } ]}siteId*string1 <= lengthThe export, running or already ready
application/json- response
id*stringsiteId*stringstatus*SiteDatabaseExportStatusrunning until the file is made. ready while the file can be downloaded. expired after expiresAt: start a new export. failed gives the cause in failure.
"running""ready""failed""expired"createdAt*integercompletedAt*integer|nullexpiresAt*integer|nullfilename*string|nullpollAfterSeconds*integer|nullfailure*|nullcurl -X POST 'https://api.letsship.ai/v1/sites/string/database/exports'{ "id": "string", "siteId": "string", "status": "running", "createdAt": 0, "completedAt": 0, "expiresAt": 0, "filename": "string", "pollAfterSeconds": 0, "failure": { "category": "auth", "title": "string", "description": "string", "retryable": true, "code": "string" }}siteId*string1 <= lengthexportId*string1 <= lengthThe export
application/json- response
id*stringsiteId*stringstatus*SiteDatabaseExportStatusrunning until the file is made. ready while the file can be downloaded. expired after expiresAt: start a new export. failed gives the cause in failure.
"running""ready""failed""expired"createdAt*integercompletedAt*integer|nullexpiresAt*integer|nullfilename*string|nullpollAfterSeconds*integer|nullfailure*|nullcurl -X GET 'https://api.letsship.ai/v1/sites/string/database/exports/string'{ "id": "string", "siteId": "string", "status": "running", "createdAt": 0, "completedAt": 0, "expiresAt": 0, "filename": "string", "pollAfterSeconds": 0, "failure": { "category": "auth", "title": "string", "description": "string", "retryable": true, "code": "string" }}siteId*string1 <= lengthexportId*string1 <= lengthThe SQL file, as an attachment
response?stringapplication/sqlcurl -X GET 'https://api.letsship.ai/v1/sites/string/database/exports/string/file'"string"How is this page?