Website API
REST endpoints for website in SKUFlow: 25 operations with parameters, request and response fields, permissions and curl examples.
Base URL: https://app.skuflow.ai/api. Every call needs Authorization: Bearer skf_… (Authentication); {organizationId} ($ORGANIZATION_ID in the examples) is your business's id.
| Operation | Endpoint | Risk |
|---|---|---|
| The business's website | GET /organizations/{organizationId}/website | read |
| Build the website | POST /organizations/{organizationId}/website | write |
| Change the website's settings | PATCH /organizations/{organizationId}/website | write |
| Delete the website | DELETE /organizations/{organizationId}/website | sensitive |
| Write a fresh draft | POST /organizations/{organizationId}/website/regenerate | sensitive |
| Apply a style from the gallery | POST /organizations/{organizationId}/website/style | write |
| Publish the website | POST /organizations/{organizationId}/website/publish | sensitive |
| Take the website down | POST /organizations/{organizationId}/website/unpublish | sensitive |
| Preview the draft | GET /organizations/{organizationId}/website/preview | read |
| Website visitors and requests | GET /organizations/{organizationId}/website/stats | read |
| Upload a website image | POST /organizations/{organizationId}/website/image-upload-url | write |
| The website's image library | GET /organizations/{organizationId}/website/media | read |
| Add an uploaded image to the library | POST /organizations/{organizationId}/website/media/uploads | write |
| Search free stock photos | GET /organizations/{organizationId}/website/stock-photos | read |
| Use a stock photo | POST /organizations/{organizationId}/website/media/stock | write |
| Remove an image from the library | DELETE /organizations/{organizationId}/website/media/{mediaId} | sensitive |
| Add a page | POST /organizations/{organizationId}/website/pages | write |
| Edit a page | PATCH /organizations/{organizationId}/website/pages/{pageId} | write |
| Delete a page | DELETE /organizations/{organizationId}/website/pages/{pageId} | sensitive |
| Reorder the pages | POST /organizations/{organizationId}/website/pages/order | write |
| Connect a custom domain | POST /organizations/{organizationId}/website/domains | write |
| Check a domain's DNS now | POST /organizations/{organizationId}/website/domains/{domainId}/verify | write |
| Make a domain the main address | POST /organizations/{organizationId}/website/domains/{domainId}/primary | write |
| Disconnect a domain | DELETE /organizations/{organizationId}/website/domains/{domainId} | sensitive |
| Add an image to the website library (content in the request) | POST /organizations/{organizationId}/website/media/content | write |
The business's website
GET /organizations/{organizationId}/website
The website with its draft pages (sections included), theme, business details, SEO, publish state and custom domains; null before one is made (POST /website). Read-only.
| Permission | website.read (Owner, Admin, Staff) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | website_get (toolset marketing) |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Build the website
POST /organizations/{organizationId}/website
Makes the business's website as a DRAFT (nobody outside sees it until POST /website/publish): Claude writes the copy from the business's own records (what it does, its place, its trade template) and invents no reviews or claims; without a model configured a template is used (generatedBy). Pages: home, services, plans (when there are plans), about, book, contact. 409 when one exists.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_create (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
subdomain | string | No | The site's address under the sites domain: lower-case letters, numbers and hyphens. Up to 40 characters. |
trade | string or null | No | The trade template: its style, curated banner and about photos, and copy for its customers. One of: screen-printing, embroidery, promotional-products, print-on-demand, signs-and-graphics, other. Up to 40 characters. |
style | string or null | No | A style from the gallery instead of the trade's own (POST /website/style lists them). Up to 40 characters. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"subdomain":"string","trade":"string","style":"string"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Change the website's settings
PATCH /organizations/{organizationId}/website
Changes the fields sent in the draft: subdomain (409 when taken; a published site moves at once), theme, business profile (merged), SEO. Publish to show the changes on the live site (the subdomain change applies immediately).
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_update (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
subdomain | string | No | The site's address under the sites domain: lower-case letters, numbers and hyphens. Up to 40 characters. |
trade | string or null | No | The trade template: steers photo suggestions and the next fresh draft. Up to 40 characters. |
theme | object | No | |
theme.primaryColor | string | No | |
theme.accentColor | string | No | |
theme.font | enum | No | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | No | One of square, soft, round. |
theme.headerStyle | enum | No | One of light, dark, brand. |
theme.style | string | No | Up to 40 characters. |
profile | object | No | Merged into the business details: send only what changes (hours and links per day / network). |
profile.tagline | string | No | Up to 160 characters. |
profile.phone | string | No | Up to 40 characters. |
profile.email | empty string or string (email) | No | |
profile.address | string | No | Up to 300 characters. |
profile.hours | object | No | |
profile.hoursNote | string | No | Up to 200 characters. |
profile.licenseText | string | No | Up to 200 characters. |
profile.serviceAreaText | string | No | Up to 600 characters. |
profile.social | object | No | |
profile.social.facebook | empty string or string (uri) | No | |
profile.social.instagram | empty string or string (uri) | No | |
profile.social.google | empty string or string (uri) | No | |
profile.social.yelp | empty string or string (uri) | No | |
profile.social.nextdoor | empty string or string (uri) | No | |
profile.logoUrl | string | No | Up to 2000 characters. |
profile.headerBrand | enum | No | One of logoAndName, logo, name. |
profile.logoSize | enum | No | One of small, medium, large. |
profile.siteName | string | No | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | No | |
profile.termsUrl | empty string or string (uri) | No | |
profile.footer | object | No | |
profile.footer.showTagline | boolean | No | |
profile.footer.showContact | boolean | No | |
profile.footer.showHours | boolean | No | |
profile.footer.showSocial | boolean | No | |
profile.footer.showPoweredBy | boolean | No | |
profile.footer.copyright | string | No | Up to 200 characters. |
seoTitle | string or null | No | Up to 70 characters. |
seoDescription | string or null | No | Up to 170 characters. |
Example
curl -X PATCH "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"subdomain":"string","trade":"string","theme":{"primaryColor":"string","accentColor":"string","font":"modern"}}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Delete the website
DELETE /organizations/{organizationId}/website
DESTRUCTIVE and permanent: deletes the website with every page, custom domain and visitor stat; a published site goes offline at once (its addresses answer 404). The image library stays, and POST /website can build a new one. confirm must equal the site's subdomain (400 otherwise). Emits website.deleted.
| Permission | website.manage (Owner, Admin) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | website_delete (toolset marketing) |
| Approval | Over MCP, this tool always waits for your approval in SKUFlow. See Approvals. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
confirm | string | Yes | The site's subdomain, typed by the user to confirm. Up to 60 characters. |
Example
curl -X DELETE "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
ok | true |
Write a fresh draft
POST /organizations/{organizationId}/website/regenerate
DESTRUCTIVE for the draft: replaces every draft page with a newly written set (theme, domains and filled-in business details stay). The published site is unchanged until published again.
| Permission | website.manage (Owner, Admin) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | website_regenerate (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
| Approval | Over MCP, this tool asks for your OK first (in the AI app when it supports it, otherwise in SKUFlow). See Approvals. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/regenerate" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Apply a style from the gallery
POST /organizations/{organizationId}/website/style
Changes the draft's look in one step: the theme (colours unless keepColors, type, corners, header) and the layout of every block on every page. Words and photos stay; the live site changes when published. Styles: clean, bold, classic, friendly, elegant, fresh, cool, warm.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_applyStyle (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
style | string | Yes | Up to 40 characters. |
keepColors | boolean | No | Keep the business's own primary and accent colours. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/style" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"style":"string"}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Publish the website
POST /organizations/{organizationId}/website/publish
EXPOSES the draft to the public: freezes every page as it stands and serves it at the site's addresses (subdomain and active custom domains). Republishing replaces the live site. Emits website.published.
| Permission | website.manage (Owner, Admin) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | website_publish (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
| Approval | Over MCP, this tool asks for your OK first (in the AI app when it supports it, otherwise in SKUFlow). See Approvals. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/publish" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Take the website down
POST /organizations/{organizationId}/website/unpublish
The live site's addresses answer 404 until published again; the draft is kept. 409 when not published.
| Permission | website.manage (Owner, Admin) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | website_unpublish (toolset marketing) |
| Approval | Over MCP, this tool asks for your OK first (in the AI app when it supports it, otherwise in SKUFlow). See Approvals. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/unpublish" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Preview the draft
GET /organizations/{organizationId}/website/preview
The draft site exactly as POST /website/publish would serve it, with the live request form and phone settings. Read-only.
| Permission | website.read (Owner, Admin, Staff) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | website_preview (toolset marketing) |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/preview" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
subdomain | string | |
canonicalHost | string or null | |
publishedAt | string (date-time) | |
site | object | |
site.theme | object | |
site.theme.primaryColor | string | |
site.theme.accentColor | string | |
site.theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
site.theme.radius | enum | One of square, soft, round. |
site.theme.headerStyle | enum | One of light, dark, brand. |
site.theme.style | string | Up to 40 characters. |
site.profile | object | |
site.profile.tagline | string | Up to 160 characters. |
site.profile.phone | string | Up to 40 characters. |
site.profile.email | empty string or string (email) | |
site.profile.address | string | Up to 300 characters. |
site.profile.hours | object | |
site.profile.hoursNote | string | Up to 200 characters. |
site.profile.licenseText | string | Up to 200 characters. |
site.profile.serviceAreaText | string | Up to 600 characters. |
site.profile.social | object | |
site.profile.logoUrl | string | Up to 2000 characters. |
site.profile.headerBrand | enum | One of logoAndName, logo, name. |
site.profile.logoSize | enum | One of small, medium, large. |
site.profile.siteName | string | Up to 120 characters. |
site.profile.privacyUrl | empty string or string (uri) | |
site.profile.termsUrl | empty string or string (uri) | |
site.profile.footer | object | |
site.seoTitle | string or null | |
site.seoDescription | string or null | |
site.pages | array of object | |
site.pages[].slug | string | |
site.pages[].title | string | |
site.pages[].showInNav | boolean | |
site.pages[].seoTitle | string or null | |
site.pages[].seoDescription | string or null | |
site.pages[].sections | array of object | |
site.photoCredits | array of object | Credits for the stock photos the site shows; the footer lists them. |
site.photoCredits[].provider | string | |
site.photoCredits[].author | string | |
site.photoCredits[].authorUrl | string | |
site.photoCredits[].sourceUrl | string | |
organizationSlug | string | For the request form: /api/public/book/{organizationSlug}. |
businessName | string | |
customerSignIn | boolean | Customers can sign in at /c/{organizationSlug}: the header shows Customer login. |
logo | string or null | |
timezone | string | |
booking | object | |
booking.enabled | boolean | The request form takes requests. |
trackingNumbers | array of object | Dynamic number insertion: tracking numbers by visitor source; the site's phone links show the matching one. |
trackingNumbers[].phone | string | |
trackingNumbers[].isDefault | boolean | |
trackingNumbers[].utmSource | string or null | |
trackingNumbers[].utmMedium | string or null | |
trackingNumbers[].campaignId | string or null |
Website visitors and requests
GET /organizations/{organizationId}/website/stats
Page views per day and the top pages over days (default 30), and online requests by channel (request page, widget, website). Read-only.
| Permission | website.read (Owner, Admin, Staff) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | website_stats (toolset marketing) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
days | query | integer | No | 7–90. |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/stats" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
from | string | |
to | string | |
views | integer | |
days | array of object | |
days[].date | string | |
days[].views | integer | |
topPages | array of object | |
topPages[].path | string | |
topPages[].views | integer | |
requests | object | |
requests.BOOKING_PAGE | integer | |
requests.WIDGET | integer | |
requests.WEBSITE | integer |
Upload a website image
POST /organizations/{organizationId}/website/image-upload-url
Step 1 of adding an image to a section: returns a signed URL to PUT the file to (with the same Content-Type) and the url to put in the section (hero, about, gallery).
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_createImageUploadUrl (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
contentType | enum | Yes | One of image/jpeg, image/png, image/webp. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/image-upload-url" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contentType":"image/jpeg"}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
signedUploadUrl | string (uri) | |
url | string |
The website's image library
GET /organizations/{organizationId}/website/media
The business's uploaded images and the stock photos it has used, most recently used first, paged by cursor. stockProviders lists the photo libraries that can be searched (none: stock search is off). Read-only.
| Permission | website.read (Owner, Admin, Staff) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | website_media (toolset marketing) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
cursor | query | string | No | Up to 100 characters. |
limit | query | integer | No | 1–200. |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/media" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
items | array of object | |
items[].id | string | |
items[].source | enum | One of UPLOAD, STOCK. |
items[].url | string | What to put in a section's image field. |
items[].thumbUrl | string or null | |
items[].alt | string | |
items[].width | integer or null | |
items[].height | integer or null | |
items[].provider | string or null | Stock: UNSPLASH or PEXELS. |
items[].providerId | string or null | |
items[].author | string or null | Stock: the photographer, credited on the site. |
items[].authorUrl | string or null | |
items[].sourceUrl | string or null | |
items[].createdAt | string (date-time) | |
items[].lastUsedAt | string (date-time) | |
nextCursor | string or null | |
stockProviders | array of enum |
Add an uploaded image to the library
POST /organizations/{organizationId}/website/media/uploads
Step 2 of uploading, after the PUT to the signed URL: the image joins the library. url must be one POST /website/image-upload-url gave this business (404 otherwise).
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_confirmImageUpload (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Up to 300 characters. |
width | integer or null | No | 1–20000. |
height | integer or null | No | 1–20000. |
alt | string or null | No | Up to 300 characters. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/media/uploads" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
source | enum | One of UPLOAD, STOCK. |
url | string | What to put in a section's image field. |
thumbUrl | string or null | |
alt | string | |
width | integer or null | |
height | integer or null | |
provider | string or null | Stock: UNSPLASH or PEXELS. |
providerId | string or null | |
author | string or null | Stock: the photographer, credited on the site. |
authorUrl | string or null | |
sourceUrl | string or null | |
createdAt | string (date-time) | |
lastUsedAt | string (date-time) |
Search free stock photos
GET /organizations/{organizationId}/website/stock-photos
Landscape photos for query from Unsplash (or Pexels when Unsplash is unavailable; provider picks one), free for commercial use with a credit the site shows. Searching uses nothing; POST /website/media/stock with a result's provider and id to use one. Rate-limited per business. Read-only.
| Permission | website.manage (Owner, Admin) |
| Risk | read, OAuth scope skuflow.read. Read: reads only. |
| MCP tool | website_searchStockPhotos (toolset marketing) |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
query | query | string | Yes | Up to 100 characters. |
page | query | integer | No | 1–50. |
provider | query | enum | No | One of UNSPLASH, PEXELS. |
Example
curl "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/stock-photos?query=string" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
provider | enum or null | One of UNSPLASH, PEXELS. |
photos | array of object | |
photos[].provider | enum | One of UNSPLASH, PEXELS. |
photos[].id | string | Pass with provider to POST /website/media/stock to use it. |
photos[].url | string | |
photos[].thumbUrl | string | |
photos[].width | integer | |
photos[].height | integer | |
photos[].alt | string | |
photos[].author | string | |
photos[].authorUrl | string | |
photos[].sourceUrl | string | |
page | integer | |
hasMore | boolean |
Use a stock photo
POST /organizations/{organizationId}/website/media/stock
Adds a stock photo (by provider and id from GET /website/stock-photos) to the library with its credit and reports the use to the provider, as its licence asks. Answers the library item: put its url in a section. Using the same photo again answers the same item.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_useStockPhoto (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
provider | enum | Yes | One of UNSPLASH, PEXELS. |
photoId | string | Yes | Up to 100 characters. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/media/stock" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"provider":"UNSPLASH","photoId":"photo_id"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
source | enum | One of UPLOAD, STOCK. |
url | string | What to put in a section's image field. |
thumbUrl | string or null | |
alt | string | |
width | integer or null | |
height | integer or null | |
provider | string or null | Stock: UNSPLASH or PEXELS. |
providerId | string or null | |
author | string or null | Stock: the photographer, credited on the site. |
authorUrl | string or null | |
sourceUrl | string or null | |
createdAt | string (date-time) | |
lastUsedAt | string (date-time) |
Remove an image from the library
DELETE /organizations/{organizationId}/website/media/{mediaId}
Takes the image out of the library. Pages that already show it keep showing it (and keep its credit) until changed.
| Permission | website.manage (Owner, Admin) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | website_deleteMedia (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
| Approval | Over MCP, this tool always waits for your approval in SKUFlow. See Approvals. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
mediaId | path | string | Yes |
Example
curl -X DELETE "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/media/{mediaId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
ok | true |
Add a page
POST /organizations/{organizationId}/website/pages
Adds a draft page at the end of the navigation. sections is validated per type. 409 when the address is taken.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_createPage (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | The page address: "" (home only) or words-with-hyphens. Up to 60 characters. |
title | string | Yes | Up to 120 characters. |
showInNav | boolean | No | |
seoTitle | string or null | No | Up to 70 characters. |
seoDescription | string or null | No | Up to 170 characters. |
sections | array of any | No | Every section of the page in order: { id, type, hidden, props }; replaces the list. Up to 30 items. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/pages" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"slug":"string","title":"string"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
slug | string | The page address; "" is the home page. |
title | string | |
showInNav | boolean | |
sortOrder | integer | |
seoTitle | string or null | |
seoDescription | string or null | |
sections | array of object | |
sections[].id | string | |
sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
sections[].hidden | boolean | |
sections[].props | object | The section's fields; their schema per type is in. |
updatedAt | string (date-time) |
Edit a page
PATCH /organizations/{organizationId}/website/pages/{pageId}
Changes the fields sent on a draft page; sections replaces the whole list (validated per type; a 400 names the field). The home page keeps its address.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_updatePage (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
pageId | path | string | Yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | No | The page address: "" (home only) or words-with-hyphens. Up to 60 characters. |
title | string | No | Up to 120 characters. |
showInNav | boolean | No | |
seoTitle | string or null | No | Up to 70 characters. |
seoDescription | string or null | No | Up to 170 characters. |
sections | array of any | No | Every section of the page in order: { id, type, hidden, props }; replaces the list. Up to 30 items. |
Example
curl -X PATCH "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/pages/{pageId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"slug":"string","title":"string","showInNav":true}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
slug | string | The page address; "" is the home page. |
title | string | |
showInNav | boolean | |
sortOrder | integer | |
seoTitle | string or null | |
seoDescription | string or null | |
sections | array of object | |
sections[].id | string | |
sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
sections[].hidden | boolean | |
sections[].props | object | The section's fields; their schema per type is in. |
updatedAt | string (date-time) |
Delete a page
DELETE /organizations/{organizationId}/website/pages/{pageId}
Destructive: removes the draft page (the home page cannot be deleted). The live site keeps it until published again.
| Permission | website.manage (Owner, Admin) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | website_deletePage (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
| Approval | Over MCP, this tool always waits for your approval in SKUFlow. See Approvals. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
pageId | path | string | Yes |
Example
curl -X DELETE "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/pages/{pageId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
ok | true |
Reorder the pages
POST /organizations/{organizationId}/website/pages/order
Sets the navigation order: pageIds lists every page of the site once.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_reorderPages (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
pageIds | array of string | Yes | Up to 50 items. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/pages/order" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"pageIds":["page_id"]}'Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Connect a custom domain
POST /organizations/{organizationId}/website/domains
Adds a domain (e.g. www.example.com) as PENDING. The business then adds every record in records: the ownership TXT and a CNAME to cnameTarget (A records for a bare domain); the domain becomes ACTIVE (served, certificate on demand) once the checks pass, checked every ten minutes or with POST …/verify. Up to five per site.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_addDomain (toolset marketing) |
| Plan | Custom domain: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
hostname | string | Yes | Up to 260 characters. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/domains" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"hostname":"string"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Check a domain's DNS now
POST /organizations/{organizationId}/website/domains/{domainId}/verify
Runs the DNS checks for one domain now; lastError says what is missing. ACTIVE domains serve the site.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_verifyDomain (toolset marketing) |
| Plan | Custom domain: the Grow plan and up. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
domainId | path | string | Yes |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/domains/{domainId}/verify" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Make a domain the main address
POST /organizations/{organizationId}/website/domains/{domainId}/primary
The site's canonical address (search engines, sitemap) becomes this domain once it is ACTIVE.
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_setPrimaryDomain (toolset marketing) |
| Plan | Custom domain: the Grow plan and up. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
domainId | path | string | Yes |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/domains/{domainId}/primary" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Disconnect a domain
DELETE /organizations/{organizationId}/website/domains/{domainId}
Destructive: the domain stops serving the site at once.
| Permission | website.manage (Owner, Admin) |
| Risk | sensitive, OAuth scope skuflow.sensitive. Sensitive: moves money, deletes, sends or shows something to a customer, or grants or revokes access. |
| MCP tool | website_removeDomain (toolset marketing) |
| Approval | Over MCP, this tool always waits for your approval in SKUFlow. See Approvals. |
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
domainId | path | string | Yes |
Example
curl -X DELETE "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/domains/{domainId}" \
-H "Authorization: Bearer $SKUFLOW_API_KEY"Response
200: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
subdomain | string | |
status | enum | One of DRAFT, PUBLISHED, UNPUBLISHED. |
theme | object | |
theme.primaryColor | string | |
theme.accentColor | string | |
theme.font | enum | One of modern, classic, friendly, bold, elegant, geometric. |
theme.radius | enum | One of square, soft, round. |
theme.headerStyle | enum | One of light, dark, brand. |
theme.style | string | Up to 40 characters. |
profile | object | |
profile.tagline | string | Up to 160 characters. |
profile.phone | string | Up to 40 characters. |
profile.email | empty string or string (email) | |
profile.address | string | Up to 300 characters. |
profile.hours | object | |
profile.hoursNote | string | Up to 200 characters. |
profile.licenseText | string | Up to 200 characters. |
profile.serviceAreaText | string | Up to 600 characters. |
profile.social | object | |
profile.social.facebook | empty string or string (uri) | |
profile.social.instagram | empty string or string (uri) | |
profile.social.google | empty string or string (uri) | |
profile.social.yelp | empty string or string (uri) | |
profile.social.nextdoor | empty string or string (uri) | |
profile.logoUrl | string | Up to 2000 characters. |
profile.headerBrand | enum | One of logoAndName, logo, name. |
profile.logoSize | enum | One of small, medium, large. |
profile.siteName | string | Up to 120 characters. |
profile.privacyUrl | empty string or string (uri) | |
profile.termsUrl | empty string or string (uri) | |
profile.footer | object | |
profile.footer.showTagline | boolean | |
profile.footer.showContact | boolean | |
profile.footer.showHours | boolean | |
profile.footer.showSocial | boolean | |
profile.footer.showPoweredBy | boolean | |
profile.footer.copyright | string | Up to 200 characters. |
seoTitle | string or null | |
seoDescription | string or null | |
publishedAt | string (date-time) or null | |
generatedBy | string or null | AI (written by Claude) or TEMPLATE. |
trade | string or null | The trade template it was built from (e.g. screen-printing, embroidery); null when none. |
defaultUrl | string or null | https://{subdomain}.{sites domain}, when one is configured. |
path | string | /sites/{subdomain} on the app: always works. |
hasUnpublishedChanges | boolean | |
pages | array of object | |
pages[].id | string | |
pages[].slug | string | The page address; "" is the home page. |
pages[].title | string | |
pages[].showInNav | boolean | |
pages[].sortOrder | integer | |
pages[].seoTitle | string or null | |
pages[].seoDescription | string or null | |
pages[].sections | array of object | |
pages[].sections[].id | string | |
pages[].sections[].type | string | hero, trust, services, about, testimonials, booking (the request form), serviceArea, faq, cta, contact, gallery or text. |
pages[].sections[].hidden | boolean | |
pages[].sections[].props | object | The section's fields; their schema per type is in. |
pages[].updatedAt | string (date-time) | |
domains | array of object | |
domains[].id | string | |
domains[].hostname | string | |
domains[].status | enum | One of PENDING, ACTIVE, FAILED. |
domains[].verificationToken | string | The value of the TXT record at recordName. |
domains[].recordName | string | |
domains[].cnameTarget | string or null | Where the domain's CNAME (or A records) must point. |
domains[].apex | boolean | A bare domain (example.com): A records instead of a CNAME. |
domains[].records | array of object | Every DNS record to add: the ownership TXT, then the CNAME (or A records). |
domains[].records[].type | enum | One of TXT, CNAME, A. |
domains[].records[].name | string | Relative to the domain, as DNS providers ask: "@", "www", "_skuflow.www". |
domains[].records[].fullName | string | |
domains[].records[].value | string | |
domains[].records[].purpose | enum | One of verify, route. |
domains[].verifiedAt | string (date-time) or null | |
domains[].lastCheckedAt | string (date-time) or null | |
domains[].lastError | string or null | |
domains[].isPrimary | boolean | |
domains[].createdAt | string (date-time) | |
createdAt | string (date-time) | |
updatedAt | string (date-time) |
Add an image to the website library (content in the request)
POST /organizations/{organizationId}/website/media/content
Adds a JPEG, PNG or WebP image to the website's library in one call: send contentBase64 or a public sourceUrl (up to 10 MB). Returns the library entry; put its url in a section's image field (hero, about, gallery) with website.updatePage. Needs a website (website.create first).
| Permission | website.manage (Owner, Admin) |
| Risk | write, OAuth scope skuflow.write. Write: creates or changes records inside the business; nothing leaves it. |
| MCP tool | website_uploadImage (toolset marketing) |
| Plan | Website builder: the Grow plan and up. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
alt | string or null | No | Alternative text describing the image. Up to 300 characters. |
fileName | string | No | The file's name, e.g. receipt.jpg (default: from the URL, else 'file'). Up to 500 characters. |
contentType | string | No | MIME type, e.g. image/jpeg or application/pdf. Optional: taken from a data: URL, the URL's answer or the file's first bytes. Up to 200 characters. |
contentBase64 | string | No | The file's bytes as base64 (a data: URL is accepted too). Up to 10 MB. Give this or sourceUrl. |
sourceUrl | string (uri) | No | A public http(s) URL SKUFlow downloads the file from (up to 10 MB, 20 s, 3 redirects; private and internal addresses are refused). Give this or contentBase64. Up to 2000 characters. |
Example
curl -X POST "https://app.skuflow.ai/api/organizations/$ORGANIZATION_ID/website/media/content" \
-H "Authorization: Bearer $SKUFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{"alt":"string","fileName":"string","contentType":"string"}'Response
201: the answer's fields
| Field | Type | Description |
|---|---|---|
id | string | |
source | enum | One of UPLOAD, STOCK. |
url | string | What to put in a section's image field. |
thumbUrl | string or null | |
alt | string | |
width | integer or null | |
height | integer or null | |
provider | string or null | Stock: UNSPLASH or PEXELS. |
providerId | string or null | |
author | string or null | Stock: the photographer, credited on the site. |
authorUrl | string or null | |
sourceUrl | string or null | |
createdAt | string (date-time) | |
lastUsedAt | string (date-time) |