Dent
Dent spins up marketing Sites that sell and deliver digital Products. Operate it through the first-party schema-driven API. Vocabulary is strict: Funnel, Funnel Step, Offer, Product, Order, Cart, Checkout, Journey, Experiment, Conversion, Link, Session, Visitor Event, Attribution, Designer, Design, Designable, Site, Page, Article, Space, Course.
Process
-
Establish the session before touching data.
-
IF
dentis not on PATH, run each Dent CLI command asnpx @parkerlabs/dent@latest <command>. -
Run the local checks:
dent check dent whoami -
If the session-start check or
dent checksays the skill is stale, update the skill before a write. Use the update reference instead of asking the user to touch a terminal. -
If
dent whoamifails, rundent login. The standalone CLI starts Dent's OAuth 2.0 Authorization Code + PKCE login against production Dent by default (https://ondent.app): it opens the browser, the operator signs in and clicks Authorize, the loopback callback receives the authorization code, and the CLI exchanges code + verifier before saving the token. This is the same flow an agent can guide; the agent layer does not replace the CLI. -
If loopback cannot reach the CLI (remote browser, web agent, locked-down machine), run
dent login --copy-code. The operator authorizes in the browser, copies the short code Dent shows, and pastes it into the CLI. The code still exchanges only with that CLI's PKCE verifier. -
Never ask the operator to create a personal access token for normal login. Never put a token in argv. Explicit token input remains only for power-user recovery with
--token-promptor--stdin.dent login dent login --copy-code -
Use
dent setuponly when developing against a local Dent repo. It switches the CLI and skill to the local target while preserving the live credential. Usedent resetto switch back to the live target without re-authenticating either target. -
Use
dent logoutwhen the active target's stored credential should be removed. -
Power users may set the credential variables below.
DENT_TARGETselectsliveorlocal;DENT_SITE_URLandDENT_API_KEYeach override the matching active stored credential and fall back to stored config for the other:export DENT_TARGET=live export DENT_SITE_URL=https://example.com export DENT_API_KEY=...
-
-
Fetch the schema catalog first; never guess entity names, fields, actions, or routes.
- Catalog:
GET /api/v1/schema/. - Local development target switching is owned by the CLI's
dent setupanddent reset; do not hardcode local Dent URLs in the skill. - Read the target entity's
fields,actions,parameters,mode,target, andaccessbefore writing. The Funnel Stepstatusfield is in the catalog; set it through the generic step update route. - Action bodies are top-level fields. Do not wrap them in
{attributes: ...}unless the catalog parameter is literally namedattributesand the action expects that wrapper from a non-generic endpoint. For Dent generic create/update/actions, send the fields directly.
dent schema dent schema funnels dent api funnels dent api courses 9203 sections dent api dent breakdown --param dimension=source --param period=last_month --param includeAdmin=true --param limit=5dent apiresolves HTTP methods from the tenant schema catalog for each invocation:readactions useGET,writeandremoteactions usePOST, anddestroyactions useDELETE.--methodis the explicit override. Do not infer a write from positional shape; a bare nested collection likedent api courses 9203 sectionsis a list (GET). Creates require--dataor an explicit method. - Catalog:
-
Use the generic route grammar the catalog confirms, and route every Page surface through interview, writing, then design.
GET /api/v1/{entities}lists records.GET /api/v1/{entities}/{id}reads one record.POST /api/v1/{entities}creates a record.POST /api/v1/{entities}/{id}updates a record.DELETE /api/v1/{entities}/{id}deletes when the catalog and the user intent allow it.GET|POST|DELETE /api/v1/{entities}/{action}invokes a collection action.GET|POST|DELETE /api/v1/{entities}/{id}/{action}invokes a member action.- Nested creates/reads use the parent route the catalog exposes, for example:
POST /api/v1/funnels/{funnelId}/stepsPOST /api/v1/funnels/{funnelId}/steps/{stepId}POST /api/v1/courses/{courseId}/sectionsGET /api/v1/courses/{courseId}/sectionsGET /api/v1/courses/{courseId}/sections/{sectionId}POST /api/v1/sections/{sectionId}/lessonsGET /api/v1/sections/{sectionId}/lessonsGET /api/v1/sections/{sectionId}/lessons/{lessonId}
- For any Page, Article, Funnel Step, Checkout Funnel Step, Upsell Funnel Step, or redesign, use this exact order:
- FIRST establish the brief by interviewing the operator in plain language →
references/interview.md. - THEN write the finished copy →
references/writing/copywriting.mdplus the matching writing leaf. - THEN design from the brief and finished copy →
references/design/designing.mdplus the matching design leaf.
- FIRST establish the brief by interviewing the operator in plain language →
- Match the leaf to the surface:
- Opt-in Page or lead-capture Funnel Step →
references/writing/optin-page.mdandreferences/design/optin-page.md. - Sales Page or sales Funnel Step →
references/writing/sales-page.mdandreferences/design/sales-page.md. - Checkout Funnel Step →
references/writing/sales-page.mdand the Checkout starter insidereferences/design/sales-page.md. - Upsell Funnel Step →
references/writing/upsell-page.mdandreferences/design/upsell-page.md. - Thank-you Page or Funnel Step →
references/writing/thank-you-page.mdandreferences/design/thank-you-page.md. - Article or content Page →
references/writing/article.mdandreferences/design/article.md.
- Opt-in Page or lead-capture Funnel Step →
- Do not duplicate the reference content inline. Use the references to produce the brief, copy, and Designer JSON, then send the resulting Design through the API owner.
Designer write contracts:
- Read a Design before replacing it. Design reads return
{owner, design, revision}; send{revision, design}toreplace-design. A stale revision returns409; read again and deliberately reapply the change rather than retrying the stale document. - A form submit names the form by its authored form id: the
formvalue is the id the author gave the form, not a label or the Designer element key. Send the rendered owner, holder when present, andlink_idhidden values unchanged. - Checkout submits by Cart, not by Funnel Step or checkout element:
POST /api/v1/checkout/{cartId}/submit. Establish the Cart by visiting the public Checkout URL first and keep that cookie jar. - Media creation is
POST /api/v1/media/upload-urlwithfilename→PUTbytes to the returned URL →POST /api/v1/media/processwith the returnedkey. NeverPOST /api/v1/mediadirectly. - Script elements are the only JavaScript path and are first-class Designer elements. A script element sets
position: "head"orposition: "body"and exactly one of a reusablescriptkey or inlinecode. The renderer emitsposition: "body"scripts at the end of the document body (external ones withdefer) andposition: "head"scripts in the head. So DOM-touching code in a head script needs aDOMContentLoadedguard; a body script already runs after the page markup. raw-htmlis for inert markup only. It rejects<script>tags and event-handler attributes such asonclick. URL-bearing attributes allow onlyhttp,https,mailto,tel, relative, and anchor URLs; other schemes such asjavascript,data, andvbscriptreturn422. Rejection happens at the write boundary, not by silent stripping. Use a script element for JavaScript.- Dynamic
{{ ... }}tags interpolate only in an element's first-class fields — text, classes, style, and attributes. Araw-htmlelement's markup and a script element'scodeare emitted verbatim and are never interpolated; put dynamic values in real fields, or read them in the script itself. In a field, an unknown{{ var }}resolves to the empty string and an unbound@rootchain stays verbatim. Publish validation still scans{{ }}everywhere, raw-html and script code included, so write\{{for a literal{{— it passes strict validation in any carrier, and in a field it also renders as{{. GET /api/v1/settings/themereturns the writable theme block undertheme. Send that same public shape toPOST /api/v1/settings/save-theme:colors,colorsDark,radius,fontSize,tracking,shadow,fontSans,fontMono,fontSerif,presetStyle,presetBaseColor,presetThemeColor,locks, and the returnedartifactwhen round-tripping the whole block. Unknown keys return422.
-
Build an opt-in Funnel with a thank-you Funnel Step.
-
Start with the page creation sequence for both public steps:
- Opt-in Funnel Step → interview brief, opt-in copy, opt-in Design.
- Thank-you Funnel Step → interview brief, thank-you copy, thank-you Design.
-
Create the Funnel:
POST /api/v1/funnelswith{ "name": "Lead magnet opt-in" }. -
Use
homeStepIdfrom the response as the opt-in Funnel Step. -
Create the thank-you Funnel Step:
POST /api/v1/funnels/{funnelId}/stepswith{ "title": "Thank You" }. -
Author the opt-in Design through the Funnel Step, not WordPress. Every
{{ fields.* }}value used by a behavior must have a matching input whoseconfig.nameis that field. -
Read the current Funnel Step Design, then send
{ "revision": revision, "design": design }toPOST /api/v1/funnels/{funnelId}/steps/{stepId}/replace-design. -
Put a polished thank-you Design on the thank-you Funnel Step so the public URL is not empty.
-
Read the current Thank-you Step Design, then send its revision with the finished Design to
POST /api/v1/funnels/{funnelId}/steps/{thankYouStepId}/replace-design. -
Publish every public Funnel Step before opening
viewUrlor submitting the form:{"status": "published"}Send it to
POST /api/v1/funnels/{funnelId}/steps/{stepId}for the opt-in and thank-you Funnel Steps. Public URLs and public form submits require a published Funnel Step on an active Funnel. -
Read the Funnel Step with
GET /api/v1/funnels/{funnelId}/steps/{stepId}afterreplace-designand publishing. Confirmstatus: "published",viewUrl,hasOptinAction: truefor an opt-in step, andhasBuilderbefore opening the public URL.replace-designreturns the saved Design, not the Step metadata. -
Verify the public opt-in path with a real pageview and form submit:
GETthe opt-in StepviewUrlwith a cookie jar to render the form and establish the Visitor Session.POST /api/v1/forms/submitwith the same cookies and body{ "owner": {"type": "step", "id": stepId}, "form": "<authored form id>", "fields": {"email": "visitor@example.com", "name": "Visitor Name"} }.formis the authored form id per the write contract above.- Treat
success: trueand the returnedredirectUrlas the submit proof; followredirectUrlwhen the form redirects. - Find the captured Contact with
GET /api/v1/contacts?search=visitor@example.com.
-
Every Site render sets the
_vidvisitor cookie server-side, so a cookie-jarcurlis tracked as a Visitor — but only if itsUser-Agentis not bot-flagged. The crawler filter rejectscurl's defaultcurl/*agent, so send a real browserUser-Agentheader or the request identifies no Visitor. A tracked Visitor is still anonymous: a{{ contact.* }}personalized render needs that Visitor linked to a Contact, which happens when the same cookie jar submits an opt-in or Checkout (email capture). Submit first, keep the cookies, then the personalized render resolves.
-
-
Build a sales Funnel with Order bump, upsell, and thank-you Funnel Step.
-
Start with the page creation sequence for each public step:
- Sales or Checkout Funnel Step → interview brief, sales/Checkout copy, sales or Checkout Design.
- Upsell Funnel Step → interview brief, upsell copy, upsell Design.
- Thank-you Funnel Step → interview brief, thank-you copy, thank-you Design.
-
Create each Product:
POST /api/v1/productswith top-level fields such as{ "name": "Core Course", "value": 97, "status": "publish" }. -
Create each Offer:
POST /api/v1/offerswith{ "name": "Core Offer", "price": 97, "active": true, "products": [{"productId": productId}] }. -
Create the Funnel and Funnel Steps:
POST /api/v1/funnelsfor the Funnel.POST /api/v1/funnels/{funnelId}/stepsfor Checkout.POST /api/v1/funnels/{funnelId}/stepsfor Upsell.POST /api/v1/funnels/{funnelId}/stepsfor Thank You.
-
Attach the main Offer and bump to Checkout:
{"offers": [{"offerId": 123}], "bumps": [{"offerId": 456}]}Send it to
POST /api/v1/funnels/{funnelId}/steps/{checkoutStepId}. -
Attach the upsell Offer to the Upsell Funnel Step with
POST /api/v1/funnels/{funnelId}/steps/{upsellStepId}and{ "offers": [{"offerId": upsellOfferId}] }. -
Read each Step Design first, then send each finished Design with that read's
revisionto its owner: -
Checkout Design →
POST /api/v1/funnels/{funnelId}/steps/{checkoutStepId}/replace-design. -
Upsell Design →
POST /api/v1/funnels/{funnelId}/steps/{upsellStepId}/replace-design. -
Thank-you Design →
POST /api/v1/funnels/{funnelId}/steps/{thankYouStepId}/replace-design. -
Publish every public Funnel Step before opening
viewUrlor submitting Checkout:{"status": "published"}Send it to
POST /api/v1/funnels/{funnelId}/steps/{stepId}for the Checkout, Upsell, and Thank You Funnel Steps. Public URLs, form submits, and Checkout require a published Funnel Step on an active Funnel. -
Start Checkout like a real Visitor:
GETthe Checkout Funnel StepviewUrlfirst and keep the response cookies. That pageview creates the Funnel Session/Journey/Cart context. Send laterPOST /api/v1/funnel/cartmutations and Checkout submit calls with those same cookies; without the step pageview they return401 No active funnel session.
-
-
Build a Dent-selling Funnel that delivers Dent access and triggers tenant provisioning.
-
Start with the page creation sequence for the Dent-selling Checkout, Upsell, and Thank You Funnel Steps so the Funnel presents the Offer like a polished sales surface, not a wireframe.
-
Provisioning is Product delivery. Configure the Product included in the sold Offer with the access grants and provisioning webhook it must deliver after purchase; do not add a separate operator API step.
-
Create or choose the Webhook Endpoint for the provisioning webhook target.
customHeadersis write-only; use it only for the secret that target expects:{ "name": "Dent Provisioning", "url": "https://provisioning.example.com/dent/product-delivery", "events": [], "customHeaders": {"Authorization": "Bearer <provisioning-secret>"}, "enabled": true }Send it to
POST /api/v1/webhook-endpoints. -
Create the Dent Product with Product delivery configuration. Use
courseIdsorspaceIdsfor Dent access grants the buyer receives, andwebhooksfor the provisioning webhook payload:{ "name": "Dent Pro", "value": 197, "status": "publish", "courseIds": [321], "spaceIds": [654], "webhooks": [{ "endpointId": 17, "parameters": { "email": "{{ contact.email }}", "first_name": "{{ contact.firstName }}", "last_name": "{{ contact.lastName }}", "plan": "pro", "order_id": "{{ order.id }}", "source": "dent-selling-funnel", "slug": "buyer-tenant-slug", "name": "Buyer Tenant" } }] }Send it to
POST /api/v1/products. -
Create the Offer for that Product, then build Checkout, Upsell, and Thank You Funnel Steps exactly as in step 5.
-
Publish every public Funnel Step exactly as in step 5 before opening
viewUrl, changing the Cart, or submitting Checkout. -
Before Cart or Checkout API calls,
GETthe Checkout Funnel StepviewUrlwith a cookie jar and reuse those cookies forPOST /api/v1/funnel/cartand Checkout submit calls. -
A TestGateway purchase of the Offer creates the Order, runs Product delivery, grants the configured Course or Space access, posts
product.deliveryto the configured provisioning webhook, and records the Webhook Delivery response. Treat tenant creation as the provisioning webhook's downstream effect, not as a separate operator API step in this skill.
-
-
Create Articles and Pages as Designables.
- Start with the page creation sequence before writing the
designfield or callingreplace-design:- Article or content Page → interview brief, Article/content copy, Article/content Design.
- Opt-in Page → interview brief, opt-in copy, opt-in Design.
- Sales Page → interview brief, sales copy, sales Design.
- Thank-you Page → interview brief, thank-you copy, thank-you Design.
- Article:
POST /api/v1/articleswith top-level{ "title", "status", "excerpt", "slug", "design" }. Dent renders the Article content from the Design. - Page:
POST /api/v1/pageswith{ "title", "status", "slug" }, readGET /api/v1/pages/{pageId}/design, thenPOST /api/v1/pages/{pageId}/replace-designwith{ "revision": revision, "design": {"version": 1, "elements": [...] } }. - Read the saved Design with
GET /api/v1/pages/{pageId}/designorGET /api/v1/articles/{articleId}. - A Funnel Step is not a Page. Never call a Funnel Step a Page.
- Start with the page creation sequence before writing the
-
Iterate on Funnel Step Design until the operator approves.
- Read the current Design:
GET /api/v1/funnels/{funnelId}/steps/{stepId}/design. - Re-establish or update the brief by interviewing the operator in plain language about what the current Step must do differently.
- Rewrite or confirm the finished copy through
references/writing/copywriting.mdand the matching writing leaf before changing layout. - Redesign from the updated brief and finished copy through
references/design/designing.mdand the matching design leaf. - Replace it:
POST /api/v1/funnels/{funnelId}/steps/{stepId}/replace-designwith{ "revision": "<revision from the read>", "design": ... }. - If the operator will inspect the public
viewUrl, publish the Funnel Step first withPOST /api/v1/funnels/{funnelId}/steps/{stepId}and{ "status": "published" }. - Read the Funnel Step:
GET /api/v1/funnels/{funnelId}/steps/{stepId}. - Open or refresh
viewUrl, inspect the rendered Site result, then repeat. If a render needs WordPress-only admin routes, stop and report the gap instead of using them.
- Read the current Design:
-
Create Components and Templates.
-
Template create works first-party:
POST /api/v1/templateswith{ "name", "type": "single", "entityType": "page", "status": "publish", "design" }. -
Read Template Design with
GET /api/v1/templates/{templateId}/design. -
Component create works first-party:
POST /api/v1/componentswith{ "key", "name", "status": "publish", "design" }. -
Component Design uses variants:
{ "design": { "version": 1, "defaultVariant": "base", "properties": [], "slots": [], "variants": [{ "key": "base", "name": "Base", "elements": [{"key": "component-root", "type": "section", "children": []}] }] } } -
Read Component Design with
GET /api/v1/components/{componentId}/design. -
Replace Component Design with
POST /api/v1/components/{componentId}/replace-design, sending the revision from the preceding Design read.
-
-
Query Analytics and break down revenue.
-
Use
GET /api/v1/dent/breakdown?dimension=source&period=last_month&includeAdmin=true&limit=5for breakdowns. -
Use
GET /api/v1/orders/revenue-by-source?period=last_month&attribution=first_touchfor source revenue. -
breakdownacceptsdimension. Available dimensions:source,referrer,campaign,country,region,city,device,browser,entry_pages. -
In the CLI variant, pass catalog action parameters as query parameters:
dent api dent breakdown --param dimension=source --param period=last_month --param includeAdmin=true --param limit=5
- Set up selling: Products, Offers, Courses, Spaces.
- Course:
POST /api/v1/courseswith{ "title", "slug", "description", "status": "published", "privacy": "secret" }. - Product that grants a Course:
POST /api/v1/productswith{ "name", "value", "status": "publish", "courseIds": [courseId] }. - Product that grants a Space: use
spaceIds: [spaceId]. - Offer:
POST /api/v1/offerswith{ "name", "price", "active": true, "products": [{"productId": productId}] }. - Users buy Offers. Products are what Offers include.
- Add Course content and manage Spaces.
- Section:
POST /api/v1/courses/{courseId}/sectionswith{ "title", "position", "status": "published" }. - Read ordered Course Sections with
GET /api/v1/courses/{courseId}/sections. - Lesson:
POST /api/v1/sections/{sectionId}/lessonswith{ "title", "body", "position", "status": "published", "contentType": "text" }. - Read ordered Section Lessons with
GET /api/v1/sections/{sectionId}/lessons. - Space:
POST /api/v1/spaceswith{ "title", "slug", "privacy": "secret", "status": "published" }. - Contacts are created by identify flows such as opt-in forms and Checkout email capture; Accounts do not author Contacts directly.
- Add an existing Contact to a Space:
POST /api/v1/spaces/{spaceId}/add-memberwith{ "contact_id": contactId, "role": "member" }. - Read Space members:
GET /api/v1/spaces/{spaceId}/list-members.
- Preserve correctness while operating.
- Use Dent's words exactly; never write Product when the object is an Offer, never write Page when the object is a Funnel Step.
- Prefer small read-after-write checks and include exact ids changed in your answer.
- Show Dent's error status and body. Do not invent fallbacks.
- Do not use
/wp/wp-login.php, WordPress admin-ajax, Bricks save routes, or WordPress REST routes for these workflows. A need for those routes is a first-party API gap to report. - Never place a bearer token in argv, a project file, a report, or a commit.
References (each solves one problem)
- The installed Dent skill is stale → references/updating-the-skill.md
- Checking Dent API route grammar and auth details → references/api.md
- Establishing the Page, Article, or Funnel Step brief in operator language → references/interview.md
- Writing Page, Article, and Funnel Step copy before design → references/writing/copywriting.md
- Writing opt-in Page or lead-capture Funnel Step copy → references/writing/optin-page.md
- Writing sales Page, sales Funnel Step, or Checkout copy → references/writing/sales-page.md
- Writing one-click upsell Funnel Step copy → references/writing/upsell-page.md
- Writing thank-you Page or confirmation Funnel Step copy → references/writing/thank-you-page.md
- Writing Article or content Page copy → references/writing/article.md
- Designing Page, Article, and Funnel Step visuals after copy → references/design/designing.md
- Designing opt-in Page or lead-capture Funnel Step visuals → references/design/optin-page.md
- Designing sales Page, sales Funnel Step, or Checkout visuals → references/design/sales-page.md
- Designing one-click upsell Funnel Step visuals → references/design/upsell-page.md
- Designing thank-you Page or confirmation Funnel Step visuals → references/design/thank-you-page.md
- Designing Article or content Page visuals → references/design/article.md