Create an artifact
Create a live dist from a curated template or compact Emmet source.
This path starts from a long bearer key, creates the app descriptor in your namespace,
then publishes a live dist. POST /apps never accepts a caller-chosen namespace; it
forces the descriptor namespace to the bearer principal and returns 409 if the app
already exists.
Use a short key scoped to the app and the REST uses you need:
# docjourney: step=short-key
curl -s -X POST https://t.tini.works/token/short \
-H "authorization: Bearer <long-key>" \
-H "content-type: application/json" \
-d '{"ttlSeconds":900,"scope":{"uses":["author_app","publish_content","await_app","input_app","render_app"],"app_ids":["journey"]}}'
# -> {"token":"<short-key>","issuedAt":"...","expiresAt":"...","ttlSeconds":900}
# docjourney: step=create-app
curl -s -X POST https://t.tini.works/apps \
-H "authorization: Bearer <short-key>" \
-H "content-type: application/json" \
-d '{"appId":"journey","authMode":"anonymous"}'
# -> {"ok":true,"namespace":"<your-handle>","appId":"journey","authMode":"anonymous"}For the canonical curl-only journey and patch-vs-replace rule, see
docs/AUTHORING.md section 14.4.
Pick a create mode
| Mode | Use it when | Request |
|---|---|---|
| Template | You want a known safe page shape with typed slots. | POST /apps/{id}/content?target=dist&mode=template |
| Emmet | You want token-efficient HTML from compact source. | POST /apps/{id}/content?target=dist&mode=emmet |
Both modes require target=dist. Success computes the artifact sha256, stores the
artifact, and updates promotion.distRef in the same request.
Templates
Curated templates are typed and escaped by default. Missing, unknown, or mistyped values fail closed. URL slots must be absolute allowed URLs. The rendered output is rejected if it contains active content such as script tags, event handler attributes, unsafe URL schemes, or CSS active tokens.
| Template | Slot types |
|---|---|
curated:contact-form-v1 | text: body_class, page_title, form_title, intro_text, submit_label, privacy_note; URL: action_url; lists: fields, helper_points |
curated:ops-dashboard-v1 | text: body_class, app_title, owner_label, status_text, page_title, primary_metric, secondary_metric; URL: docs_url; lists: alerts, actions |
# Create a dist artifact from a curated template and update promotion.distRef.
# docjourney: step=template-create
curl -s -X POST "https://t.tini.works/apps/journey/content?target=dist&mode=template" \
-H "authorization: Bearer <short-key>" \
-H "content-type: application/json" \
-d '{"template_id":"curated:contact-form-v1","values":{"body_class":"form-page","page_title":"Doc Journey","form_title":"Contact PKR Docs","intro_text":"Patch this page through the artifact edge.","action_url":"https://example.com/contact","fields":["Name","Email"],"helper_points":["Escaped by default","No local packaging step"],"submit_label":"Send","privacy_note":"No spam."}}'
# -> {"artifact_id":"<sha256>","app":"journey","live":true,"hash":"<sha256>","target":"dist","mode":"template","template_id":"curated:contact-form-v1","template_hash":"<sha256>","manifest_hash":"<sha256>","values_hash":"<sha256>","expanded_hash":"<sha256>","engine_version":"template-v1","active_content":false}curl -s -X POST "https://t.tini.works/apps/journey/content?target=dist&mode=template" \
-H "authorization: Bearer <short-key>" \
-H "content-type: application/json" \
-d '{"template_id":"curated:ops-dashboard-v1","values":{"body_class":"ops-dashboard","app_title":"Ops","owner_label":"SRE","status_text":"Ready","docs_url":"https://example.com/docs","page_title":"Live status","primary_metric":"99.99%","secondary_metric":"42 ms","alerts":["Queue < 5"],"actions":["Check deploy"]}}'
# -> {"artifact_id":"<sha256>","app":"journey","live":true,"hash":"<sha256>","target":"dist","mode":"template","template_id":"curated:ops-dashboard-v1","template_hash":"<sha256>","manifest_hash":"<sha256>","values_hash":"<sha256>","expanded_hash":"<sha256>","engine_version":"template-v1","active_content":false}Emmet
Emmet mode accepts plain text Emmet source or a JSON envelope {emmet, title, css}. It
is a deterministic safe subset for compact HTML, not arbitrary browser code.
Rejected fail-closed: unsupported syntax, script, link, meta http-equiv, on*
attributes, javascript: or data: URL schemes, and remote CSS via @import or
url(http...). Unsafe CSS in the css envelope is rejected too.
# Create a second dist artifact from an Emmet source or JSON envelope.
# docjourney: step=emmet-create
curl -s -X POST "https://t.tini.works/apps/journey/content?target=dist&mode=emmet" \
-H "authorization: Bearer <short-key>" \
-H "content-type: application/json" \
-d '{"emmet":"main#emmet-proof>(h1{Emmet proof}+button.cta[data-action=select]{Select})","title":"Emmet proof","css":"body{font-family:sans-serif}.cta{padding:8px}"}'
# -> {"artifact_id":"<sha256>","app":"journey","live":true,"hash":"<sha256>","target":"dist","mode":"emmet","source_hash":"<sha256>","expanded_hash":"<sha256>"}Template and Emmet are exchangeable. Create with compact Emmet when the first draft is
small, then patch index.html later. Create from a curated template when the page shape
matters more than source compactness, then patch only the changed anchor.