# Get tent analytics Source: https://docs.tented.ai/api-reference/analytics/get-tent-analytics /api-reference/openapi.json get /v1/analytics/tent Traffic analytics for a single published tent over the last 1–7 days. Note: this endpoint uses camelCase query parameters. # Get workspace analytics Source: https://docs.tented.ai/api-reference/analytics/get-workspace-analytics /api-reference/openapi.json get /v1/analytics/org Aggregate traffic analytics across all published tents in the workspace, including a per-path breakdown. `tent_id` is the literal string `org` in the response. # Approving & Managing Emails Source: https://docs.tented.ai/api-reference/approving-emails Approve an email for sending, then list, retrieve, and delete emails through the public API. ## Endpoints ```bash theme={null} POST /v1/emails/{emailId}/approve POST /v1/emails/{emailId}/unapprove GET /v1/emails GET /v1/emails/{emailId} DELETE /v1/emails/{emailId} ``` An email must be **approved** before [blasts](/api-reference/managing-email-blasts) and [triggered flows](/api-reference/managing-triggered-flows) can use it. Approval also gates deletion and unapproval: an email attached to a scheduled blast or an active flow cannot be unapproved or deleted. ## Approve and Unapprove ```bash theme={null} POST /v1/emails/{emailId}/approve POST /v1/emails/{emailId}/unapprove ``` Approving marks the email's newest **completed** version usable by blasts and flows. It requires `subject`, `from_name`, `from_address`, and `reply_to_email` to be populated; otherwise the API returns `400 Bad Request` with `error_code: "email_missing_required_headers"`. Set the missing headers via [`PATCH /v1/emails/{emailId}`](/api-reference/editing-emails#update-metadata) before retrying. Optionally send `{"version": }` as an optimistic-concurrency precondition — a mismatch with the newest completed version (normally equal to `current_version`) returns `409 Conflict` with `error_code: "approval_version_stale"`. While a generation is running, an approve **without** `version` is refused with `409 Conflict` and `error_code: "generation_in_progress"` — it would silently pin the pre-generation content for your next send; passing `version` explicitly still approves that already-completed version mid-generation. Unapproving is rejected while the email is scheduled in a blast (`email_in_scheduled_blast`) or used by an active flow (`email_in_active_flow`). ## List Emails ```bash theme={null} GET /v1/emails ``` ### Query Parameters | Parameter | Type | Default | Notes | | ------------ | -------- | ------------ | --------------------------------------------------------------------- | | `status` | `string` | `any` | `any`, `draft`, or `approved` | | `sort_by` | `string` | `updated_at` | `updated_at`, `created_at`, or `name`. Only applies when `status=any` | | `sort_order` | `string` | `desc` | `asc` or `desc` | | `limit` | `number` | `25` | Page size, `1`-`100` | | `cursor` | `string` | — | `next_cursor` from a previous response | Responses contain `emails` and `next_cursor`. An absent `next_cursor` means the list is exhausted. List items are the [email object](#retrieve-an-email) minus `latest_generation_status` and the `content_path` / `plain_text_path` fields — lists never inline HTML; fetch it per email via [`GET /v1/emails/{emailId}/content`](/api-reference/creating-emails#read-content). ## Retrieve an Email ```bash theme={null} GET /v1/emails/{emailId} ``` `200 OK` ```json theme={null} { "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "name": "Welcome email", "status": "draft", "subject": "Welcome to Acme", "preview_text": null, "from_name": "Acme", "from_address": "hello@acme.com", "reply_to_email": "support@acme.com", "current_version": 2, "approved_version": null, "number_of_iterations": 2, "plain_text_overridden": false, "plain_text_overridden_at": null, "plain_text_stale_after_html_iteration": false, "created_at": "2026-07-01T12:00:00.000Z", "updated_at": "2026-07-01T12:00:41.000Z", "created_by_name": "tented-api", "latest_generation_status": "completed", "content_path": "/v1/emails/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1/content", "plain_text_path": "/v1/emails/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1/plain-text" } ``` `status` is `draft` or `approved`. [Creating a blank draft](/api-reference/creating-emails#create-an-email) (`201 Created`) and [metadata updates](/api-reference/editing-emails#update-metadata) return this same shape. ## Delete an Email ```bash theme={null} DELETE /v1/emails/{emailId} ``` Returns `200 OK` with `{"email_id": "...", "deleted": true}`. Deletion is blocked with `409 Conflict` while the email is scheduled in a blast (`email_in_scheduled_blast`) or used by an active flow (`email_in_active_flow`). ## Common Errors | Status | Cause | | ------------------ | ------------------------------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body or field validation failure | | `400 Bad Request` | Approval attempted with missing sender headers (`email_missing_required_headers`) | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | Email does not exist in the workspace | | `409 Conflict` | Approval without a `version` while a generation is running (`generation_in_progress`) | | `409 Conflict` | Approval attempted with no completed generation (`email_not_ready`) | | `409 Conflict` | Approval version precondition failed (`approval_version_stale`) | | `409 Conflict` | Email is in use by a scheduled blast or active flow | Send an approved email to an audience as a one-time blast. # Authentication Source: https://docs.tented.ai/api-reference/authentication Authenticate to the Tented API with workspace-scoped bearer API keys. ## How Authentication Works The Tented API uses bearer API keys. Each key belongs to exactly one Tented workspace, and every request runs in that workspace's context. Send your key in the `Authorization` header: ```bash theme={null} Authorization: Bearer tented_your_api_key ``` If the key is valid, Tented resolves the associated workspace internally and authorizes the request for that workspace only. ## Creating an API Key Tented API keys are created inside Tented by a workspace admin. 1. Ask an admin to create an API key for your workspace. 2. Copy the raw key when it is shown. 3. Store it in your secrets manager or environment variables. Tented only returns the raw API key once at creation time. After that, only key metadata remains visible in the app. ## Request Example ```bash theme={null} curl --request GET \ --url https://api.tented.ai/v1/tents/00000000-0000-4000-8000-000000000000 \ --header "Authorization: Bearer $TENTED_API_KEY" ``` ## Common Authentication Failures | Status | Cause | | ------------------ | ---------------------------------------- | | `401 Unauthorized` | Missing `Authorization` header | | `401 Unauthorized` | Header is not in `Bearer ` format | | `401 Unauthorized` | API key is unknown, revoked, or expired | ## Security Notes * Treat the key like a password. * Never expose it in browser-side code. * Rotate or revoke it if you suspect leakage. * Use separate keys for separate systems when you want cleaner auditability. ## Workspace Scope An API key cannot cross workspace boundaries. For example: * A tent created with Workspace A's key cannot be fetched with Workspace B's key. * An asset uploaded with one workspace's key cannot be attached from another workspace. ## Required Headers | Header | Required | Notes | | --------------- | --------------------- | -------------------------------------------------------------- | | `Authorization` | Yes | `Bearer ` | | `Content-Type` | Yes for JSON requests | Use `application/json` for tent and contact write endpoints | | `Content-Type` | Yes for asset uploads | Use `multipart/form-data` for `POST /v1/tents/{tentId}/assets` | Learn how to attach files to a new or existing tent before generation. # Create custom fields Source: https://docs.tented.ai/api-reference/contact-fields/create-custom-fields /api-reference/openapi.json post /v1/contact-fields Create one custom field (send a single object) or up to 50 in one request (send `{"items": [...]}`). Bulk creation is atomic — if any item collides, nothing is created. Limits and collisions: - A workspace can have at most **200 active custom fields**. Exceeding the cap returns `409` with `error_code: "field_definition_cap_exceeded"` plus `limit`, `current_active_count`, and `attempted_count`. - An `api_name` that duplicates another item in the request or an existing (active or archived) custom field returns `409` with `error_code: "field_definition_collision"` and a `conflicts` array describing each collision. To reuse an archived field's name, restore it instead via `PATCH /v1/contact-fields/{fieldDefinitionId}` with `status: "active"`. - Standard field names (`email`, `first_name`, `company`, …) are reserved and return `400`. # Get rule metadata Source: https://docs.tented.ai/api-reference/contact-fields/get-rule-metadata /api-reference/openapi.json get /v1/contact-fields/rule-metadata Metadata for building audience rule trees (used by `POST /v1/contacts/search` and dynamic contact lists): every filterable field — the standard contact fields plus your active custom fields — with its value type and the operators that apply to it, and a catalog of operator labels for display. # List contact fields Source: https://docs.tented.ai/api-reference/contact-fields/list-contact-fields /api-reference/openapi.json get /v1/contact-fields List the contact field definitions in your workspace: the built-in standard fields (`type: "standard"` — always active and read-only) followed by your custom fields (`type: "custom"`). Custom fields are either `org_managed` (created in the UI or via this API) or `integration_managed` (owned by a connected integration). Archived custom fields are hidden unless `include_archived=true`. # Update a custom field Source: https://docs.tented.ai/api-reference/contact-fields/update-a-custom-field /api-reference/openapi.json patch /v1/contact-fields/{fieldDefinitionId} Update a custom field's display name, description, editability, default list visibility, or status. At least one property must be provided. - **Archive / restore:** set `status: "archived"` to archive a field (its values are retained but it stops appearing in default field lists), or `status: "active"` to restore it. - **System fields are read-only** — passing a standard field name (e.g. `email`) instead of a custom field's UUID returns `400` (`System fields are read-only`). The `api_name` and `value_type` of a custom field cannot be changed. - **Integration-managed fields** reject `display_name`, `description`, and `status` edits (`400`); only `is_editable` and `show_in_people_list_default` can be changed. # Execute an import Source: https://docs.tented.ai/api-reference/contact-imports/execute-an-import /api-reference/openapi.json post /v1/contacts/imports/{sessionId}/execute Approve the final column mappings and start the import. The import runs asynchronously in chunks — this endpoint returns `202` with an `import_run_id`; poll `GET /v1/contacts/imports/{importRunId}` until `status` is `completed` or `failed`. Every mapping's `header` must be a column from the session's CSV. Map a column to `null` (`target_field: null`, `target_kind: null`) to skip it. At least one column must map to the `email` or `phone` standard field — contacts are matched (deduplicated) by normalized email or phone. Each session can be executed once: re-executing a session whose latest run is `queued`, `running`, or `completed` returns `400`. A session whose only runs `failed` can be executed again. # Export results as CSV Source: https://docs.tented.ai/api-reference/contact-imports/export-results-as-csv /api-reference/openapi.json get /v1/contacts/imports/{importRunId}/export Download a CSV status report for an import run. The file has two sections: 1. A `metadata,value` summary of the run (status, timestamps, and the created/updated/skipped/error counts). 2. The actionable rows — only rows with status `skipped` or `failed` — with columns `rowNumber`, `status`, `reason`, `contactId`, `fieldIssues`, followed by the original source columns from your CSV, so each problem row can be fixed and re-imported. Successfully created/updated rows are summarized in the metadata section but not listed individually; for complete per-row outcomes (including successes), or for very large result sets, use the paginated `GET /v1/contacts/imports/{importRunId}/results` endpoint instead. # Get an import run Source: https://docs.tented.ai/api-reference/contact-imports/get-an-import-run /api-reference/openapi.json get /v1/contacts/imports/{importRunId} Fetch the status and progress counters of an import run. Runs move through `queued` → `running` → `completed` or `failed`. Rows are processed in chunks; `processed_rows`, `created_count`, `updated_count`, `skipped_count`, and `error_count` update as chunks complete, so you can poll this endpoint for live progress. # List per-row results Source: https://docs.tented.ai/api-reference/contact-imports/list-per-row-results /api-reference/openapi.json get /v1/contacts/imports/{importRunId}/results Paginated per-row outcomes of an import run, ordered by row number. Each result records what happened to one CSV row — `created`, `updated`, `skipped`, or `failed` — with the affected contact ID and any field-level validation issues. Use the `status` filter to pull just the rows that need attention (e.g. `status=failed`). # Preview an import Source: https://docs.tented.ai/api-reference/contact-imports/preview-an-import /api-reference/openapi.json post /v1/contacts/imports/{sessionId}/preview Parse the session's CSV and return everything needed to build the mapping step: the detected column headers, proposed column-to-field mappings (deterministic header matching plus AI-assisted suggestions), the catalog of available target fields (importable standard fields and your active custom fields), suggested new custom fields for unmapped columns (`field_creation_candidates`), a sample of rows, and validation issues found in that sample. No request body. Safe to call repeatedly — for example, after bulk-creating suggested custom fields via `POST /v1/contact-fields`, preview again to see the new fields mapped. `blocking_issues` must be resolved before executing: an import needs at least one column mapped to `email` or `phone` so contacts can be identified and deduplicated. # Start a contact import Source: https://docs.tented.ai/api-reference/contact-imports/start-a-contact-import /api-reference/openapi.json post /v1/contacts/imports Create a contact import session. Two modes, selected by `source`: - **`upload`** — returns a presigned S3 `PUT` URL. Upload the raw CSV bytes to `upload_url` with the returned `upload_headers` before the URL expires (`expires_in_seconds`, 15 minutes). Source files up to 350 MB are supported. Only CSV content types are accepted (`text/csv`, `application/csv`, `application/vnd.ms-excel`, `text/plain`). - **`rows`** — send rows inline as JSON objects (1–1,000 rows). Keys are column headers; the union of keys across all rows becomes the CSV header row. Values are stringified; `null` becomes an empty string. Inline request bodies are limited to 5 MB — larger bodies are rejected with `413` (this keeps requests below the platform's 6 MB payload ceiling, so treat ~5 MB as the practical limit and use `upload` mode for anything bigger). After creating a session, call `POST /v1/contacts/imports/{sessionId}/preview` to get proposed column mappings, then `POST /v1/contacts/imports/{sessionId}/execute` to run the import. Sessions expire after 7 days and can each be executed once. # Add members Source: https://docs.tented.ai/api-reference/contact-lists/add-members /api-reference/openapi.json post /v1/contact-lists/{listId}/contacts Bulk add members to a static list. Dynamic list membership is rule-driven and cannot be edited directly. # Create a contact list Source: https://docs.tented.ai/api-reference/contact-lists/create-a-contact-list /api-reference/openapi.json post /v1/contact-lists Create a contact list. Choose `static` (fixed membership) or `dynamic` (rule-driven) explicitly — the kind cannot be changed later. # Delete a contact list Source: https://docs.tented.ai/api-reference/contact-lists/delete-a-contact-list /api-reference/openapi.json delete /v1/contact-lists/{listId} Delete a static or dynamic contact list. Contacts themselves are not deleted. # Get a contact list Source: https://docs.tented.ai/api-reference/contact-lists/get-a-contact-list /api-reference/openapi.json get /v1/contact-lists/{listId} # Get export status Source: https://docs.tented.ai/api-reference/contact-lists/get-export-status /api-reference/openapi.json get /v1/contact-lists/{listId}/export/{exportId} # Get live member counts Source: https://docs.tented.ai/api-reference/contact-lists/get-live-member-counts /api-reference/openapi.json get /v1/contact-lists/{listId}/member-count Compute the list's current member count and how many of those members are blocked from marketing sends. Works for static and dynamic lists. # List contact lists Source: https://docs.tented.ai/api-reference/contact-lists/list-contact-lists /api-reference/openapi.json get /v1/contact-lists Page through the workspace's contact lists, most recently updated first. Lists created inline for a blast are private to that blast and never appear here — they are reachable only by `list_id`. # List members Source: https://docs.tented.ai/api-reference/contact-lists/list-members /api-reference/openapi.json get /v1/contact-lists/{listId}/contacts Page through the members of a list (static or dynamic). # Remove members Source: https://docs.tented.ai/api-reference/contact-lists/remove-members /api-reference/openapi.json delete /v1/contact-lists/{listId}/contacts Bulk remove members from a static list. Dynamic list membership is rule-driven and cannot be edited directly. # Start a CSV export Source: https://docs.tented.ai/api-reference/contact-lists/start-a-csv-export /api-reference/openapi.json post /v1/contact-lists/{listId}/export Start an async CSV export of the list's members. Poll `GET /v1/contact-lists/{listId}/export/{exportId}` until `completed`, then download from `download_url`. # Update a contact list Source: https://docs.tented.ai/api-reference/contact-lists/update-a-contact-list /api-reference/openapi.json patch /v1/contact-lists/{listId} Patch a contact list. `kind` is immutable; `rules` only apply to dynamic lists, and replacing them recomputes membership. # Create contacts Source: https://docs.tented.ai/api-reference/contacts/create-contacts /api-reference/openapi.json post /v1/contacts Create up to 100 contacts in one request. Each item needs an `email` and/or `phone`. Duplicates (matching a normalized email or phone) are rejected per-item with `error_code: conflict` and the `existing_contact`. Partial success is normal — check each item result. # Delete a contact Source: https://docs.tented.ai/api-reference/contacts/delete-a-contact /api-reference/openapi.json delete /v1/contacts/{contactId} Delete a single contact by ID — the single-record equivalent of one `DELETE /v1/contacts` item, without the batch envelope. Returns `204` with no body on success. # Delete contacts Source: https://docs.tented.ai/api-reference/contacts/delete-contacts /api-reference/openapi.json delete /v1/contacts Delete up to 100 contacts in one request. Equivalent to `POST /v1/contacts/delete`, which exists for clients that cannot send a body with `DELETE`. # Delete contacts (POST) Source: https://docs.tented.ai/api-reference/contacts/delete-contacts-post /api-reference/openapi.json post /v1/contacts/delete Body-compatible alternative to `DELETE /v1/contacts` for clients that cannot send a request body with the `DELETE` method. # Get a contact Source: https://docs.tented.ai/api-reference/contacts/get-a-contact /api-reference/openapi.json get /v1/contacts/{contactId} Retrieve a single contact by ID. The response is the contact object itself (no envelope) and includes `custom_fields`: the contact's custom field values keyed by field API name. # List contact activities Source: https://docs.tented.ai/api-reference/contacts/list-contact-activities /api-reference/openapi.json get /v1/contacts/{contactId}/activities Paginated activity timeline for a contact — creation and update events, email engagement (e.g. `email_sent`, `email_opened`, `email_clicked`), and unsubscribes — ordered by activity `timestamp`. Returns `404` if the contact does not exist. # List or look up contacts Source: https://docs.tented.ai/api-reference/contacts/list-or-look-up-contacts /api-reference/openapi.json get /v1/contacts This endpoint has two modes. **Lookup by identifier** — pass `email` and/or `phone` to fetch a single contact by its normalized email address or phone number. `items` contains the matching contact (including `custom_fields`) or is empty; `pagination` is omitted. When both identifiers are supplied, an email match wins and the phone is only consulted if no contact has that email. Empty identifier values are ignored. **Paginated listing** — without `email`/`phone`, returns a page of contacts sorted by `sort`/`order`, optionally narrowed by `search` and `updated_after` (contacts updated strictly after an ISO 8601 timestamp — useful for incremental syncs). Listing items do not include `custom_fields`; fetch a single contact with `GET /v1/contacts/{contactId}` for those. Unrecognized query parameters are rejected in listing mode. # Search contacts by rules Source: https://docs.tented.ai/api-reference/contacts/search-contacts-by-rules /api-reference/openapi.json post /v1/contacts/search Return the contacts matching a segment rule tree — the same rule DSL used for dynamic lists, blast audiences, and flow rules. Rule trees use camelCase keys (`fieldDefinitionId`, `listId`, `timeWindow`), unlike the rest of the API. Trees may nest at most 5 levels deep and contain at most 100 nodes; `email_step` conditions are rejected here (they are only legal inside flow condition steps). Results can be additionally narrowed with `search` and are paginated. Items do not include `custom_fields` — fetch a single contact for those. # Update a contact Source: https://docs.tented.ai/api-reference/contacts/update-a-contact /api-reference/openapi.json patch /v1/contacts/{contactId} Update a single contact by ID — the single-record equivalent of one `POST /v1/contacts/update` item, without the batch envelope. Only the fields present in the body are changed; nullable fields accept `null` to clear the stored value; unknown fields are rejected. A body with no updatable fields returns `400` (`No fields to update`). Changing `email` or `phone` to a value already used by another contact returns `409` with the conflicting contact in `details.existing_contact`, and clearing both identifiers is rejected — a contact must always keep at least one valid email or phone. Returns the full updated contact, including `custom_fields`. # Update contacts Source: https://docs.tented.ai/api-reference/contacts/update-contacts /api-reference/openapi.json post /v1/contacts/update Update up to 100 contacts in one request. Omitted fields stay unchanged; `null` clears nullable fields. A contact must always keep at least one valid email or phone. Changing an email/phone to one used by another contact is rejected per-item with `error_code: conflict`. # Upsert contacts Source: https://docs.tented.ai/api-reference/contacts/upsert-contacts /api-reference/openapi.json post /v1/contacts/upsert Create or update up to 100 contacts in one request (note: this endpoint's batch limit is 100 items). Each item is matched against existing contacts by a single key: the normalized `email` when the item has a non-empty email, otherwise the normalized `phone`. A matched contact is updated with the item's remaining fields (`status: updated`); an unmatched item creates a new contact (`status: created`, with `original_source` defaulting to `Public API` and `original_source_detail` to `client_item_id` unless supplied). Because matching uses only that single key, an item whose email is new but whose phone already belongs to a different contact is **not** treated as an update — the creation collides on the phone and the item is rejected with `error_code: conflict` and the conflicting contact in `existing_contact`. (A create collision on the item's own match key — e.g. from a concurrent insert — is resolved as an update of that contact instead.) Updates that would change an email or phone to collide with another contact are likewise rejected with `conflict`, and updates that would leave a contact with neither a valid email nor phone are rejected with `bad_request`. Partial success is normal — inspect each item result; successful items include the full `contact` with `custom_fields`. Body-level validation failures (an invalid email or phone format, more than 100 items, unknown fields) reject the entire request with `400` before any item is processed. # Creating Bulk Tents Source: https://docs.tented.ai/api-reference/creating-bulk-tents Create up to 25 tents from one approved template in a single public API request. ## Endpoint ```bash theme={null} POST /v1/tents/bulk ``` This endpoint creates a bulk job that fans out template-based tent generations across multiple items. `POST /v1/tents/bulk` is an async admission endpoint. The immediate response confirms which items were accepted or rejected and returns a `bulk_job_id` for polling. ## Request Body ### Top-Level Fields | Field | Type | Required | Notes | | -------------------------- | ------------------------------ | -------- | ---------------------------------------------------------------- | | `template_id` | `string` | Yes | Approved template ID to reuse across the whole batch | | `items` | `array` | Yes | Between `1` and `25` items | | `client_job_descriptor` | `string` | No | Optional label for the bulk job | | `default_tent_name_prefix` | `string` | No | Used when an item does not provide `tent_name` | | `start_index` | `integer` | No | Starting counter for generated tent names. Defaults to `1` | | `auto_publish` | `boolean` | No | Applies to the entire bulk request | | `reasoning` | `min \| medium \| high \| max` | No | Controls reasoning effort for the template-based generation pass | | `metadata` | `object` | No | Optional job-level metadata echoed back in responses | ### Item Fields | Field | Type | Required | Notes | | ------------------- | --------- | ------------- | ------------------------------------------------------------------------------- | | `input_data` | `object` | Conditionally | Structured personalization data for this tent | | `instructions` | `string` | Conditionally | Item-specific instructions for how the tent should be generated | | `client_item_id` | `string` | No | Your own per-item identifier for reconciliation | | `tent_name` | `string` | No | Overrides batch naming defaults | | `custom_page_alias` | `string` | No | Requested published path for this item. Requires top-level `auto_publish: true` | | `include_brand` | `boolean` | No | Pulls workspace branding into this item's prompt | Each item must provide at least one of `input_data` or `instructions`. ## Important Rules * All items in one bulk request use the same `template_id` * The template must exist in the same workspace as the API key * The template must already be approved and have approved content available * Tented generates every `tent_id`; callers cannot supply or override it * Bulk requests support partial success. Some items can be accepted while others are rejected * `auto_publish` is batch-wide, but each item can still request its own `custom_page_alias` ## Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents/bulk \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "template_id": "tpl_01JABC123XYZ", "client_job_descriptor": "Acme onboarding batch", "default_tent_name_prefix": "Acme onboarding", "start_index": 1, "auto_publish": true, "reasoning": "high", "metadata": { "source": "crm-import", "batch_label": "acme-q2-onboarding" }, "items": [ { "client_item_id": "acme-row-001", "tent_name": "Acme Onboarding Page 1", "custom_page_alias": "acme-onboarding-jane", "input_data": { "first_name": "Jane", "company_name": "Acme", "event_time": "9 PM", "cta_text": "Book your onboarding kickoff" }, "instructions": "Make the tone more executive and emphasize customer onboarding.", "include_brand": true }, { "client_item_id": "acme-row-002", "custom_page_alias": "acme-onboarding-michael", "input_data": { "first_name": "Michael", "company_name": "Acme", "event_time": "10 AM", "cta_text": "Schedule your implementation review" }, "instructions": "Keep it concise and more operations-focused." }, { "client_item_id": "acme-row-003", "instructions": "Make it warmer and more personal, but keep the same structure." } ] }' ``` ### Instructions-Only Example When you do not need structured template variables, you can omit `input_data` and use `instructions` alone: ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents/bulk \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "template_id": "tpl_01JABC123XYZ", "client_job_descriptor": "Quick test batch", "default_tent_name_prefix": "test", "auto_publish": true, "items": [ { "client_item_id": "test-001", "custom_page_alias": "test-hello", "instructions": "Make the entire body of the page just say Hello World", "include_brand": true }, { "client_item_id": "test-002", "custom_page_alias": "test-goodbye", "instructions": "Make the entire body of the page just say Goodbye World", "include_brand": true } ] }' ``` Each item must provide at least one of `input_data` or `instructions`. You can provide both; `input_data` is applied first and `instructions` are treated as additional guidance. ## Response Example `202 Accepted` ```json theme={null} { "bulk_job_id": "01JQZB4H8A7T5B3R2W1X9Y6Z0K", "status": "accepted", "template_id": "tpl_01JABC123XYZ", "auto_publish": true, "accepted_count": 3, "rejected_count": 0, "items": [ { "index": 0, "client_item_id": "acme-row-001", "tent_id": "b6f8d617-7cfe-4d63-a8e9-5d971780d57f", "tent_name": "Acme Onboarding Page 1", "custom_page_alias": "acme-onboarding-jane", "status": "accepted" }, { "index": 1, "client_item_id": "acme-row-002", "tent_id": "85833f2b-40ba-4af4-b820-cf13f7356d7a", "tent_name": "Acme onboarding (2)", "custom_page_alias": "acme-onboarding-michael", "status": "accepted" }, { "index": 2, "client_item_id": "acme-row-003", "tent_id": "53e8237e-0c87-495d-8d10-946db64dbf8d", "tent_name": "Acme onboarding (3)", "status": "accepted" } ], "metadata": { "source": "crm-import", "batch_label": "acme-q2-onboarding" }, "created_at": "2026-03-25T14:30:00.000Z" } ``` ## Partial Success Bulk requests are not all-or-nothing. Individual items can be rejected during admission while the rest of the batch proceeds. ```json theme={null} { "bulk_job_id": "01JQZB4H8A7T5B3R2W1X9Y6Z0K", "status": "accepted", "template_id": "tpl_01JABC123XYZ", "auto_publish": true, "accepted_count": 1, "rejected_count": 1, "items": [ { "index": 0, "client_item_id": "valid-row", "tent_id": "b6f8d617-7cfe-4d63-a8e9-5d971780d57f", "tent_name": "Bulk Tent (1)", "status": "accepted" }, { "index": 1, "client_item_id": "invalid-row", "status": "rejected", "error_code": "INVALID_ITEM", "message": "Invalid request body" } ], "created_at": "2026-03-25T14:30:00.000Z" } ``` Possible item-level rejection codes include: * `INVALID_ITEM` * `NOT_FOUND` * `INSUFFICIENT_CREDITS` ## Naming Behavior If an item omits `tent_name`, Tented generates a name using: * `default_tent_name_prefix` when provided * otherwise `client_job_descriptor` when provided * otherwise `Bulk Tent` The generated counter starts at `start_index` and is rendered as `Prefix (N)`. ## Auto-Publish Behavior Set top-level `auto_publish` to `true` to publish all accepted items after generation succeeds. Each item can optionally request a different `custom_page_alias`. Alias rules match `POST /v1/tents`: * Maximum `100` characters * Lowercase letters, numbers, hyphens, underscores, and dots only * Must start with a letter or number * Must not be a UUID * Must not use reserved words such as `api`, `admin`, `submit`, or `assets` * Use `/` to publish at the domain root `custom_page_alias` requires top-level `auto_publish: true`. If `auto_publish` is `false`, the affected item is rejected during admission. ## Polling For Results Use the returned `bulk_job_id` with `GET /v1/bulk-jobs/{bulkJobId}` to track aggregate progress and read final published URLs. Poll the bulk job until all accepted items complete or fail. # Creating Emails Source: https://docs.tented.ai/api-reference/creating-emails Create an email from an AI prompt, an approved template, or a blank scaffold, then poll the generation and read the HTML through the public API. ## Endpoints ```bash theme={null} POST /v1/emails GET /v1/emails/{emailId}/generations/{generationId} GET /v1/emails/{emailId}/content GET /v1/emails/{emailId}/generations/{generationId}/content ``` Emails are the reusable content assets that [blasts](/api-reference/managing-email-blasts) and [triggered flows](/api-reference/managing-triggered-flows) send. Each email starts as a `draft`, accumulates versions as you [iterate](/api-reference/editing-emails), and must be [**approved**](/api-reference/approving-emails) before a blast or flow can use it. AI generation is asynchronous. Creating with a `prompt` returns `202 Accepted` with a `generation_id`, and you poll the generation endpoint to track progress. Only one generation can run per email at a time. ## Idempotency `POST /v1/emails` accepts an optional `Idempotency-Key` header. Retrying with the same key replays the stored result instead of creating a duplicate email. If a request with the same key is still being processed, the API returns `409 Conflict` with `error_code: "idempotency_in_progress"`. The same header works on the [editing endpoints](/api-reference/editing-emails#idempotency). ## Create an Email ```bash theme={null} POST /v1/emails ``` ### Request Body | Field | Type | Required | Notes | | ---------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `name` | `string` | Yes | Internal display name, `1`-`200` characters. Not shown to recipients | | `prompt` | `string` | No | AI brief, `1`-`10000` characters. When present, content is generated asynchronously | | `template_id` | `string` | No | Seed content and sender defaults from an approved [email template](/api-reference/managing-email-templates) | | `token_data` | `object` | No | Structured data available to the generation as tokens. Requires `prompt` or `template_id` | | `subject` | `string` | No | Maximum `998` characters. Must be set before approval | | `preview_text` | `string \| null` | No | Inbox preheader, maximum `200` characters | | `from_name` | `string \| null` | No | Maximum `120` characters. Must be set before approval | | `from_address` | `string \| null` | No | Must be set before approval; sending requires a verified domain | | `reply_to_email` | `string \| null` | No | Must be set before approval | Include `prompt` to queue an AI generation (`202 Accepted`). Without a `prompt`, the email is created immediately (`201 Created`) — seeded from an approved template when `template_id` is present, or as a blank draft scaffolded from your workspace branding. ### Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/emails \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Welcome email", "prompt": "A warm welcome email for new Acme Analytics signups with a CTA to book a demo", "subject": "Welcome to Acme" }' ``` ### Response Example `202 Accepted` ```json theme={null} { "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "generation_id": "01JZ9GLYFA4L4Y9CBM4H31TT8V", "message_id": "01JZ9GLYFA6H2T0N8W1QG64M3E", "status": "generating" } ``` ## Create From a Template Pass `template_id` to start from an approved [email template](/api-reference/managing-email-templates) instead of a prompt or a blank scaffold: ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/emails \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "July newsletter", "template_id": "7c3f0a4e-91d2-4a8f-b344-2f6f6f0a1b9d" }' ``` When `template_id` is present: * The template must exist in the same workspace and be **approved** * The template's HTML is copied into the new email * The template's `default_subject`, `default_preview_text`, `default_from_name`, `default_from_address`, and `default_reply_to_email` are inherited as the email's sender headers Template-seeded creates without a `prompt` return `201 Created` with the [email object](/api-reference/approving-emails#retrieve-an-email). ## Start From Your Own HTML `POST /v1/emails` does not accept raw HTML directly. To start from your own code, create a blank draft (omit `prompt` and `template_id`), then replace its content with [`POST /v1/emails/{emailId}/save-code`](/api-reference/editing-emails#save-code-directly) — up to `2 MB` of HTML. Blasts can also create an inline email seeded from your own `html` when [setting the blast's email](/api-reference/managing-email-blasts#set-the-email). ## Poll a Generation ```bash theme={null} GET /v1/emails/{emailId}/generations/{generationId} ``` Generation `status` moves through `generating` to `completed` or `failed`. Completed generations include the version they produced and paths to their content: `200 OK` ```json theme={null} { "generation_id": "01JZ9GLYFA4L4Y9CBM4H31TT8V", "status": "completed", "type": "iteration", "version": 2, "content_path": "/v1/emails/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1/generations/01JZ9GLYFA4L4Y9CBM4H31TT8V/content", "plain_text_path": "/v1/emails/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1/generations/01JZ9GLYFA4L4Y9CBM4H31TT8V/plain-text", "plain_text_source": "auto", "created_at": "2026-07-01T12:00:00.000Z", "completed_at": "2026-07-01T12:00:41.000Z" } ``` Failed generations return `error_code: "generation_failed"` and an `error_message` instead. ## Read Content ```bash theme={null} GET /v1/emails/{emailId}/content GET /v1/emails/{emailId}/generations/{generationId}/content ``` Returns the HTML of the latest completed version (or of one specific generation) with a `text/html` content type — not JSON. ## Common Errors | Status | Cause | | ------------------ | ------------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body or field validation failure | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | Email or generation does not exist in the workspace | | `409 Conflict` | Same `Idempotency-Key` still processing (`idempotency_in_progress`) | Iterate with AI, save your own HTML, and manage the plain-text alternative. # Creating Tents Source: https://docs.tented.ai/api-reference/creating-tents Create a tent generation job through the public API from a prompt or approved template, with optional branding, assets, and auto-publish. ## Endpoint ```bash theme={null} POST /v1/tents ``` This endpoint creates the initial generation job for a tent. `POST /v1/tents` is an async trigger, not a synchronous HTML response. The immediate response only confirms the request was accepted. ## Request Body | Field | Type | Required | Notes | | ------------------- | ---------- | -------- | ------------------------------------------------------------------------------------ | | `name` | `string` | Yes | Human-readable tent name | | `prompt` | `string` | Yes | The generation prompt | | `user_id` | `string` | No | Optional external identifier to attribute the request | | `include_brand` | `boolean` | No | Pulls brand context from the workspace before generation | | `template_id` | `string` | No | Starts from an approved template in the same workspace instead of a blank generation | | `auto_publish` | `boolean` | No | Automatically publishes after successful generation | | `custom_page_alias` | `string` | No | Only valid when `auto_publish` is `true` | | `tent_id` | `uuid` | No | Use this when you uploaded assets first and want to generate into that idle tent | | `asset_ids` | `string[]` | No | Up to 5 asset IDs. Requires `tent_id` | ## Request Examples ### Minimal Request ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Acme launch page", "prompt": "Create a launch page for Acme Analytics with a hero, feature sections, customer logos, pricing, and a lead capture form" }' ``` ### With Uploaded Assets If you uploaded files first, provide the returned tent and asset IDs: ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Partner landing page", "prompt": "Use the uploaded brief and logo to build a partner-specific campaign page", "tent_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "asset_ids": ["01JPAEWP9PY5SCX1V6X03XK9M2"] }' ``` ## Response Example `202 Accepted` ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "status": "pending", "created_at": "2026-03-13T15:40:00.000Z" } ``` `asset_ids` can only be used with a `tent_id`. If you send asset IDs without a tent ID, the API returns `400 Bad Request`. ## Create From a Template Pass `template_id` when you want Tented to start from an existing approved template instead of generating from scratch. ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Template-based onboarding page", "prompt": "Adapt it into a customer onboarding page for Acme with updated copy, pricing, and a contact form", "template_id": "01JPC2YJ5S6M3T5H8XQ4N7R9AB" }' ``` When `template_id` is present: * The template must exist in the same workspace as the API key * The template must already be approved * Tented uses the approved template content as the starting point for the generation * The request still requires a `prompt` describing the customization you want The public API does not currently expose a template listing endpoint. You should obtain the template ID from Tented's template workflow in the same workspace before calling `POST /v1/tents`. Template-based requests use the same `POST /v1/tents` endpoint and return the same `202 Accepted` response shape as prompt-only creates. ## Include Workspace Branding Set `include_brand` to `true` when you want Tented to enrich the prompt with workspace brand context such as: * Brand logo * Brand icon * Primary color * Workspace or company name * Company domain * Company description * Brand guidelines Example: ```json theme={null} { "name": "Branded page", "prompt": "Build a product marketing page for our latest release", "include_brand": true } ``` ## Auto-Publish After Generation Set `auto_publish` to `true` if you want Tented to publish the finished tent automatically. ```json theme={null} { "name": "Launch page", "prompt": "Create a launch page for our new feature release", "auto_publish": true } ``` When auto-publish is enabled: * Generation still completes asynchronously * The create response is still `202 Accepted` * Publication details appear later in `GET /v1/tents/{tentId}` ### Requesting a custom page alias You can optionally request a custom published path: ```json theme={null} { "name": "Spring launch", "prompt": "Create a campaign page for our spring launch", "auto_publish": true, "custom_page_alias": "spring-launch" } ``` Alias rules: * Maximum `100` characters * Lowercase letters, numbers, hyphens, underscores, and dots only * Must start with a letter or number * Must not be a UUID * Must not use reserved words such as `api`, `admin`, `submit`, or `assets` * Use `/` to publish at the domain root `custom_page_alias` requires `auto_publish: true`. Sending an alias without auto-publish returns `400 Bad Request`. ## Important Behavior ### One initial generation per tent If you supply `tent_id`, that tent must not already have a generation. Otherwise Tented returns: ```json theme={null} { "error": "Tent already has a generation" } ``` with `409 Conflict`. ### Prompt validation Tented validates that your prompt is actually asking for web content. Requests that do not look like a landing page, form, registration page, or related web experience can be rejected with: ```json theme={null} { "error": "Invalid request", "message": "Please enter a prompt that describes the web content you'd like to create (e.g., landing page, form, registration page).", "code": "INVALID_PROMPT_INTENT" } ``` If you send `template_id`, prompt validation becomes more permissive. Short customization requests like "make it blue", "change the speaker name", or "use it as is" are accepted as long as they are clearly about modifying the selected template. ### Credit limits If the workspace does not have enough credits to start generation, the API returns `429 Too Many Requests`. ### User attribution If you provide `user_id`, Tented stores it as the request's creator identity for that tent's first chat message and generation trigger. It does not change authentication or workspace scope. ## Common Errors | Status | Cause | | ----------------------- | ----------------------------------------------- | | `400 Bad Request` | Invalid JSON body | | `400 Bad Request` | Request body is missing | | `400 Bad Request` | Missing required fields like `name` or `prompt` | | `400 Bad Request` | `asset_ids` sent without `tent_id` | | `400 Bad Request` | `template_id` exists but is not approved | | `400 Bad Request` | `template_id` has no approved content available | | `400 Bad Request` | Invalid `custom_page_alias` | | `400 Bad Request` | Prompt fails web-content intent validation | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | `tent_id` does not exist | | `404 Not Found` | One of the supplied `asset_ids` does not exist | | `404 Not Found` | `template_id` does not exist in the workspace | | `409 Conflict` | The supplied `tent_id` already has a generation | | `429 Too Many Requests` | Credit limit exceeded | Poll for generation progress and read publication results. # Deleting Tents Source: https://docs.tented.ai/api-reference/deleting-tents Delete a tent and its associated public assets through the public API. ## Endpoint ```bash theme={null} DELETE /v1/tents/{identifier} ``` `identifier` can be: * The tent UUID * The current published alias for that tent * `__index__` for a tent published at the domain root ## Request Examples Delete by UUID: ```bash theme={null} curl --request DELETE \ --url https://api.tented.ai/v1/tents/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1 \ --header "Authorization: Bearer $TENTED_API_KEY" ``` Delete by alias: ```bash theme={null} curl --request DELETE \ --url https://api.tented.ai/v1/tents/launch-page \ --header "Authorization: Bearer $TENTED_API_KEY" ``` Delete a root-page tent: ```bash theme={null} curl --request DELETE \ --url https://api.tented.ai/v1/tents/__index__ \ --header "Authorization: Bearer $TENTED_API_KEY" ``` ## Response Example ```json theme={null} { "message": "Tent deleted successfully", "tent_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1" } ``` ## What Gets Removed Deleting a tent removes the tent record and its related public artifacts, including: * Uploaded tent assets * Published page assets for that tent * Alias reservations for published pages * Form submission delivery configs tied to the tent * Scheduled automations associated with the tent ## Identifier Rules * If you use a UUID, it must be a valid v4 tent ID * If you use an alias, it must match the same alias validation rules as publishing * For the root page, use `__index__` in the path segment ## Common Errors | Status | Cause | | ------------------ | ------------------------------------------------------ | | `400 Bad Request` | The identifier is empty or not a valid tent ID / alias | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | No tent in the workspace matches that UUID or alias | Review the full public API endpoint map. # Editing Emails Source: https://docs.tented.ai/api-reference/editing-emails Iterate on an email with AI, save HTML directly, update metadata, and manage the plain-text alternative through the public API. ## Endpoints ```bash theme={null} POST /v1/emails/{emailId}/messages POST /v1/emails/{emailId}/save-code POST /v1/emails/{emailId}/clone PATCH /v1/emails/{emailId} GET /v1/emails/{emailId}/plain-text PUT /v1/emails/{emailId}/plain-text DELETE /v1/emails/{emailId}/plain-text GET /v1/emails/{emailId}/generations/{generationId}/plain-text ``` Every change to an email's HTML — an AI iteration or a direct code save — creates a new version on the email. AI iteration is asynchronous. Posting a message returns `202 Accepted` with a `generation_id`, and you [poll the generation](/api-reference/creating-emails#poll-a-generation) to track progress. Only one generation can run per email at a time. ## Idempotency `POST /v1/emails/{emailId}/messages` and `POST /v1/emails/{emailId}/save-code` accept an optional `Idempotency-Key` header. Retrying with the same key replays the stored result instead of creating a duplicate version. If a request with the same key is still being processed, the API returns `409 Conflict` with `error_code: "idempotency_in_progress"`. ## Iterate With AI ```bash theme={null} POST /v1/emails/{emailId}/messages ``` | Field | Type | Required | Notes | | ----------- | ---------- | -------- | ----------------------------------------------------------------- | | `prompt` | `string` | Yes | Edit instruction against the current HTML, `1`-`10000` characters | | `asset_ids` | `string[]` | No | Email asset IDs to make available to the generation | Returns `202 Accepted` with a `generation_id` to [poll](/api-reference/creating-emails#poll-a-generation). Starting a second generation while one is running returns `409 Conflict` with `error_code: "generation_in_progress"`. ## Save Code Directly ```bash theme={null} POST /v1/emails/{emailId}/save-code ``` | Field | Type | Required | Notes | | ------ | -------- | -------- | ---------------------------------------- | | `code` | `string` | Yes | Full replacement HTML body, up to `2 MB` | Creates a new version synchronously and returns `200 OK` with the new `generation_id` and `version`. Unlike an AI iteration, saving code does not unapprove an approved email. Blocked while a generation is running. ## Clone an Email ```bash theme={null} POST /v1/emails/{emailId}/clone ``` Duplicate an email — its content, versions, and assets — as a fresh `draft` attributed to the API principal. The clone is never approved, regardless of the source's state, so approve it separately before sending. Optionally send `{"name": "..."}`; the name defaults to `"{original name} (copy)"`. Returns `201 Created` with the new email object. Accepts an optional `Idempotency-Key` header. ## Update Metadata ```bash theme={null} PATCH /v1/emails/{emailId} ``` Accepts the same optional fields as [create](/api-reference/creating-emails#create-an-email) except `prompt`: `name`, `subject`, `preview_text`, `from_name`, `from_address`, `reply_to_email`. Omit a field to leave it unchanged; pass `null` to clear nullable fields. Updating `preview_text` rewrites the preheader in the current HTML in place without creating a new version. ## Plain Text ```bash theme={null} GET /v1/emails/{emailId}/plain-text PUT /v1/emails/{emailId}/plain-text DELETE /v1/emails/{emailId}/plain-text ``` Every completed generation carries a plain-text alternative derived from its HTML. `GET` returns it; `PUT` overrides it with your own text; `DELETE` reverts to the auto-derived version. All three return the same shape: `200 OK` ```json theme={null} { "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "generation_id": "01JZ9GLYFA4L4Y9CBM4H31TT8V", "version": 2, "content": "Welcome to Acme...", "overridden": true, "stale_after_html_iteration": false, "plain_text_source": "override" } ``` ### PUT Request Body | Field | Type | Required | Notes | | --------- | -------- | -------- | -------------------------------------------------------- | | `content` | `string` | Yes | Plain-text body, up to `1 MB`. An empty string clears it | `PUT` and `DELETE` require a completed generation to exist, otherwise they return `400 Bad Request`. `stale_after_html_iteration` flips to `true` when the HTML is iterated after an override — a signal to review or revert your custom plain text. The generation-scoped `GET /v1/emails/{emailId}/generations/{generationId}/plain-text` returns the same shape without the override flags. ## Common Errors | Status | Cause | | ------------------ | ------------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body or field validation failure | | `400 Bad Request` | Plain-text override or revert without a completed generation | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | Email or generation does not exist in the workspace | | `409 Conflict` | A generation is already running (`generation_in_progress`) | | `409 Conflict` | Same `Idempotency-Key` still processing (`idempotency_in_progress`) | Approve the email for sending, then list, retrieve, and delete emails. # Editing Tents Source: https://docs.tented.ai/api-reference/editing-tents Create a new generation for an existing tent through the public API. ## Endpoint ```bash theme={null} POST /v1/tents/{tentId}/edit ``` Use this endpoint to create a new generation for a tent that already has at least one completed or in-progress version. `POST /v1/tents/{tentId}/edit` is asynchronous. It queues a new generation and returns immediately with a new `generation_id`. ## Request Body | Field | Type | Required | Notes | | ------------------- | ---------- | -------- | --------------------------------------------------------- | | `prompt` | `string` | Yes | Describes the change you want to make | | `include_brand` | `boolean` | No | Pulls workspace brand context into the edit prompt | | `auto_publish` | `boolean` | No | Publishes this generation automatically after it succeeds | | `custom_page_alias` | `string` | No | Only valid when `auto_publish` is `true` | | `asset_ids` | `string[]` | No | Up to 5 existing asset IDs already attached to this tent | ## Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1/edit \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "prompt": "Make the header blue, tighten the copy, and add a testimonial section" }' ``` ## Response Example `202 Accepted` ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "generation_id": "01JRF3V2P1A9X5Y6Z7B8C9D0EF", "status": "pending", "updated_at": "2026-04-08T12:30:00.000Z" } ``` ## Important Behavior ### The tent must already exist Edits only work for tents that already have a generation history. If the tent exists but has never been generated, the API returns: ```json theme={null} { "error": "Invalid request body", "details": { "issues": [ { "path": "tent_id", "message": "Tent has no existing generation. Use POST /v1/tents to create the first version." } ] } } ``` ### Asset references stay on the same tent If you pass `asset_ids`, each asset must already belong to the same `tentId`. Missing assets return `404 Not Found`. ### Auto-publish rules match create If `custom_page_alias` is present, you must also set `auto_publish: true`. Alias rules are the same as `POST /v1/tents`: * Maximum `100` characters * Lowercase letters, numbers, hyphens, underscores, and dots only * Must start with a letter or number * Must not be a UUID * Must not use reserved words such as `api`, `admin`, `submit`, or `assets` * Use `/` to publish at the domain root ### Credits Editing checks workspace credits before the generation is queued. If the workspace cannot cover the edit, the API returns `429 Too Many Requests`. ## Clone a Tent ```bash theme={null} POST /v1/tents/{tentId}/clone ``` Duplicate a tent — its latest content and assets — into a brand-new tent. The clone starts unpublished; [publish it separately](/api-reference/publishing-tents) once you're ready. Optionally send `{"name": "..."}` to name the copy; otherwise it defaults to `"Copy of {name}"`. ### Response Example `201 Created` ```json theme={null} { "tent_id": "9b2e0e2a-2c1d-4f77-8a3e-0e6a1d2f4c8b", "name": "Copy of Product launch", "status": "draft", "created_at": "2026-07-08T14:00:00.000Z" } ``` ## Common Errors | Status | Cause | | ----------------------- | ----------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body or invalid request fields | | `400 Bad Request` | `custom_page_alias` was sent without `auto_publish: true` | | `400 Bad Request` | The tent exists but has no previous generation | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | The tent does not exist | | `404 Not Found` | One of the supplied `asset_ids` does not exist on that tent | | `429 Too Many Requests` | Credit limit exceeded | Poll the tent until the new generation completes or fails. # Archive a blast Source: https://docs.tented.ai/api-reference/email-blasts/archive-a-blast /api-reference/openapi.json post /v1/email-blasts/{blastId}/archive Archive a blast that is not mid-send. # Create a blast Source: https://docs.tented.ai/api-reference/email-blasts/create-a-blast /api-reference/openapi.json post /v1/email-blasts Create a draft blast. Attach an audience and an email, then schedule it or send it immediately. # Delete a blast Source: https://docs.tented.ai/api-reference/email-blasts/delete-a-blast /api-reference/openapi.json delete /v1/email-blasts/{blastId} Delete a blast. Scheduled blasts must be unscheduled first; archived blasts cannot be deleted (`409 campaign_not_deletable`). # Get a blast Source: https://docs.tented.ai/api-reference/email-blasts/get-a-blast /api-reference/openapi.json get /v1/email-blasts/{blastId} Retrieve a blast, including send/engagement counters and (for drafts) `approval_readiness`. # Get export status Source: https://docs.tented.ai/api-reference/email-blasts/get-export-status /api-reference/openapi.json get /v1/email-blasts/{blastId}/contacts/export/{exportId} # List blasts Source: https://docs.tented.ai/api-reference/email-blasts/list-blasts /api-reference/openapi.json get /v1/email-blasts # List recipients & engagement Source: https://docs.tented.ai/api-reference/email-blasts/list-recipients-&-engagement /api-reference/openapi.json get /v1/email-blasts/{blastId}/contacts List the contacts in an engagement category (recipients, opened, clicked, …) with per-contact event timestamps. # Rename a blast Source: https://docs.tented.ai/api-reference/email-blasts/rename-a-blast /api-reference/openapi.json patch /v1/email-blasts/{blastId} # Schedule a blast Source: https://docs.tented.ai/api-reference/email-blasts/schedule-a-blast /api-reference/openapi.json post /v1/email-blasts/{blastId}/schedule Schedule the send. The blast must be ready (approved email, audience, valid future time) — otherwise `400 campaign_not_ready` with an `approval_readiness` breakdown. # Send a blast now Source: https://docs.tented.ai/api-reference/email-blasts/send-a-blast-now /api-reference/openapi.json post /v1/email-blasts/{blastId}/send-now Start the send immediately (asynchronous). Same readiness requirements as scheduling. # Set the audience Source: https://docs.tented.ai/api-reference/email-blasts/set-the-audience /api-reference/openapi.json put /v1/email-blasts/{blastId}/audience Attach the audience, replacing any previous one. Use an existing list, an inline static list of contact IDs, or an inline dynamic segment defined by rules. Only editable while the blast is a draft (`409 campaign_not_editable` otherwise). # Set the email Source: https://docs.tented.ai/api-reference/email-blasts/set-the-email /api-reference/openapi.json put /v1/email-blasts/{blastId}/email Attach the email to send — an existing email, or one created inline (from a template, your own HTML, or a blank scaffold). Only editable while the blast is a draft. # Start a CSV export Source: https://docs.tented.ai/api-reference/email-blasts/start-a-csv-export /api-reference/openapi.json post /v1/email-blasts/{blastId}/contacts/export Start an async CSV export of one engagement category. Poll `GET /v1/email-blasts/{blastId}/contacts/export/{exportId}` until `completed`, then download from `download_url`. # Unschedule a blast Source: https://docs.tented.ai/api-reference/email-blasts/unschedule-a-blast /api-reference/openapi.json post /v1/email-blasts/{blastId}/unschedule Cancel a scheduled send and return the blast to draft. # Approve an email template Source: https://docs.tented.ai/api-reference/email-templates/approve-an-email-template /api-reference/openapi.json post /v1/email-templates/{templateId}/approve Mark the newest completed version as approved and usable for seeding new emails via `POST /v1/emails` with `template_id`. Requires at least one completed generation (`409 template_not_ready`); unlike emails there is no required-sender-header gate — the default header fields are optional. Pass `version` as an optimistic-concurrency precondition; a mismatch with the newest completed version fails with `409 approval_version_stale`. While a generation is running, an approve without `version` fails with `409 generation_in_progress`; a version-pinned approve still succeeds. # Clone an email template Source: https://docs.tented.ai/api-reference/email-templates/clone-an-email-template /api-reference/openapi.json post /v1/email-templates/{templateId}/clone Duplicate an email template — its content, versions, and assets — as a fresh draft attributed to the API principal. The clone is never approved. # Create an email template Source: https://docs.tented.ai/api-reference/email-templates/create-an-email-template /api-reference/openapi.json post /v1/email-templates Create an email template. With a `prompt`, AI generation starts asynchronously (`202` — poll the returned generation). Without one the template is created synchronously (`201`) — seeded from your `code` when provided, otherwise a blank scaffold branded from your workspace. Approve a template to let `POST /v1/emails` seed new emails from its HTML and default sender headers. # Delete a template asset Source: https://docs.tented.ai/api-reference/email-templates/delete-a-template-asset /api-reference/openapi.json delete /v1/email-templates/{templateId}/assets/{assetId} Remove an asset from the email template and delete the underlying file. # Delete an email template Source: https://docs.tented.ai/api-reference/email-templates/delete-an-email-template /api-reference/openapi.json delete /v1/email-templates/{templateId} Delete an email template and all of its versions. Unlike emails, deletion is never blocked — emails already seeded from the template keep their content and are unaffected. # Get a template asset Source: https://docs.tented.ai/api-reference/email-templates/get-a-template-asset /api-reference/openapi.json get /v1/email-templates/{templateId}/assets/{assetId} Retrieve one asset's metadata and a `download_url`. # Get an email template Source: https://docs.tented.ai/api-reference/email-templates/get-an-email-template /api-reference/openapi.json get /v1/email-templates/{templateId} # Get generation HTML Source: https://docs.tented.ai/api-reference/email-templates/get-generation-html /api-reference/openapi.json get /v1/email-templates/{templateId}/generations/{generationId}/content Returns the HTML produced by one specific generation as `text/html`. # Get generation plain text Source: https://docs.tented.ai/api-reference/email-templates/get-generation-plain-text /api-reference/openapi.json get /v1/email-templates/{templateId}/generations/{generationId}/plain-text # Get generation status Source: https://docs.tented.ai/api-reference/email-templates/get-generation-status /api-reference/openapi.json get /v1/email-templates/{templateId}/generations/{generationId} Poll an AI generation. `completed` generations include the version produced and content paths; `failed` ones include `error_code` and `error_message`. # Get plain text Source: https://docs.tented.ai/api-reference/email-templates/get-plain-text /api-reference/openapi.json get /v1/email-templates/{templateId}/plain-text Returns the plain-text rendition of the latest version — auto-derived from the HTML unless overridden. # Get template HTML Source: https://docs.tented.ai/api-reference/email-templates/get-template-html /api-reference/openapi.json get /v1/email-templates/{templateId}/content Returns the HTML of the latest completed version as `text/html` (not JSON). # Iterate with AI Source: https://docs.tented.ai/api-reference/email-templates/iterate-with-ai /api-reference/openapi.json post /v1/email-templates/{templateId}/messages Queue an AI iteration on the template. Only one generation can run per template at a time — a second request fails with `409 generation_in_progress`. Poll the returned generation for completion. # List email templates Source: https://docs.tented.ai/api-reference/email-templates/list-email-templates /api-reference/openapi.json get /v1/email-templates List email templates with cursor pagination. Follow `next_cursor` until it is absent. # List template assets Source: https://docs.tented.ai/api-reference/email-templates/list-template-assets /api-reference/openapi.json get /v1/email-templates/{templateId}/assets List every asset attached to an email template. Each item includes a `download_url`. # Override plain text Source: https://docs.tented.ai/api-reference/email-templates/override-plain-text /api-reference/openapi.json put /v1/email-templates/{templateId}/plain-text Replace the auto-derived plain text with your own (up to 1 MB; an empty string clears the content). Requires a completed generation. If the HTML is iterated later, `stale_after_html_iteration` flips to `true` on the override. # Reset plain text Source: https://docs.tented.ai/api-reference/email-templates/reset-plain-text /api-reference/openapi.json delete /v1/email-templates/{templateId}/plain-text Remove the manual override and revert to plain text auto-derived from the current HTML. # Save HTML directly Source: https://docs.tented.ai/api-reference/email-templates/save-html-directly /api-reference/openapi.json post /v1/email-templates/{templateId}/save-code Replace the template’s HTML with your own code (up to 2 MB), creating a new version synchronously. Does not unapprove an approved template. Blocked while an AI generation is running. # Unapprove an email template Source: https://docs.tented.ai/api-reference/email-templates/unapprove-an-email-template /api-reference/openapi.json post /v1/email-templates/{templateId}/unapprove Return the template to draft. Unlike emails, unapproval is never blocked — emails already seeded from the template are unaffected, but new emails can no longer be created from it until it is approved again. # Update template metadata Source: https://docs.tented.ai/api-reference/email-templates/update-template-metadata /api-reference/openapi.json patch /v1/email-templates/{templateId} Update the name and the default sender fields copied onto emails seeded from this template. Omitted fields stay unchanged; `null` clears nullable fields. # Upload a template asset Source: https://docs.tented.ai/api-reference/email-templates/upload-a-template-asset /api-reference/openapi.json post /v1/email-templates/{templateId}/assets Upload one file (max 4 MB) to an email template as `multipart/form-data` with a single `file` field. Reference the returned `asset_id` in `asset_ids` when iterating the template with AI. # Approve an email Source: https://docs.tented.ai/api-reference/emails/approve-an-email /api-reference/openapi.json post /v1/emails/{emailId}/approve Mark the newest completed version as approved and usable by blasts and flows. Requires `subject`, `from_name`, `from_address`, and `reply_to_email` to be set (`400 email_missing_required_headers` otherwise). Pass `version` as an optimistic-concurrency precondition; a mismatch with the newest completed version fails with `409 approval_version_stale`. While a generation is running, an approve without `version` fails with `409 generation_in_progress`; a version-pinned approve still succeeds. # Clone an email Source: https://docs.tented.ai/api-reference/emails/clone-an-email /api-reference/openapi.json post /v1/emails/{emailId}/clone Duplicate an email — its content, versions, and assets — as a fresh draft attributed to the API principal. The clone is never approved, regardless of the source's state. # Create an email Source: https://docs.tented.ai/api-reference/emails/create-an-email /api-reference/openapi.json post /v1/emails Create an email. With a `prompt`, AI generation starts asynchronously (`202` — poll the returned generation). Without a prompt you get a blank draft (`201`), optionally seeded from an approved email template via `template_id`. Emails are reusable content assets: approve one, then attach it to blasts and flows. # Delete an email Source: https://docs.tented.ai/api-reference/emails/delete-an-email /api-reference/openapi.json delete /v1/emails/{emailId} Delete an email. Blocked with `409` while the email is scheduled in a blast (`email_in_scheduled_blast`) or used by an active flow (`email_in_active_flow`). # Delete an email asset Source: https://docs.tented.ai/api-reference/emails/delete-an-email-asset /api-reference/openapi.json delete /v1/emails/{emailId}/assets/{assetId} Remove an asset from the email and delete the underlying file. # Get an email Source: https://docs.tented.ai/api-reference/emails/get-an-email /api-reference/openapi.json get /v1/emails/{emailId} # Get an email asset Source: https://docs.tented.ai/api-reference/emails/get-an-email-asset /api-reference/openapi.json get /v1/emails/{emailId}/assets/{assetId} Retrieve one asset's metadata and a `download_url`. # Get email HTML Source: https://docs.tented.ai/api-reference/emails/get-email-html /api-reference/openapi.json get /v1/emails/{emailId}/content Returns the HTML of the latest completed version as `text/html` (not JSON). # Get generation HTML Source: https://docs.tented.ai/api-reference/emails/get-generation-html /api-reference/openapi.json get /v1/emails/{emailId}/generations/{generationId}/content Returns the HTML produced by one specific generation as `text/html`. # Get generation plain text Source: https://docs.tented.ai/api-reference/emails/get-generation-plain-text /api-reference/openapi.json get /v1/emails/{emailId}/generations/{generationId}/plain-text # Get generation status Source: https://docs.tented.ai/api-reference/emails/get-generation-status /api-reference/openapi.json get /v1/emails/{emailId}/generations/{generationId} Poll an AI generation. `completed` generations include the version produced and content paths; `failed` ones include `error_code` and `error_message`. # Get plain text Source: https://docs.tented.ai/api-reference/emails/get-plain-text /api-reference/openapi.json get /v1/emails/{emailId}/plain-text Returns the plain-text rendition of the latest version — auto-derived from the HTML unless overridden. # Iterate with AI Source: https://docs.tented.ai/api-reference/emails/iterate-with-ai /api-reference/openapi.json post /v1/emails/{emailId}/messages Queue an AI iteration on the email. Only one generation can run per email at a time — a second request fails with `409 generation_in_progress`. Poll the returned generation for completion. # List email assets Source: https://docs.tented.ai/api-reference/emails/list-email-assets /api-reference/openapi.json get /v1/emails/{emailId}/assets List every asset attached to an email. Each item includes a `download_url`. # List emails Source: https://docs.tented.ai/api-reference/emails/list-emails /api-reference/openapi.json get /v1/emails List emails with cursor pagination. Follow `next_cursor` until it is absent. # Override plain text Source: https://docs.tented.ai/api-reference/emails/override-plain-text /api-reference/openapi.json put /v1/emails/{emailId}/plain-text Replace the auto-derived plain text with your own (up to 1 MB; an empty string clears the content). Requires a completed generation. If the HTML is iterated later, `stale_after_html_iteration` flips to `true` on the override. # Reset plain text Source: https://docs.tented.ai/api-reference/emails/reset-plain-text /api-reference/openapi.json delete /v1/emails/{emailId}/plain-text Remove the manual override and revert to plain text auto-derived from the current HTML. # Save HTML directly Source: https://docs.tented.ai/api-reference/emails/save-html-directly /api-reference/openapi.json post /v1/emails/{emailId}/save-code Replace the email’s HTML with your own code (up to 2 MB), creating a new version synchronously. Does not unapprove an approved email. Blocked while an AI generation is running. # Unapprove an email Source: https://docs.tented.ai/api-reference/emails/unapprove-an-email /api-reference/openapi.json post /v1/emails/{emailId}/unapprove Return the email to draft. Rejected while the email is scheduled in a blast (`email_in_scheduled_blast`) or used by an active flow (`email_in_active_flow`). # Update email metadata Source: https://docs.tented.ai/api-reference/emails/update-email-metadata /api-reference/openapi.json patch /v1/emails/{emailId} Update name, subject, and sender fields. Omitted fields stay unchanged; `null` clears nullable fields. Updating `preview_text` rewrites the preheader in the current HTML in place without creating a new version. # Upload an email asset Source: https://docs.tented.ai/api-reference/emails/upload-an-email-asset /api-reference/openapi.json post /v1/emails/{emailId}/assets Upload one file (max 4 MB) to an email as `multipart/form-data` with a single `file` field. Reference the returned `asset_id` in `asset_ids` when iterating the email with AI. # Importing Contacts Source: https://docs.tented.ai/api-reference/importing-contacts Bulk-import contacts from a CSV file or inline JSON rows, with column mapping, dedupe control, and per-row results. ## Endpoints ```bash theme={null} POST /v1/contacts/imports POST /v1/contacts/imports/{session_id}/preview POST /v1/contacts/imports/{session_id}/execute GET /v1/contacts/imports/{import_run_id} GET /v1/contacts/imports/{import_run_id}/results GET /v1/contacts/imports/{import_run_id}/export ``` Imports run asynchronously: start a session (upload a CSV or send rows inline), optionally preview the detected column mappings, execute, then poll the run until it completes. Every row gets an individually reported outcome. For small synchronous batches (up to 100 records), use [`POST /v1/contacts/upsert`](/api-reference/managing-contacts) instead. Imports shine for large files, dedupe control, and auditable per-row results. ## Start an Import ```bash theme={null} POST /v1/contacts/imports ``` Two mutually exclusive modes, selected by `source`: ### Upload mode | Field | Type | Required | Notes | | -------------- | ---------- | -------- | ------------------------------- | | `source` | `"upload"` | Yes | Requests a presigned upload URL | | `file_name` | `string` | Yes | Original CSV file name | | `content_type` | `string` | No | Defaults to `text/csv` | Returns an `import_session_id` plus a presigned S3 `upload_url` (valid for 1 hour) — `PUT` your CSV bytes directly to it, then continue to preview/execute. ### Inline rows mode | Field | Type | Required | Notes | | ----------- | -------- | -------- | ------------------------------------------------ | | `source` | `"rows"` | Yes | Send data inline, no file handling | | `file_name` | `string` | No | Label used in run history | | `rows` | `array` | Yes | Up to `1000` objects; keys become column headers | Request bodies are capped at **5 MB**. Larger datasets should use upload mode. ```bash theme={null} curl --request POST \ --url 'https://api.tented.ai/v1/contacts/imports' \ --header 'Authorization: Bearer tented_your_api_key' \ --header 'Content-Type: application/json' \ --data '{ "source": "rows", "file_name": "crm-sync.json", "rows": [ {"Email": "jane@example.com", "First Name": "Jane", "Company": "Acme"}, {"Email": "sam@example.com", "First Name": "Sam", "Company": "Globex"} ] }' ``` ## Preview an Import ```bash theme={null} POST /v1/contacts/imports/{session_id}/preview ``` Parses the header plus sample rows and returns auto-detected `mappings` (source header → target field), per-column sample validation `issues`, the org's `available_fields`, and `field_creation_candidates` for unmapped columns. Optional for headless runs — you can pass mappings straight to execute. ## Execute an Import ```bash theme={null} POST /v1/contacts/imports/{session_id}/execute ``` | Field | Type | Required | Notes | | ------------------------------ | --------- | -------- | ------------------------------------------------------------------------------ | | `mappings` | `array` | Yes | `{header, target_field, target_kind, overwrite_behavior?}` per column | | `duplicate_strategy` | `string` | No | `update` (default) merges into matching contacts; `skip` leaves them untouched | | `overwrite_blank_only` | `boolean` | No | When `true`, mapped fields default to filling blanks only | | `list_ids` | `array` | No | Static list IDs (≤ 500) every imported contact is added to | | `created_field_definition_ids` | `array` | No | Custom fields bulk-created for this session's unmapped columns | Rows matching an existing contact (normalized email or phone) follow `duplicate_strategy`; per-column `overwrite_behavior` (`overwrite` | `skip_if_filled`) controls whether filled values are replaced. Returns `202` with an `import_run_id`. ## Poll a Run ```bash theme={null} GET /v1/contacts/imports/{import_run_id} ``` `status` moves `queued → running → completed | failed`, with `created_count`, `updated_count`, `skipped_count`, `error_count`, and chunk-level progress. ## Per-Row Results ```bash theme={null} GET /v1/contacts/imports/{import_run_id}/results?status=failed&page=1&limit=100 ``` Paginated outcomes (`limit` ≤ 250), filterable by `status` (`created` | `updated` | `skipped` | `failed`); failed rows include `field_issues` with error codes. Row numbers count the header as line 1. ```bash theme={null} GET /v1/contacts/imports/{import_run_id}/export ``` Streams a CSV of `skipped` and `failed` rows with reasons (`Content-Disposition: attachment`). Use the paginated results endpoint for full result sets. ## Common Errors | Status | Meaning | | ------ | ----------------------------------------------------------------------- | | `400` | Invalid mappings, unknown target field, or malformed rows | | `404` | Unknown `session_id` / `import_run_id` (sessions expire after 24 hours) | | `409` | Session already executed | | `413` | Inline rows body over 5 MB | # Tented API Overview Source: https://docs.tented.ai/api-reference/introduction Create, edit, publish, measure, and delete tents, plus manage contacts, through Tented's public API. ## Overview Tented's public API lets you manage the full lifecycle of tents from your own systems without using the app UI. The current public surface supports: * Creating a tent from a prompt * Creating a tent from an approved template * Uploading files before generation * Editing an existing tent * Publishing and unpublishing tents * Creating bulk tent jobs from one approved template * Polling tent and bulk-job status * Reading tent and workspace analytics * Deleting tents by ID or alias * Creating, updating, and deleting contacts in batches Public generation endpoints are asynchronous. Create, edit, and bulk-create requests return immediately after admission, and you then poll status endpoints to track progress. ## Base URL Use the production base URL: ```bash theme={null} https://api.tented.ai ``` All endpoints live under the `/v1` prefix. For the complete, exhaustive list — every endpoint with its parameters, request bodies, and response schemas — see the full API Reference. Every endpoint across tents, analytics, contacts, lists, imports, emails, templates, blasts, and triggered flows, with live request and response schemas. ## Authentication Authenticate every request with a bearer API key: ```bash theme={null} Authorization: Bearer tented_your_api_key ``` API keys are scoped to a single Tented workspace. A request can only access tents, assets, aliases, and analytics that belong to the workspace associated with that key. Learn how API keys work, where to create them, and which headers to send. ## Common Workflows ### 1. Single-tent flow * Optionally upload files with `POST /v1/tents/new/assets` or `POST /v1/tents/{tentId}/assets` * Create the first version with `POST /v1/tents` * Poll progress with `GET /v1/tents/{tentId}` * Iterate later with `POST /v1/tents/{tentId}/edit` * Publish or unpublish manually with `POST /v1/tents/{tentId}/publish` and `POST /v1/tents/{tentId}/unpublish` ### 2. Bulk template flow * Start a bulk job with `POST /v1/tents/bulk` * Poll aggregate progress with `GET /v1/bulk-jobs/{bulkJobId}` ### 3. Measurement and cleanup * Read tent-level analytics with `GET /v1/analytics/tent` * Read workspace-wide analytics with `GET /v1/analytics/org` * Delete a tent with `DELETE /v1/tents/{identifier}` ### 4. Contact sync * Create up to 25 contacts with `POST /v1/contacts` * Update up to 25 contacts with `POST /v1/contacts/update` * Delete up to 25 contacts with `POST /v1/contacts/delete` or `DELETE /v1/contacts` ## Quickstart ### Minimal create flow ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Acme product page", "prompt": "Create a landing page for Acme Analytics with a hero, customer logos, features, pricing, and lead form" }' ``` Then poll: ```bash theme={null} curl --request GET \ --url https://api.tented.ai/v1/tents/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1 \ --header "Authorization: Bearer $TENTED_API_KEY" ``` ## Next Steps Start a single tent from a prompt, template, or uploaded files. Queue a new generation for an existing tent. Fan out one approved template across up to 25 items. Fetch tent-level or workspace-wide analytics. Create, update, and delete contacts in batches. # Managing Contact Fields Source: https://docs.tented.ai/api-reference/managing-contact-fields List, create, and archive custom contact field definitions, and discover the fields and operators available for segment rules. ## Endpoints ```bash theme={null} GET /v1/contact-fields POST /v1/contact-fields PATCH /v1/contact-fields/{field_definition_id} GET /v1/contact-fields/rule-metadata ``` Custom fields extend contacts beyond the standard schema. Values are written via the `custom_fields` object on [contact create, update, and upsert](/api-reference/managing-contacts), keyed by each field's `api_name`. ## List Fields ```bash theme={null} GET /v1/contact-fields ``` | Parameter | Type | Required | Notes | | ------------------ | --------- | -------- | ---------------------------- | | `include_archived` | `boolean` | No | Include archived definitions | Returns both `system` fields (the built-in contact schema, read-only) and `org_managed` custom fields. Each definition includes `api_name`, `display_name`, `value_type` (`string` | `number` | `boolean` | `date`), `status`, `is_editable`, and provenance metadata. ## Create Fields ```bash theme={null} POST /v1/contact-fields ``` Accepts a single definition or `items[]` (up to `50` per request). | Field | Type | Required | Notes | | ----------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------- | | `display_name` | `string` | Yes | Human label | | `api_name` | `string` | No | snake\_case identifier; derived from `display_name` when omitted. Unique per org (case-insensitive) | | `value_type` | `string` | Yes | `string` \| `number` \| `boolean` \| `date` | | `description` | `string` | No | Shown in the app's field manager | | `show_in_people_list_default` | `boolean` | No | Show as a default column in the contacts table | ```bash theme={null} curl --request POST \ --url 'https://api.tented.ai/v1/contact-fields' \ --header 'Authorization: Bearer tented_your_api_key' \ --header 'Content-Type: application/json' \ --data '{ "display_name": "Account Tier", "api_name": "account_tier", "value_type": "string" }' ``` Organizations can have at most `200` active custom fields. Exceeding the cap or colliding with an existing `api_name` (including archived ones) returns `409` with the conflicting names in `details`. ## Update or Archive a Field ```bash theme={null} PATCH /v1/contact-fields/{field_definition_id} ``` Updatable: `display_name`, `description`, `show_in_people_list_default`, and `status` (`archived` to soft-delete, `active` to restore). `api_name` and `value_type` are immutable; system fields are read-only and return `400`. Archived fields stop accepting writes — contact upserts referencing them get a per-item rejection — but their stored values are preserved and return when the field is restored. ## Rule Metadata ```bash theme={null} GET /v1/contact-fields/rule-metadata ``` Returns every filterable field (standard + custom) with its supported operators — the vocabulary for building rule trees used by [contact search](/api-reference/managing-contacts) and [dynamic lists](/api-reference/managing-contact-lists). Use it to build rule editors without hardcoding operator lists. ## Common Errors | Status | Meaning | | ------ | ----------------------------------------------------------------------------------- | | `400` | Invalid value\_type, reserved/system api\_name, or modifying an immutable attribute | | `404` | Unknown `field_definition_id` | | `409` | `api_name` collision or the 200-active-field cap would be exceeded | # Dynamic & Static Lists Source: https://docs.tented.ai/api-reference/managing-contact-lists Create static and dynamic contact lists, manage membership, and export members to CSV through the public API. ## Endpoints ```bash theme={null} GET /v1/contact-lists POST /v1/contact-lists GET /v1/contact-lists/{listId} PATCH /v1/contact-lists/{listId} DELETE /v1/contact-lists/{listId} GET /v1/contact-lists/{listId}/contacts POST /v1/contact-lists/{listId}/contacts DELETE /v1/contact-lists/{listId}/contacts GET /v1/contact-lists/{listId}/member-count POST /v1/contact-lists/{listId}/export GET /v1/contact-lists/{listId}/export/{exportId} ``` Contact lists group [contacts](/api-reference/managing-contacts) into audiences for blasts and flows. A list is one of two kinds, chosen at creation: | Kind | Membership | Editable via member endpoints | | --------- | ---------------------------------------------- | ------------------------------- | | `static` | Fixed — you add and remove contacts explicitly | Yes | | `dynamic` | Computed continuously from a segment rule tree | No — change the `rules` instead | `kind` is **immutable**. Creating a list requires an explicit choice between `static` and `dynamic`, and there is no way to convert one to the other later. You may also see `kind: "system"` on lists auto-managed by the platform. System lists are read-only — editing or deleting one returns `403` with `error_code: "system_list_protected"`. ## List Contact Lists ```bash theme={null} GET /v1/contact-lists ``` ### Query Parameters | Parameter | Type | Required | Notes | | --------- | --------- | -------- | ------------------------------------------ | | `page` | `integer` | No | 1-based page number, defaults to `1` | | `limit` | `integer` | No | Page size `1`-`100`, defaults to `25` | | `search` | `string` | No | Case-insensitive name search | | `kind` | `string` | No | Filter by `static`, `dynamic`, or `system` | Lists created inline for a blast never appear here — they are private to that blast and reachable only by `list_id`. ## List Request Example ```bash theme={null} curl --request GET \ --url 'https://api.tented.ai/v1/contact-lists?kind=dynamic&limit=10' \ --header 'Authorization: Bearer tented_your_api_key' ``` ## List Response Example ```json theme={null} { "lists": [ { "list_id": "11111111-1111-4111-8111-111111111111", "name": "California Leads", "slug": "california-leads", "description": "Leads located in CA", "kind": "dynamic", "rules": { "kind": "group", "operator": "and", "conditions": [ {"kind": "condition", "source": "contact", "field": "state", "operator": "equals", "value": "CA"} ] }, "member_count": null, "owner_campaign_id": null, "created_at": "2026-04-30T12:00:00.000Z", "updated_at": "2026-04-30T12:00:00.000Z" } ], "pagination": {"page": 1, "limit": 10, "total": 1, "totalPages": 1} } ``` `member_count` is a stored counter for static lists and `null` for dynamic lists — use the [member-count endpoint](#member-counts) for a live count of either kind. ## Create a Contact List ```bash theme={null} POST /v1/contact-lists ``` ### Request Body | Field | Type | Required | Notes | | ------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `name` | `string` | Yes | `1`-`255` characters. Must be unique within the workspace | | `description` | `string \| null` | No | Maximum `1000` characters | | `kind` | `string` | Yes | `static` or `dynamic`. Immutable after creation | | `rules` | `object \| null` | No | Segment rule tree for a dynamic list. A dynamic list without rules has no members. Rejected on static lists | | `contact_ids` | `uuid[]` | No | Initial members for a static list, maximum `500` per request. Rejected on dynamic lists | Returns `201 Created` with the list object, or `409 Conflict` with `error_code: "contact_list_name_exists"` when the name is taken. ## Create Request Examples A static list seeded with two members: ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/contact-lists \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Webinar Attendees", "kind": "static", "contact_ids": [ "11111111-1111-4111-8111-111111111111", "22222222-2222-4222-8222-222222222222" ] }' ``` A dynamic list of qualified California leads: ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/contact-lists \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "California Leads", "kind": "dynamic", "rules": { "kind": "group", "operator": "and", "conditions": [ {"kind": "condition", "source": "contact", "field": "state", "operator": "equals", "value": "CA"}, {"kind": "condition", "source": "contact", "field": "leadStatus", "operator": "equals", "value": "qualified"} ] } }' ``` ### Dynamic List Rules Rules use the same segment rule tree as blast audiences: groups (`and`/`or`, nested up to `5` levels, at most `100` nodes) of conditions over contact fields, custom fields, activities, and static-list membership. Rule objects use **camelCase** keys, unlike the rest of the public API. See [Audience Rules](/api-reference/managing-email-blasts#audience-rules) for the full condition reference, and call `GET /v1/contact-fields/rule-metadata` to discover the available fields and the operators each supports. `email_step` conditions are **not** allowed in list rules, even though the contact search endpoint accepts them — they only work inside [flow condition steps](/api-reference/managing-triggered-flows#condition-rules). Including one returns `400 Bad Request`. ## Get, Update, and Delete a List ```bash theme={null} GET /v1/contact-lists/{listId} PATCH /v1/contact-lists/{listId} DELETE /v1/contact-lists/{listId} ``` `GET` returns the list object shown above. `DELETE` returns `204 No Content`; the contacts themselves are not deleted. ### Update Request Body At least one field is required. `kind` cannot be changed. | Field | Type | Required | Notes | | ------------- | ---------------- | -------- | -------------------------------------------------------------------- | | `name` | `string` | No | `1`-`255` characters. Must be unique within the workspace | | `description` | `string \| null` | No | Maximum `1000` characters. Pass `null` to clear | | `rules` | `object \| null` | No | Replacement rule tree — dynamic lists only. Membership is recomputed | Sending `rules` for a static list returns `400 Bad Request`. Returns `200 OK` with the updated list object. ## Update Request Example ```bash theme={null} curl --request PATCH \ --url https://api.tented.ai/v1/contact-lists/11111111-1111-4111-8111-111111111111 \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "California Leads (Qualified)", "rules": { "kind": "group", "operator": "and", "conditions": [ {"kind": "condition", "source": "contact", "field": "state", "operator": "equals", "value": "CA"}, {"kind": "condition", "source": "contact", "field": "leadStatus", "operator": "equals", "value": "qualified"} ] } }' ``` ## List Members ```bash theme={null} GET /v1/contact-lists/{listId}/contacts ``` Pages through the members of a static or dynamic list. ### Query Parameters | Parameter | Type | Required | Notes | | ---------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `page` | `integer` | No | 1-based page number, defaults to `1` | | `limit` | `integer` | No | Page size `1`-`100`, defaults to `25` | | `search` | `string` | No | Case-insensitive name/email search | | `sort` | `string` | No | `member_added_at`, `created_at`, `updated_at` (default), `last_activity_at`, `display_name`, `first_name`, `last_name`, `normalized_email`, or `original_source`. `member_added_at` sorts by when the contact joined this list | | `order` | `string` | No | `asc` or `desc` (default) | | `exclude_unsubscribed` | `string` | No | `1` or `true` — omit contacts unsubscribed from marketing email | | `only_unsubscribed` | `string` | No | `1` or `true` — return only unsubscribed contacts. `exclude_unsubscribed` wins if both are set | ## Members Request Example ```bash theme={null} curl --request GET \ --url 'https://api.tented.ai/v1/contact-lists/11111111-1111-4111-8111-111111111111/contacts?sort=member_added_at&exclude_unsubscribed=1' \ --header 'Authorization: Bearer tented_your_api_key' ``` ## Members Response Example ```json theme={null} { "contacts": [ { "contact_id": "22222222-2222-4222-8222-222222222222", "display_name": "Jane Doe", "first_name": "Jane", "last_name": "Doe", "email": "jane@example.com", "phone": null, "original_source": "Public API", "original_source_detail": null, "company": "Acme", "job_title": "CEO", "lead_status": "qualified", "lifecycle_stage": "subscriber", "tented_score": 42, "last_activity_at": "2026-04-30T12:00:00.000Z", "member_added_at": "2026-04-29T09:30:00.000Z", "created_at": "2026-04-01T12:00:00.000Z", "updated_at": "2026-04-30T12:00:00.000Z" } ], "pagination": {"page": 1, "limit": 25, "total": 1, "totalPages": 1} } ``` `member_added_at` is `null` for dynamic-list members — dynamic membership is computed, not recorded. ## Add and Remove Members ```bash theme={null} POST /v1/contact-lists/{listId}/contacts DELETE /v1/contact-lists/{listId}/contacts ``` Static lists only — calling either endpoint on a dynamic list returns `400 Bad Request`. Both take the same body: | Field | Type | Required | Notes | | ------------- | -------- | -------- | --------------------------------------------- | | `contact_ids` | `uuid[]` | Yes | Between `1` and `500` contact IDs per request | Adding returns `404` if the list or any referenced contact does not exist. Contacts already in the list are counted, not duplicated. ## Add Members Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/contact-lists/11111111-1111-4111-8111-111111111111/contacts \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "contact_ids": [ "22222222-2222-4222-8222-222222222222", "33333333-3333-4333-8333-333333333333" ] }' ``` ## Add Members Response Example ```json theme={null} { "added_count": 1, "already_member_count": 1 } ``` Removal responds with `{"removed_count": 1}`. ## Member Counts ```bash theme={null} GET /v1/contact-lists/{listId}/member-count ``` Computes live counts for any list kind — use this for dynamic lists, whose list object always has `member_count: null`. ```json theme={null} { "list_id": "11111111-1111-4111-8111-111111111111", "kind": "dynamic", "member_count": 1204, "blocked_count": 37 } ``` `blocked_count` is the number of members currently blocked from marketing sends (unsubscribed contacts). ## Export Members to CSV ```bash theme={null} POST /v1/contact-lists/{listId}/export GET /v1/contact-lists/{listId}/export/{exportId} ``` Starting an export returns `202 Accepted` with an `export` job. Poll the job until `status` is `completed`, then fetch the file from its presigned `download_url`: ```json theme={null} { "export": { "export_id": "44444444-4444-4444-8444-444444444444", "status": "completed", "file_name": "contact-list-california-leads-11111111-1111-4111-8111-111111111111.csv", "row_count": 1204, "error_message": null, "download_url": "https://exports.tented.ai/...", "created_at": "2026-04-30T12:00:00.000Z", "updated_at": "2026-04-30T12:00:30.000Z", "completed_at": "2026-04-30T12:00:30.000Z" } } ``` `status` moves `pending` → `processing` → `completed`, with `failed` as the error exit (see `error_message`). `download_url` is populated once the job completes and is signed with a limited lifetime — download promptly rather than storing the URL. ## Common Request Errors | Status | Cause | | ------------------ | -------------------------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body, missing body, or invalid parameters | | `400 Bad Request` | `rules` supplied for a static list, or `contact_ids` supplied for a dynamic list | | `400 Bad Request` | Member add/remove on a dynamic list | | `400 Bad Request` | `rules` contains an `email_step` condition | | `401 Unauthorized` | Missing or invalid bearer token | | `403 Forbidden` | Editing or deleting a system list (`system_list_protected`) | | `404 Not Found` | List, contact, or export job does not exist in the workspace | | `409 Conflict` | A list with this name already exists (`contact_list_name_exists`) | Review the full Tented API endpoint map. # Managing Contacts Source: https://docs.tented.ai/api-reference/managing-contacts Look up, create, update, and delete contacts through the public API in batches of up to 100 records. ## Endpoints ```bash theme={null} GET /v1/contacts GET /v1/contacts/{contact_id} PATCH /v1/contacts/{contact_id} DELETE /v1/contacts/{contact_id} GET /v1/contacts/{contact_id}/activities POST /v1/contacts POST /v1/contacts/upsert POST /v1/contacts/update POST /v1/contacts/delete DELETE /v1/contacts POST /v1/contacts/search ``` Use these endpoints to sync CRM contacts from external systems. Batch write requests accept an `items` array with up to `100` records. For bulk file-based ingestion, see [Importing Contacts](/api-reference/importing-contacts); for field definitions, see [Managing Contact Fields](/api-reference/managing-contact-fields). Contact writes support partial success. Tented processes each item independently and returns accepted or rejected results in request order. ## Look Up Contacts ```bash theme={null} GET /v1/contacts ``` `GET /v1/contacts` has two modes. With an `email` or `phone` query parameter it is an identifier lookup returning zero or one contact (shown here). Without an identifier it becomes a paginated listing — see [List Contacts](#list-contacts). ### Query Parameters | Parameter | Type | Required | Notes | | --------- | -------- | ------------- | ----------------------------------------------------------------------- | | `email` | `string` | Conditionally | Either `email` or `phone` is required. Matched on the normalized value. | | `phone` | `string` | Conditionally | Either `email` or `phone` is required. Matched on the normalized value. | ## Lookup Request Example ```bash theme={null} curl --request GET \ --url 'https://api.tented.ai/v1/contacts?email=jane%40example.com' \ --header 'Authorization: Bearer tented_your_api_key' ``` ## Lookup Response Example Returns `items` with the matching contact, or an empty array when no contact matches. `custom_fields` is keyed by each custom field's API name. ```json theme={null} { "items": [ { "contact_id": "11111111-1111-4111-8111-111111111111", "display_name": "Jane Doe", "first_name": "Jane", "last_name": "Doe", "email": "jane@example.com", "phone": null, "company": "Acme", "job_title": "CEO", "original_source": "Public API", "lifecycle_stage": "subscriber", "unsubscribed": false, "custom_fields": { "favorite_color": "Green" }, "created_at": "2026-04-30T12:00:00.000Z", "updated_at": "2026-04-30T12:00:00.000Z" } ] } ``` ## Get a Contact ```bash theme={null} GET /v1/contacts/{contact_id} ``` Fetch a single contact by its ID — the `contact_id` returned by create, update, and lookup responses. ### Path Parameters | Parameter | Type | Required | Notes | | ------------ | ------ | -------- | ----------------------------------------------------------- | | `contact_id` | `uuid` | Yes | Returns `404` when no contact matches, `400` when malformed | ## Get Request Example ```bash theme={null} curl --request GET \ --url 'https://api.tented.ai/v1/contacts/11111111-1111-4111-8111-111111111111' \ --header 'Authorization: Bearer tented_your_api_key' ``` ## Get Response Example Returns the contact object directly, including `custom_fields` keyed by each custom field's API name — the same shape as a lookup item. ```json theme={null} { "contact_id": "11111111-1111-4111-8111-111111111111", "display_name": "Jane Doe", "first_name": "Jane", "last_name": "Doe", "email": "jane@example.com", "phone": null, "company": "Acme", "job_title": "CEO", "original_source": "Public API", "lifecycle_stage": "subscriber", "unsubscribed": false, "custom_fields": { "favorite_color": "Green" }, "created_at": "2026-04-30T12:00:00.000Z", "updated_at": "2026-04-30T12:00:00.000Z" } ``` ## List Contacts ```bash theme={null} GET /v1/contacts?page=1&limit=100&updated_after=2026-07-01T00:00:00Z ``` Without an `email`/`phone` identifier, `GET /v1/contacts` pages through the workspace's contacts. | Parameter | Type | Required | Notes | | --------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `page` | `integer` | No | 1-based page number (default `1`) | | `limit` | `integer` | No | Page size, max `100` (default `25`) | | `sort` | `string` | No | `created_at` (default) \| `updated_at` \| `last_activity_at` \| `display_name` \| `first_name` \| `last_name` \| `normalized_email` \| `original_source` | | `order` | `string` | No | `asc` \| `desc` (default) | | `search` | `string` | No | Matches name, email, phone, and company | | `updated_after` | `string` | No | ISO 8601 timestamp — only contacts updated after it. The building block for incremental syncs | Returns `{items, pagination}`. List items do not include `custom_fields` — fetch a single contact for those. ## Update a Single Contact ```bash theme={null} PATCH /v1/contacts/{contact_id} ``` Single-record alternative to the batch update: the request body takes the same fields as an update item (minus `contact_id`), returns the updated contact directly, `404` when missing, and `409` with `existing_contact` when an email/phone change collides with another contact. ## Delete a Single Contact ```bash theme={null} DELETE /v1/contacts/{contact_id} ``` Returns `204` on success, `404` when the contact does not exist. ## Contact Activities ```bash theme={null} GET /v1/contacts/{contact_id}/activities?page=1&limit=25&order=desc ``` Read-only, paginated activity timeline: `contact_created`, `contact_updated`, email engagement events (sent, delivered, opened, clicked, bounced, unsubscribed), and flow entry/exit — each with a `type`, `timestamp`, and event-specific `metadata`. ## Create Contacts ```bash theme={null} POST /v1/contacts ``` ### Request Body | Field | Type | Required | Notes | | ------- | ------- | -------- | ------------------------------ | | `items` | `array` | Yes | Between `1` and `100` contacts | ### Create Item Fields | Field | Type | Required | Notes | | ---------------------------- | ----------------- | ------------- | ----------------------------------------------- | | `email` | `string` | Conditionally | Either `email` or `phone` is required | | `phone` | `string` | Conditionally | Either `email` or `phone` is required | | `client_item_id` | `string` | No | Your own per-item identifier for reconciliation | | `display_name` | `string` | No | Maximum `255` characters | | `first_name` | `string` | No | Maximum `255` characters | | `last_name` | `string` | No | Maximum `255` characters | | `company` | `string` | No | Maximum `255` characters | | `job_title` | `string` | No | Maximum `255` characters | | `address` | `string` | No | Maximum `500` characters | | `address2` | `string` | No | Maximum `255` characters | | `city` | `string` | No | Maximum `255` characters | | `state` | `string` | No | Maximum `255` characters | | `country` | `string` | No | Maximum `255` characters | | `zip_code` | `string` | No | Maximum `50` characters | | `lead_status` | `string` | No | Maximum `100` characters | | `lifecycle_stage` | `string` | No | Maximum `100` characters | | `tented_score` | `number \| null` | No | Optional contact score | | `unsubscribed` | `boolean` | No | Defaults to `false` when omitted | | `marketing_email_subscribed` | `boolean \| null` | No | Email subscription state | | `marketing_sms_subscribed` | `boolean \| null` | No | SMS subscription state | ## Create Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/contacts \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "items": [ { "client_item_id": "crm-row-001", "email": "jane@example.com", "first_name": "Jane", "last_name": "Doe", "company": "Acme", "job_title": "CEO" }, { "client_item_id": "crm-row-002", "phone": "+15551234567", "display_name": "Sam Rivera" } ] }' ``` ## Create Response Example `201 Created` ```json theme={null} { "status": "completed", "created_count": 2, "rejected_count": 0, "items": [ { "index": 0, "client_item_id": "crm-row-001", "contact_id": "11111111-1111-4111-8111-111111111111", "status": "created", "contact": { "contact_id": "11111111-1111-4111-8111-111111111111", "display_name": "Jane Doe", "first_name": "Jane", "last_name": "Doe", "email": "jane@example.com", "phone": null, "company": "Acme", "job_title": "CEO", "original_source": "Public API", "created_at": "2026-04-30T12:00:00.000Z", "updated_at": "2026-04-30T12:00:00.000Z" } }, { "index": 1, "client_item_id": "crm-row-002", "contact_id": "22222222-2222-4222-8222-222222222222", "status": "created", "contact": { "contact_id": "22222222-2222-4222-8222-222222222222", "display_name": "Sam Rivera", "email": null, "phone": "+15551234567", "original_source": "Public API", "created_at": "2026-04-30T12:00:00.000Z", "updated_at": "2026-04-30T12:00:00.000Z" } } ] } ``` ## Duplicate Contacts Tented checks normalized email and phone values before creating a contact. If an item matches an existing contact, that item is rejected with `error_code: "conflict"`. If you want matches to be updated instead of rejected, use [Upsert Contacts](#upsert-contacts). ```json theme={null} { "index": 0, "client_item_id": "crm-row-001", "status": "rejected", "error_code": "conflict", "message": "Contact already exists", "existing_contact": { "contact_id": "11111111-1111-4111-8111-111111111111", "display_name": "Jane Doe", "email": "jane@example.com", "phone": null } } ``` ## Upsert Contacts ```bash theme={null} POST /v1/contacts/upsert ``` Create-or-update in one call — no conflict handling required. Each item (up to `100`) is matched by its **normalized email** (or phone, for phone-only items): a match updates that contact, no match creates one. Items accept the full update field set including `custom_fields`, and each result reports `status: "created"` or `"updated"`. Matching is identifier-scoped: an item is only ever matched on its own identifier. If a *new* email's item carries a phone that belongs to a different contact, the item is rejected with `error_code: "conflict"` and the `existing_contact` — it never silently modifies the phone-matched contact. ```bash theme={null} curl --request POST \ --url 'https://api.tented.ai/v1/contacts/upsert' \ --header 'Authorization: Bearer tented_your_api_key' \ --header 'Content-Type: application/json' \ --data '{ "items": [ { "email": "jane@example.com", "first_name": "Jane", "company": "Acme", "custom_fields": {"account_tier": "enterprise"} } ] }' ``` Response mirrors the batch format with `created_count`, `updated_count`, and `rejected_count`. ## Search Contacts ```bash theme={null} POST /v1/contacts/search ``` Filter contacts with a rule tree — the same engine that powers dynamic lists and blast audiences. | Field | Type | Required | Notes | | ---------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | | `rules` | `object` | Yes | AND/OR rule group over standard fields, custom fields, activity, and list membership (max depth `5`, max `100` conditions) | | `page` / `limit` | `integer` | No | Pagination, `limit` ≤ `100` | | `search` | `string` | No | Free-text filter applied alongside the rules | | `sort` / `order` | `string` | No | Same sort keys as [List Contacts](#list-contacts) | Discover the available fields and operators with [`GET /v1/contact-fields/rule-metadata`](/api-reference/managing-contact-fields). ## Update Contacts ```bash theme={null} POST /v1/contacts/update ``` ### Request Body | Field | Type | Required | Notes | | ------- | ------- | -------- | ------------------------------------ | | `items` | `array` | Yes | Between `1` and `100` update records | ### Update Item Fields | Field | Type | Required | Notes | | ---------------------------- | ----------------- | -------- | -------------------------------------------------------- | | `contact_id` | `uuid` | Yes | Contact to update | | `client_item_id` | `string` | No | Your own per-item identifier for reconciliation | | `email` | `string \| null` | No | Set to `null` to clear, as long as phone remains present | | `phone` | `string \| null` | No | Set to `null` to clear, as long as email remains present | | `display_name` | `string \| null` | No | Maximum `255` characters | | `first_name` | `string` | No | Maximum `255` characters | | `last_name` | `string` | No | Maximum `255` characters | | `email_domain` | `string \| null` | No | Usually derived automatically from email | | `company` | `string` | No | Maximum `255` characters | | `job_title` | `string` | No | Maximum `255` characters | | `address` | `string` | No | Maximum `500` characters | | `address2` | `string` | No | Maximum `255` characters | | `city` | `string` | No | Maximum `255` characters | | `state` | `string` | No | Maximum `255` characters | | `country` | `string` | No | Maximum `255` characters | | `zip_code` | `string` | No | Maximum `50` characters | | `original_source` | `string \| null` | No | Maximum `255` characters | | `original_source_detail` | `string \| null` | No | Maximum `255` characters | | `lead_status` | `string` | No | Maximum `100` characters | | `lifecycle_stage` | `string` | No | Maximum `100` characters | | `tented_score` | `number \| null` | No | Optional contact score | | `unsubscribed` | `boolean` | No | Unsubscribe flag | | `marketing_email_subscribed` | `boolean \| null` | No | Email subscription state | | `marketing_sms_subscribed` | `boolean \| null` | No | SMS subscription state | | `marketing_sms_invalid` | `boolean` | No | SMS validity flag | | `custom_fields` | `object` | No | Editable custom fields by API name | A contact must always have at least one valid email or phone. An update that clears both identity fields is rejected for that item. `marketing_email_invalid` is system-managed and read-only: Tented sets it to `true` when a marketing email to the contact hard-bounces, and resets it to `false` when the contact's email address changes. It appears in contact responses and audience rules, but requests that include it are rejected. ## Update Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/contacts/update \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "items": [ { "client_item_id": "crm-row-001", "contact_id": "11111111-1111-4111-8111-111111111111", "company": "Acme Enterprise", "lead_status": "qualified", "custom_fields": { "favorite_color": "Green" } } ] }' ``` ## Update Response Example `200 OK` ```json theme={null} { "status": "completed", "updated_count": 1, "rejected_count": 0, "items": [ { "index": 0, "client_item_id": "crm-row-001", "contact_id": "11111111-1111-4111-8111-111111111111", "status": "updated", "contact": { "contact_id": "11111111-1111-4111-8111-111111111111", "email": "jane@example.com", "company": "Acme Enterprise", "lead_status": "qualified", "updated_at": "2026-04-30T12:05:00.000Z" } } ] } ``` ## Delete Contacts ```bash theme={null} POST /v1/contacts/delete DELETE /v1/contacts ``` Use `POST /v1/contacts/delete` if your HTTP client does not support request bodies on `DELETE`. ### Request Body | Field | Type | Required | Notes | | ------- | ------- | -------- | ------------------------------------ | | `items` | `array` | Yes | Between `1` and `100` delete records | ### Delete Item Fields | Field | Type | Required | Notes | | ---------------- | -------- | -------- | ----------------------------------------------- | | `contact_id` | `uuid` | Yes | Contact to delete | | `client_item_id` | `string` | No | Your own per-item identifier for reconciliation | ## Delete Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/contacts/delete \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "items": [ { "client_item_id": "crm-row-001", "contact_id": "11111111-1111-4111-8111-111111111111" } ] }' ``` ## Delete Response Example `200 OK` ```json theme={null} { "status": "completed", "deleted_count": 1, "rejected_count": 0, "items": [ { "index": 0, "client_item_id": "crm-row-001", "contact_id": "11111111-1111-4111-8111-111111111111", "status": "deleted" } ] } ``` ## Batch Response Format Every contacts write endpoint returns: | Field | Type | Notes | | ---------------- | -------- | ------------------------------------------------------- | | `status` | `string` | Always `completed` after the request has been processed | | `created_count` | `number` | Present on create responses | | `updated_count` | `number` | Present on update responses | | `deleted_count` | `number` | Present on delete responses | | `rejected_count` | `number` | Number of rejected items | | `items` | `array` | Per-item results in request order | Rejected items include: | Field | Type | Notes | | ------------------ | -------- | -------------------------------------------------- | | `index` | `number` | Zero-based item index from the request | | `client_item_id` | `string` | Returned when supplied | | `contact_id` | `uuid` | Returned when supplied | | `status` | `string` | `rejected` | | `error_code` | `string` | `bad_request`, `not_found`, `conflict`, or `error` | | `message` | `string` | Human-readable failure reason | | `existing_contact` | `object` | Returned for duplicate conflicts | ## Common Item-Level Rejections | Error Code | Cause | | ------------- | ------------------------------------------------------------------------ | | `bad_request` | No fields to update | | `bad_request` | Update would remove both email and phone | | `bad_request` | Custom field is missing, inactive, non-editable, or has an invalid value | | `not_found` | Contact does not exist in the workspace | | `conflict` | Email or phone matches another existing contact | ## Common Request Errors | Status | Cause | | ------------------ | -------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body | | `400 Bad Request` | Request body is missing | | `400 Bad Request` | `items` is missing, empty, or contains more than `100` records | | `400 Bad Request` | A create item has neither `email` nor `phone` | | `400 Bad Request` | Invalid email, phone, UUID, or field format | | `401 Unauthorized` | Missing or invalid bearer token | Review the full Tented API endpoint map. # Managing Email Blasts Source: https://docs.tented.ai/api-reference/managing-email-blasts Create a blast, attach an audience and an approved email, then schedule or send it through the public API. ## Endpoints ```bash theme={null} GET /v1/email-blasts POST /v1/email-blasts GET /v1/email-blasts/{blastId} PATCH /v1/email-blasts/{blastId} DELETE /v1/email-blasts/{blastId} PUT /v1/email-blasts/{blastId}/audience PUT /v1/email-blasts/{blastId}/email POST /v1/email-blasts/{blastId}/schedule POST /v1/email-blasts/{blastId}/send-now POST /v1/email-blasts/{blastId}/unschedule POST /v1/email-blasts/{blastId}/archive GET /v1/email-blasts/{blastId}/contacts POST /v1/email-blasts/{blastId}/contacts/export GET /v1/email-blasts/{blastId}/contacts/export/{exportId} ``` A blast is a one-time send of an email to an audience. The typical workflow is: 1. Create a draft blast 2. Attach an audience with `PUT .../audience` 3. Attach an email with `PUT .../email` 4. Schedule it or send it now Blast `status` moves through `draft` → `scheduled` → `sending` → `sent`, with `failed`, `cancelled`, and `archived` as side exits. The audience and email are only editable while the blast is a `draft`. ## Create a Blast ```bash theme={null} POST /v1/email-blasts ``` | Field | Type | Required | Notes | | ------------- | ---------------- | -------- | ------------------------------------------- | | `name` | `string` | Yes | Internal display name, `1`-`200` characters | | `description` | `string \| null` | No | Maximum `1000` characters | Returns `201 Created` with the blast object. ## Set the Audience ```bash theme={null} PUT /v1/email-blasts/{blastId}/audience ``` Reference an existing contact list, or create a new list and attach it in one call. This replaces any previously attached audience. Returns `200 OK` with the updated [blast object](#blast-object). ### Existing List | Field | Type | Required | Notes | | --------- | -------- | -------- | ------------------------------------------ | | `source` | `string` | Yes | `existing` | | `list_id` | `uuid` | Yes | An existing static or dynamic contact list | ### Inline Static List | Field | Type | Required | Notes | | ------------- | ---------------- | -------- | ------------------------------------------------------------------ | | `source` | `string` | Yes | `inline` | | `kind` | `string` | Yes | `static` | | `name` | `string` | No | `1`-`255` characters. Defaults to `" (campaign )"` | | `description` | `string \| null` | No | Maximum `1000` characters | | `contact_ids` | `uuid[]` | Yes | Contacts to seed the list with, maximum `500` per request | ### Inline Dynamic List | Field | Type | Required | Notes | | ------------- | ---------------- | -------- | ------------------------------------------------------- | | `source` | `string` | Yes | `inline` | | `kind` | `string` | Yes | `dynamic` | | `name` | `string` | No | `1`-`255` characters | | `description` | `string \| null` | No | Maximum `1000` characters | | `rules` | `object` | Yes | Segment rule tree — membership is computed continuously | ### Audience Rules Dynamic audiences are defined by a rule tree of groups and conditions. Rule objects use **camelCase** keys, unlike the rest of the public API — they pass through to the segment engine verbatim. ```json theme={null} { "kind": "group", "operator": "and", "conditions": [ {"kind": "condition", "source": "contact", "field": "state", "operator": "equals", "value": "CA"}, {"kind": "condition", "source": "list_membership", "listId": "11111111-1111-4111-8111-111111111111", "operator": "is_member"} ] } ``` Each condition reads one `source`: | Source | Selector | Operators | | ----------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `contact` | `field` — a standard contact field key in camelCase (e.g. `leadStatus`, `tentedScore`, `createdAt`) | Per field type: strings take `equals`, `not_equals`, `contains`, `not_contains`, `starts_with`, `ends_with`; numbers and dates take `equals`, `not_equals`, `greater_than(_or_equal)`, `less_than(_or_equal)`; booleans take `is_true`, `is_false`. All types take `is_empty` / `is_not_empty` | | `custom_field` | `fieldDefinitionId` — an active custom field definition ID | Same value operators as `contact` | | `activity` | `activityType` (e.g. `form_submission`), optional `timeWindow` (`{"amount": 30, "unit": "day"}`) | `has_activity`, `has_no_activity` | | `list_membership` | `listId` — a static list | `is_member`, `is_not_member` | Groups combine children with `operator: "and"` (default) or `"or"` and can nest up to `5` levels deep, with at most `100` nodes per tree. Shorthand operator aliases (`eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `member_of`, `occurred`, ...) are accepted. `email_step` conditions are not allowed in audiences — they only work inside [flow condition steps](/api-reference/managing-triggered-flows#condition-rules). ## Set the Email ```bash theme={null} PUT /v1/email-blasts/{blastId}/email ``` Reference an existing email with `{"source": "existing", "email_id": "..."}`, or create one inline: | Field | Type | Required | Notes | | ---------------- | -------- | -------- | ------------------------------------------------------------------------------------------ | | `source` | `string` | Yes | `inline` | | `name` | `string` | No | Defaults to `" Email"` | | `subject` | `string` | No | `1`-`998` characters. Must be set before the blast can send | | `template_id` | `uuid` | No | Seed content and default headers from an approved template. Mutually exclusive with `html` | | `html` | `string` | No | Seed content from your own HTML, up to `2 MB`. Mutually exclusive with `template_id` | | `preview_text` | `string` | No | Maximum `200` characters | | `from_name` | `string` | No | Maximum `120` characters. Must be set before the blast can send | | `from_address` | `string` | No | Must be set before the blast can send; domain must be verified | | `reply_to_email` | `string` | No | Must be set before the blast can send | Omit both `template_id` and `html` for a blank branded scaffold. Returns `200 OK` with the updated [blast object](#blast-object). The attached email must be **approved** before the blast can be scheduled or sent. Inline emails start as drafts — take the `email.email_id` from the response and approve it via [`POST /v1/emails/{emailId}/approve`](/api-reference/approving-emails#approve-and-unapprove). ## Approval Readiness `GET /v1/email-blasts/{blastId}` on a draft includes an `approval_readiness` object listing what still blocks sending: ```json theme={null} { "approval_readiness": { "ready": false, "missing": ["email_approval", "schedule"], "warnings": ["empty_static_audience"] } } ``` Scheduling or sending a blast that is not ready returns `400 Bad Request` with `error_code: "campaign_not_ready"` and the same `approval_readiness` details. ## Schedule a Blast ```bash theme={null} POST /v1/email-blasts/{blastId}/schedule ``` | Field | Type | Required | Notes | | -------------- | --------- | -------- | ----------------------------------------------------------------------------------------------- | | `scheduled_at` | `string` | Yes | ISO-8601 datetime with timezone offset, e.g. `2026-07-10T09:00:00-07:00`. Must be in the future | | `operational` | `boolean` | No | Defaults to `false` — see below | Returns `200 OK` with the blast in `scheduled` status. Use `POST .../unschedule` to cancel a scheduled send and return the blast to `draft`. Setting `operational: true` marks the send as transactional: unsubscribed contacts are **not** suppressed and the unsubscribe footer is skipped. Only use it for non-marketing mail such as receipts and service notices. **Free plan:** scheduling always succeeds, but the limits are re-checked when the schedule fires. If at send time the blast's recipient count exceeds what remains of the day's **100-email** allotment — or the workspace is over its **1,000-contact** allowance — the blast is automatically **cancelled** instead of sent: `status` becomes `cancelled`, `cancelled_reason` is set to `daily_email_limit` or `contact_allowance`, no emails go out, and `scheduled_at` keeps the originally scheduled time. A/B test deliveries are guarded the same way at their send moments. Pro and Max sends are never auto-cancelled. ## Send Now ```bash theme={null} POST /v1/email-blasts/{blastId}/send-now ``` Accepts the same optional `operational` flag and starts the send immediately through the async pipeline. Returns `202 Accepted` with the blast in `sending` status; poll `GET /v1/email-blasts/{blastId}` for progress counters. On the Free plan, a send that would exceed the remaining daily 100-email allotment (or a workspace over its 1,000-contact allowance) is rejected up front with `403 Forbidden` and `error_code: "free_plan_email_limit"` — the blast stays an editable `draft`, nothing is cancelled: ```json theme={null} { "error": "Sending this blast to 250 recipients would exceed the free plan's daily limit of 100 emails (100 remaining today). Upgrade to Pro or Max to send more.", "error_code": "free_plan_email_limit", "details": { "reason": "daily_email_limit", "recipient_count": 250, "daily_limit": 100, "sent_today": 0, "remaining_today": 100, "contact_count": 800, "contact_limit": 1000 } } ``` ## Blast Object `200 OK` ```json theme={null} { "blast_id": "e6a1e6de-6a2e-4a3a-9a01-3f1c2b4d5e6f", "type": "email_blast", "name": "July launch", "description": null, "status": "sent", "audience_list_id": "11111111-1111-4111-8111-111111111111", "audience_source": "existing", "audience": { "list_id": "11111111-1111-4111-8111-111111111111", "source": "existing", "name": "Newsletter subscribers", "kind": "static", "member_count": 1250 }, "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "email_source": "existing", "email": { "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "source": "existing", "name": "July launch email", "status": "approved", "subject": "The July release is here", "current_version": 3, "approved_version": 3 }, "operational": false, "scheduled_at": "2026-07-10T16:00:00.000Z", "approved_at": "2026-07-08T09:12:00.000Z", "approved_email_version": 3, "sent_at": "2026-07-10T16:04:12.000Z", "send_mode": "scheduled", "cancelled_reason": null, "cancelled_at": null, "details": { "blast_recipient_count": 1250, "blast_sent_count": 1248, "blast_failed_count": 0, "blast_blocked_count": 2, "blast_qualified_count": 1250, "blast_audience_truncated": false, "blast_delivered_count": 1240, "blast_opened_count": 611, "blast_clicked_count": 187, "blast_bounced_count": 8, "blast_spam_count": 0, "blast_unsubscribed_count": 3 }, "created_at": "2026-07-08T09:00:00.000Z", "updated_at": "2026-07-10T16:20:00.000Z" } ``` `cancelled_at` is stamped on every cancelled blast. `cancelled_reason` stays `null` for user-initiated cancels; it is `daily_email_limit` or `contact_allowance` when the platform auto-cancelled a scheduled Free-plan blast at send time (see [Schedule a Blast](#schedule-a-blast)) — those blasts sent nothing and keep their original `scheduled_at`. ## List Blasts ```bash theme={null} GET /v1/email-blasts ``` | Parameter | Type | Default | Notes | | ------------ | -------- | ------------ | ------------------------------------------------------------------------------------ | | `status` | `string` | `any` | `any`, `draft`, `scheduled`, `sending`, `sent`, `failed`, `cancelled`, or `archived` | | `archived` | `string` | `exclude` | `exclude`, `include`, or `only` | | `sort_by` | `string` | `updated_at` | `updated_at`, `created_at`, `scheduled_at`, or `name` | | `sort_order` | `string` | `desc` | `asc` or `desc` | | `page` | `number` | `1` | 1-based page number | | `limit` | `number` | `25` | Page size, `1`-`100` | | `search` | `string` | — | Case-insensitive name search | Responses contain `blasts` and a `pagination` object with `page`, `limit`, `total`, and `totalPages`. ## Recipients and Engagement ```bash theme={null} GET /v1/email-blasts/{blastId}/contacts ``` Page through a blast's contacts by engagement `category`: `recipients` (everyone prepared for the send, the default), `sent`, `delivered`, `opened`, `clicked`, `bounced`, `unsubscribed`, or `spam`. Also accepts `page`, `limit`, `search`, `sort` (engagement `*_at` timestamps sort contacts without that event last), and `order`. Responses contain `contacts` and a `pagination` object: ```json theme={null} { "contacts": [ { "recipient_id": "01JZ9J8Q2K7X4W1R5T3V6Y0ZBM", "contact_id": "22222222-2222-4222-8222-222222222222", "display_name": "Jane Doe", "email": "jane@example.com", "contact_deleted": false, "sent_at": "2026-07-10T16:04:12.000Z", "delivered_at": "2026-07-10T16:04:15.000Z", "opened_at": "2026-07-10T17:22:03.000Z", "clicked_at": null, "clicked_url": null, "bounced_at": null, "unsubscribed_at": null, "complained_at": null } ], "pagination": {"page": 1, "limit": 25, "total": 611, "totalPages": 25} } ``` ### Export to CSV ```bash theme={null} POST /v1/email-blasts/{blastId}/contacts/export?category=opened GET /v1/email-blasts/{blastId}/contacts/export/{exportId} ``` Starting an export returns `202 Accepted` with an `export` job. Poll the job until `status` is `completed`, then fetch the file from its `download_url`: ```json theme={null} { "export": { "export_id": "01JZ9H2M7Q3W8R5T1V6X0YKZ4B", "status": "completed", "file_name": "july-launch-opened.csv", "row_count": 611, "error_message": null, "download_url": "https://...", "created_at": "2026-07-11T10:00:00.000Z", "updated_at": "2026-07-11T10:00:09.000Z", "completed_at": "2026-07-11T10:00:09.000Z" } } ``` ## Rename, Archive, Delete * `PATCH /v1/email-blasts/{blastId}` updates `name` and/or `description`. * `POST /v1/email-blasts/{blastId}/archive` archives a blast that is not mid-send. * `DELETE /v1/email-blasts/{blastId}` deletes it and returns `{"blast_id": "...", "deleted": true}`. Scheduled blasts must be unscheduled first, and archived blasts cannot be deleted. ## Common Errors | Status | Cause | | ------------------ | ------------------------------------------------------------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body or field validation failure | | `400 Bad Request` | The ID belongs to a triggered flow, not a blast (`campaign_not_blast`) | | `400 Bad Request` | Blast is not ready to schedule or send (`campaign_not_ready`, with `approval_readiness` details) | | `400 Bad Request` | `scheduled_at` is in the past (`campaign_schedule_in_past`) | | `401 Unauthorized` | Missing or invalid bearer token | | `403 Forbidden` | Send Now over a Free-plan limit (`free_plan_email_limit`, with the limit details) — the blast stays a `draft` | | `404 Not Found` | Blast does not exist in the workspace (`campaign_not_found`) | | `409 Conflict` | Audience or email edited outside `draft` status (`campaign_not_editable`) | | `409 Conflict` | Invalid lifecycle transition, e.g. unscheduling a blast that is not scheduled (`campaign_invalid_state_transition`) | | `409 Conflict` | Deleting a scheduled or archived blast (`campaign_not_deletable`) | Automate multi-step email journeys that enroll contacts on triggers. # Email Templates Source: https://docs.tented.ai/api-reference/managing-email-templates Create, generate, and approve reusable email templates through the public API. ## Endpoints ```bash theme={null} GET /v1/email-templates POST /v1/email-templates GET /v1/email-templates/{templateId} PATCH /v1/email-templates/{templateId} DELETE /v1/email-templates/{templateId} GET /v1/email-templates/{templateId}/content GET /v1/email-templates/{templateId}/plain-text PUT /v1/email-templates/{templateId}/plain-text DELETE /v1/email-templates/{templateId}/plain-text POST /v1/email-templates/{templateId}/messages POST /v1/email-templates/{templateId}/save-code POST /v1/email-templates/{templateId}/clone POST /v1/email-templates/{templateId}/approve POST /v1/email-templates/{templateId}/unapprove GET /v1/email-templates/{templateId}/generations/{generationId} GET /v1/email-templates/{templateId}/generations/{generationId}/content GET /v1/email-templates/{templateId}/generations/{generationId}/plain-text ``` Email templates are reusable layouts that seed new [emails](/api-reference/creating-emails). A template carries HTML plus optional **default sender headers** (`default_subject`, `default_from_name`, and so on). Once a template is **approved**, [`POST /v1/emails` with `template_id`](/api-reference/creating-emails#create-from-a-template) copies its HTML into a new email and inherits those defaults. Like emails, templates start as a `draft` and accumulate versions as you iterate. AI generation is asynchronous. Creating with a `prompt` or posting a message returns `202 Accepted` with a `generation_id`, and you poll the generation endpoint to track progress. Only one generation can run per template at a time. ## Idempotency `POST /v1/email-templates`, `POST /v1/email-templates/{templateId}/messages`, and `POST /v1/email-templates/{templateId}/save-code` accept an optional `Idempotency-Key` header. Retrying with the same key replays the stored result instead of creating a duplicate. If a request with the same key is still being processed, the API returns `409 Conflict` with `error_code: "idempotency_in_progress"`. ## Create a Template ```bash theme={null} POST /v1/email-templates ``` ### Request Body | Field | Type | Required | Notes | | ------------------------ | ---------------- | -------- | ----------------------------------------------------------------------------------- | | `name` | `string` | Yes | Internal display name, `1`-`200` characters. Not shown to recipients | | `code` | `string` | No | Hand-authored template HTML, up to `2 MB` | | `prompt` | `string` | No | AI brief, `1`-`10000` characters. When present, content is generated asynchronously | | `default_subject` | `string \| null` | No | Default subject for seeded emails, maximum `998` characters | | `default_preview_text` | `string \| null` | No | Default preheader for seeded emails, maximum `200` characters | | `default_from_name` | `string \| null` | No | Default sender name for seeded emails, maximum `120` characters | | `default_from_address` | `string \| null` | No | Default sender address; sending still requires a verified domain | | `default_reply_to_email` | `string \| null` | No | Default Reply-To for seeded emails | Provide `code` to upload your own HTML, or `prompt` to generate it with AI (`202 Accepted`). With neither, you get a blank scaffold branded from your workspace. Both `code`-based and blank creates return `201 Created` with the template object. ### Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/email-templates \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Monthly newsletter shell", "prompt": "A newsletter layout for Acme Analytics: header logo, intro block, three article cards, footer", "default_subject": "Acme Analytics monthly digest", "default_from_name": "Acme" }' ``` ### Response Example `202 Accepted` ```json theme={null} { "template_id": "7c3f0a4e-91d2-4a8f-b344-2f6f6f0a1b9d", "generation_id": "01JZ9GLYFA4L4Y9CBM4H31TT8V", "message_id": "01JZ9GLYFA6H2T0N8W1QG64M3E", "status": "generating" } ``` ## Poll a Generation ```bash theme={null} GET /v1/email-templates/{templateId}/generations/{generationId} ``` Generation `status` moves through `generating` to `completed` or `failed`, with the same shape as [email generations](/api-reference/creating-emails#poll-a-generation): completed generations include the `version` they produced plus `content_path` and `plain_text_path` under `/v1/email-templates/...`; failed ones return `error_code: "generation_failed"` and an `error_message`. ## Retrieve a Template ```bash theme={null} GET /v1/email-templates/{templateId} ``` `200 OK` ```json theme={null} { "template_id": "7c3f0a4e-91d2-4a8f-b344-2f6f6f0a1b9d", "name": "Monthly newsletter shell", "status": "draft", "source_type": "upload", "source_email_id": null, "default_subject": "Acme Analytics monthly digest", "default_preview_text": null, "default_from_name": "Acme", "default_from_address": null, "default_reply_to_email": null, "current_version": 1, "approved_version": null, "number_of_iterations": 1, "plain_text_overridden": false, "plain_text_overridden_at": null, "plain_text_stale_after_html_iteration": false, "created_at": "2026-07-01T12:00:00.000Z", "updated_at": "2026-07-01T12:00:41.000Z", "created_by_name": "tented-api", "latest_generation_status": "completed", "content_path": "/v1/email-templates/7c3f0a4e-91d2-4a8f-b344-2f6f6f0a1b9d/content", "plain_text_path": "/v1/email-templates/7c3f0a4e-91d2-4a8f-b344-2f6f6f0a1b9d/plain-text" } ``` `status` is `draft` or `approved`. `source_type` is `upload` for API-created templates, or `email` for templates saved from an existing email (then `source_email_id` points to it). ## Read Content ```bash theme={null} GET /v1/email-templates/{templateId}/content GET /v1/email-templates/{templateId}/generations/{generationId}/content ``` Returns the HTML of the latest completed version (or of one specific generation) with a `text/html` content type — not JSON. ## Plain Text ```bash theme={null} GET /v1/email-templates/{templateId}/plain-text PUT /v1/email-templates/{templateId}/plain-text DELETE /v1/email-templates/{templateId}/plain-text ``` Works exactly like [email plain text](/api-reference/editing-emails#plain-text), keyed by `template_id` instead of `email_id`: `GET` returns the auto-derived text, `PUT` overrides it (body: `{"content": "..."}`, up to `1 MB`), and `DELETE` reverts to the auto-derived version. `PUT` and `DELETE` require a completed generation, otherwise they return `400 Bad Request`. Iterating the HTML after an override flips `stale_after_html_iteration` to `true`. ## Iterate With AI ```bash theme={null} POST /v1/email-templates/{templateId}/messages ``` Takes the same request body as [iterating an email](/api-reference/editing-emails#iterate-with-ai): a required `prompt` (`1`-`10000` characters) with the edit instruction, plus optional `asset_ids` naming template assets to make available to the generation. Returns `202 Accepted` with a `generation_id` to poll. Starting a second generation while one is running returns `409 Conflict` with `error_code: "generation_in_progress"`; an unknown asset returns `409` with `error_code: "asset_not_found"`. ## Save Code Directly ```bash theme={null} POST /v1/email-templates/{templateId}/save-code ``` Works like [saving email code](/api-reference/editing-emails#save-code-directly): send `{"code": "..."}` with the full replacement HTML (up to `2 MB`) to create a new version synchronously — `200 OK` with the new `generation_id` and `version`. Saving code does not unapprove an approved template. Blocked while a generation is running. ## Update Metadata ```bash theme={null} PATCH /v1/email-templates/{templateId} ``` Accepts the same optional fields as create except `code` and `prompt`: `name`, `default_subject`, `default_preview_text`, `default_from_name`, `default_from_address`, `default_reply_to_email`. Omit a field to leave it unchanged; pass `null` to clear nullable fields. Returns the updated template object. ## Clone a Template ```bash theme={null} POST /v1/email-templates/{templateId}/clone ``` Duplicate a template — its content, versions, and assets — as a fresh `draft` attributed to the API principal. The clone is never approved. Optionally send `{"name": "..."}`; the name defaults to `"{original name} (copy)"`. Returns `201 Created` with the new template object. Accepts an optional `Idempotency-Key` header. ## Approve and Unapprove ```bash theme={null} POST /v1/email-templates/{templateId}/approve POST /v1/email-templates/{templateId}/unapprove ``` Approving marks the template's newest **completed** version usable for seeding new emails. Unlike [emails](/api-reference/approving-emails#approve-and-unapprove), there is no required-header gate — the `default_*` fields are optional — but the template must have at least one completed generation, otherwise the API returns `409 Conflict` with `error_code: "template_not_ready"`. Optionally send `{"version": }` as an optimistic-concurrency precondition — a mismatch with the newest completed version (normally equal to `current_version`) returns `409 Conflict` with `error_code: "approval_version_stale"`. While a generation is running, an approve **without** `version` is refused with `409 Conflict` and `error_code: "generation_in_progress"`; passing `version` explicitly still approves that already-completed version mid-generation. Unapproving is never blocked: emails already seeded from the template are unaffected, but new emails can no longer be created from it until it is approved again. Both endpoints return the updated template object. ## List Templates ```bash theme={null} GET /v1/email-templates ``` Takes the same query parameters as [listing emails](/api-reference/approving-emails#list-emails): `status` (`any`, `draft`, `approved`), `sort_by`, `sort_order`, `limit`, and `cursor`. Responses contain `templates` and `next_cursor`. List items are the template object minus `latest_generation_status` and the `content_path` / `plain_text_path` fields — fetch HTML per template via `GET /v1/email-templates/{templateId}/content`. ## Delete a Template ```bash theme={null} DELETE /v1/email-templates/{templateId} ``` Returns `200 OK` with `{"template_id": "...", "deleted": true}`. Unlike [emails](/api-reference/approving-emails#delete-an-email), deletion is never blocked — emails already seeded from the template keep their own copy of the content. ## Common Errors | Status | Cause | | ------------------ | ---------------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body or field validation failure | | `400 Bad Request` | Plain-text override or revert without a completed generation | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | Template or generation does not exist in the workspace | | `409 Conflict` | A generation is already running (`generation_in_progress`) | | `409 Conflict` | Approval attempted with no completed generation (`template_not_ready`) | | `409 Conflict` | Approval version precondition failed (`approval_version_stale`) | | `409 Conflict` | Referenced asset does not exist (`asset_not_found`) | | `409 Conflict` | Same `Idempotency-Key` still processing (`idempotency_in_progress`) | Seed a new email from your approved template and send it with blasts and flows. # Tent Templates Source: https://docs.tented.ai/api-reference/managing-tent-templates Create, iterate, and approve reusable tent layouts through the public API. ## Endpoints ```bash theme={null} GET /v1/tent-templates POST /v1/tent-templates POST /v1/tent-templates/from-tent/{tentId} GET /v1/tent-templates/{templateId} DELETE /v1/tent-templates/{templateId} GET /v1/tent-templates/{templateId}/content POST /v1/tent-templates/{templateId}/messages POST /v1/tent-templates/{templateId}/save-code POST /v1/tent-templates/{templateId}/approve POST /v1/tent-templates/{templateId}/unapprove POST /v1/tent-templates/{templateId}/clone GET /v1/tent-templates/{templateId}/generations/{generationId} GET /v1/tent-templates/{templateId}/generations/{generationId}/content ``` Tent templates are reusable, AI-editable page layouts that seed new [tents](/api-reference/creating-tents). Once a template is **approved**, [`POST /v1/tents` with `template_id`](/api-reference/creating-tents#create-from-a-template) starts a new generation from its HTML instead of from a blank page. Templates start as a `draft` and accumulate versions as you iterate — either by editing the HTML directly or by prompting the AI. AI iteration is asynchronous. Posting a message returns `202 Accepted` with a `generation_id`, and you poll the generation endpoint to track progress. Only one generation can run per template at a time. Assets (images and files an iteration can reference) are managed with the shared asset endpoints — see [Managing Assets](/api-reference/uploading-assets#assets-on-emails-templates-and-tent-templates). ## Idempotency `POST /v1/tent-templates`, `POST /v1/tent-templates/from-tent/{tentId}`, `POST /v1/tent-templates/{templateId}/messages`, `POST /v1/tent-templates/{templateId}/save-code`, and `POST /v1/tent-templates/{templateId}/clone` accept an optional `Idempotency-Key` header. Retrying with the same key replays the stored result instead of creating a duplicate. If a request with the same key is still being processed, the API returns `409 Conflict` with `error_code: "idempotency_in_progress"`. ## Create a Template ```bash theme={null} POST /v1/tent-templates ``` ### Request Body | Field | Type | Required | Notes | | ------ | -------- | -------- | --------------------------------------------------------------------------------- | | `name` | `string` | Yes | Internal display name, `1`-`200` characters | | `code` | `string` | Yes | Full template HTML, `1 byte`-`2 MB`. Must contain a `` declaration | Creating with `code` seeds a completed v1 that the editor loads directly and returns `201 Created` with the template object. To create from an existing tent instead, use [`POST /v1/tent-templates/from-tent/{tentId}`](#create-from-a-tent); to change the HTML with AI afterward, [iterate with a message](#iterate-with-ai). ### Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tent-templates \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Product launch landing page", "code": "......" }' ``` ### Response Example `201 Created` ```json theme={null} { "template_id": "01JPC2YJ5S6M3T5H8XQ4N7R9AB", "name": "Product launch landing page", "status": "draft", "source_type": "upload", "source_tent_id": null, "current_version": 1, "approved_version": null, "number_of_iterations": 1, "created_at": "2026-07-08T12:00:00.000Z", "updated_at": "2026-07-08T12:00:00.000Z", "created_by_name": "tented-api", "content_path": "/v1/tent-templates/01JPC2YJ5S6M3T5H8XQ4N7R9AB/content" } ``` `status` is `draft` or `approved`. `source_type` is `upload` for API- or hand-authored templates, or `tent` for templates created from an existing tent (then `source_tent_id` points to it). On a subsequent `GET`, the object also includes `latest_generation_status` (`generating`, `completed`, or `failed`). ## Create From a Tent ```bash theme={null} POST /v1/tent-templates/from-tent/{tentId} ``` Turn an existing tent's latest completed render into a reusable template — its HTML and assets are copied over. Send `{"name": "..."}` with the new template's display name. Returns `201 Created` with the template object (`source_type: "tent"`). ## Iterate With AI ```bash theme={null} POST /v1/tent-templates/{templateId}/messages ``` Queue an AI edit against the template's current HTML. Send a required `prompt` (`1`-`10000` characters) with the instruction, plus optional `asset_ids` naming [template assets](/api-reference/uploading-assets#assets-on-emails-templates-and-tent-templates) to make available to the generation. `202 Accepted` ```json theme={null} { "template_id": "01JPC2YJ5S6M3T5H8XQ4N7R9AB", "generation_id": "01JZ9GLYFA4L4Y9CBM4H31TT8V", "message_id": "01JZ9GLYFA6H2T0N8W1QG64M3E", "status": "generating" } ``` Starting a second generation while one is running returns `409 Conflict` with `error_code: "generation_in_progress"`. ## Poll a Generation ```bash theme={null} GET /v1/tent-templates/{templateId}/generations/{generationId} ``` Generation `status` moves through `generating` to `completed` or `failed`. Completed generations include the `version` they produced plus a `content_path` under `/v1/tent-templates/...`; failed ones return `error_code: "generation_failed"` and an `error_message`. ## Read Content ```bash theme={null} GET /v1/tent-templates/{templateId}/content GET /v1/tent-templates/{templateId}/generations/{generationId}/content ``` Returns the HTML of the latest completed version (or of one specific generation) with a `text/html` content type — not JSON. ## Save Code Directly ```bash theme={null} POST /v1/tent-templates/{templateId}/save-code ``` Send `{"code": "..."}` with the full replacement HTML (`1 byte`-`2 MB`) to create a new version synchronously — `200 OK` with the new `generation_id` and `version`. Blocked while an AI generation is running. ## Approve and Unapprove ```bash theme={null} POST /v1/tent-templates/{templateId}/approve POST /v1/tent-templates/{templateId}/unapprove ``` Approving marks the template's newest **completed** version usable for seeding new tents via [`POST /v1/tents` with `template_id`](/api-reference/creating-tents#create-from-a-template). The template must have at least one completed generation. Optionally send `{"version": }` as an optimistic-concurrency precondition — a mismatch with the newest completed version (normally equal to `current_version`) returns `409 Conflict` with `error_code: "approval_version_stale"`. Approval works while a generation is running — it pins the latest already-completed version; the running generation, once finished, produces a newer draft version until you approve again. Unapproving returns the template to draft and is never blocked: tents already seeded from the template are unaffected, but the template can no longer seed new tents until it is approved again. Both endpoints return the updated template object. ## Clone a Template ```bash theme={null} POST /v1/tent-templates/{templateId}/clone ``` Duplicate the template — its chat, generation history, and assets — as a fresh `draft`. Optionally send `{"name": "..."}`; the name defaults to `"Copy of {name}"`. Returns `201 Created` with the new template object. ## List Templates ```bash theme={null} GET /v1/tent-templates ``` Cursor-paginated, with the same query parameters as [listing email templates](/api-reference/managing-email-templates#list-templates): `status` (`any`, `draft`, `approved`), `sort_by` (`updated_at`, `created_at`, `name`), `sort_order`, `limit` (`1`-`100`), and `cursor`. Responses contain `templates` and `next_cursor`. List items carry metadata only (no `latest_generation_status` or `content_path`) — fetch HTML per template via `GET /v1/tent-templates/{templateId}/content`. ## Delete a Template ```bash theme={null} DELETE /v1/tent-templates/{templateId} ``` Returns `200 OK` with `{"template_id": "...", "deleted": true}`. Deletion is never blocked — tents already seeded from the template keep their own copy of the content. ## Common Errors | Status | Cause | | ------------------ | --------------------------------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body or field validation failure (e.g. `code` missing a ``) | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | Template, tent, or generation does not exist in the workspace | | `409 Conflict` | A generation is already running (`generation_in_progress`) | | `409 Conflict` | Approval version precondition failed (`approval_version_stale`) | | `409 Conflict` | Same `Idempotency-Key` still processing (`idempotency_in_progress`) | Seed a new tent from your approved template with `POST /v1/tents`. # Managing Triggered Flows Source: https://docs.tented.ai/api-reference/managing-triggered-flows Build, activate, and monitor automated multi-step email journeys through the public API. ## Endpoints ```bash theme={null} GET /v1/email-flows POST /v1/email-flows GET /v1/email-flows/{flowId} PATCH /v1/email-flows/{flowId} DELETE /v1/email-flows/{flowId} PUT /v1/email-flows/{flowId}/triggers PUT /v1/email-flows/{flowId}/steps POST /v1/email-flows/{flowId}/steps PATCH /v1/email-flows/{flowId}/steps/{stepId} DELETE /v1/email-flows/{flowId}/steps/{stepId} PUT /v1/email-flows/{flowId}/steps/{stepId}/email POST /v1/email-flows/{flowId}/activate POST /v1/email-flows/{flowId}/pause POST /v1/email-flows/{flowId}/archive POST /v1/email-flows/{flowId}/trigger POST /v1/email-flows/{flowId}/contacts/{contactId} DELETE /v1/email-flows/{flowId}/contacts/{contactId} GET /v1/email-flows/{flowId}/members GET /v1/email-flows/{flowId}/members/{membershipId} ``` A triggered flow enrolls contacts when they match a **trigger** and moves them through a graph of **steps** — sending emails, waiting, branching, updating fields. See the [Triggered Flows guide](/email/triggered-flows) for the concepts; this page covers the API surface. Flow `status` is `draft`, `active`, `paused`, or `archived`. Structure (triggers, steps, reentry policy) is only editable while the flow is a `draft` or `paused` — pause an active flow before changing it, then reactivate. Drafts may be incomplete. Step and trigger *format* is validated on every write, but *completeness* (a `send_email` step without an email, a trigger without its list) is only enforced when you activate. Activation failures return the missing pieces. ## Create a Flow ```bash theme={null} POST /v1/email-flows ``` | Field | Type | Required | Notes | | ------------------------ | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | Yes | Internal display name, `1`-`200` characters | | `description` | `string \| null` | No | Maximum `1000` characters | | `reentry_policy` | `string` | No | `allow` (default) lets contacts re-enter after exiting; `no_reentry` runs each contact at most once | | `triggers` | `array` | No | Entry triggers, maximum `25` | | `steps` | `array` | No | Step list, maximum `100` steps | | `disqualification_rules` | `object \| null` | No | Contacts matching this rule tree are dropped from the flow. Same rule format as [blast audiences](/api-reference/managing-email-blasts#audience-rules); `email_step` conditions are not allowed here | | `settings` | `object` | No | Free-form settings blob, maximum `16 KB` | Triggers and steps can be supplied here or configured incrementally before activation. ### Request Example A welcome series — send an email, wait three days, then follow up only if the first email wasn't opened: ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/email-flows \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "name": "Welcome series", "reentry_policy": "no_reentry", "triggers": [ {"type": "added_to_static_list", "list_id": "11111111-1111-4111-8111-111111111111"} ], "steps": [ { "step_id": "send-welcome", "type": "send_email", "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "next_step_id": "wait-3-days" }, { "step_id": "wait-3-days", "type": "wait", "duration_amount": 3, "duration_unit": "days", "next_step_id": "check-opened" }, { "step_id": "check-opened", "type": "condition_check", "condition": { "kind": "group", "operator": "and", "conditions": [ {"kind": "condition", "source": "email_step", "nodeId": "send-welcome", "metric": "opened", "operator": "has_activity"} ] }, "false_step_id": "send-followup" }, { "step_id": "send-followup", "type": "send_email", "email_id": "a3d1b2c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d" } ] }' ``` Members who open the welcome email exit at `check-opened` (its true branch has no edge); everyone else gets the follow-up. ### Response Example `201 Created` with the flow object. All flow and step endpoints on this page return this same shape, except the member endpoints: ```json theme={null} { "flow_id": "7c1b9e2a-4f3d-4b6a-9e8c-2d5f7a1b3c4e", "type": "triggered_flow", "name": "Welcome series", "description": null, "status": "draft", "triggers": [ {"type": "added_to_static_list", "list_id": "11111111-1111-4111-8111-111111111111"} ], "steps": [ {"step_id": "send-welcome", "type": "send_email", "email_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "next_step_id": "wait-3-days"}, {"step_id": "wait-3-days", "type": "wait", "duration_amount": 3, "duration_unit": "days", "next_step_id": "check-opened"}, { "step_id": "check-opened", "type": "condition_check", "condition": { "kind": "group", "operator": "and", "conditions": [ {"kind": "condition", "source": "email_step", "nodeId": "send-welcome", "metric": "opened", "operator": "has_activity"} ] }, "false_step_id": "send-followup" }, {"step_id": "send-followup", "type": "send_email", "email_id": "a3d1b2c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"} ], "disqualification_rules": null, "reentry_policy": "no_reentry", "manual_membership_enabled": true, "settings": {}, "details": { "total_entered": 0, "active_count": 0, "waiting_count": 0, "completed_count": 0, "dropped_count": 0, "most_recent_entry_at": null }, "archived_at": null, "created_at": "2026-07-02T09:00:00.000Z", "updated_at": "2026-07-02T09:00:00.000Z" } ``` ## Triggers A flow can have up to `25` triggers; a contact entering via any of them joins the flow, subject to the reentry policy. Each trigger is an object discriminated on `type`: | Type | Configuration | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `added_to_static_list` | `list_id` — fires when a contact is added to that static list | | `removed_from_static_list` | `list_id` — fires when a contact is removed from that static list | | `contact_created` | None — fires for every newly created contact | | `contact_updated` | `field` (standard camelCase key or custom field `api_name`), optional `operator` + `value` to filter on the new value. `changed` fires on any change | | `date_based` | `field` (`createdAt` or an active custom date field), `mode` (`on_date`, `before_date` + `days_before`, or `after_date` + `days_after`), optional `include_anniversaries`, `trigger_time` (`"HH:00"` or `"HH:30"`, default `"09:00"`), `timezone` (IANA, default `"UTC"`) | | `form_submitted` | `tent_id` + `form_id` — fires when a contact submits that form. Submitted answers become `{{campaign.dynamic.}}` tokens (field names sanitized to letters/numbers/underscores) | | `tented_api` | Optional `fields` — an array of `{name, type}` (`string`, `number`, or `boolean`) declaring the token keys callers may pass to [Trigger a Flow](#trigger-a-flow-api-trigger). `"trigger"` is reserved. No configuration required before activation | Replace the full trigger list with: ```bash theme={null} PUT /v1/email-flows/{flowId}/triggers ``` ```json theme={null} { "triggers": [ {"type": "added_to_static_list", "list_id": "11111111-1111-4111-8111-111111111111"} ] } ``` ## Steps Steps form a graph. Every step needs a unique `step_id` (`1`-`120` characters) and links to the next step through edge fields — `next_step_id` for linear steps, or `true_step_id` / `false_step_id` / `default_step_id` on condition steps. Omitting the edge ends the flow after that step. An optional `label` names the step in the UI and member history. | Type | Configuration | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `send_email` | `email_id` (must be approved before activation), optional `operational` flag | | `wait` | Exactly one mode: `duration_amount` + `duration_unit` (`minutes`, `hours`, `days`, `weeks`), `until_date_field` (a contact date field), or `condition` (a rule tree re-evaluated periodically) | | `update_contact` | `updates` — an array of `{field, value, write_mode?}` assignments (`overwrite` or `skip_if_filled`), or a single `field` + `value` shorthand | | `add_to_list` | `list_id` — must be a static list | | `remove_from_list` | `list_id` — must be a static list | | `condition_check` | `condition` — a rule tree; branches via `true_step_id` / `false_step_id` / `default_step_id` | | `create_tent` | `source` (`scratch` or `template` + `template_id`), `prompt`, `auto_publish`, `custom_page_alias`, `tent_name`. `prompt`, alias, and name support `{{contact.*}}` merge tokens | | `send_webhook` | `endpoint_url` (public http(s) only), `method` (`post` default, `put`, `get`), `auth_type` (`none` or `jwt` with `token` or `secret_arn`), `custom_fields` merged into the payload | | `drop_from_flow` | None — terminal step that removes the member immediately | ### Condition Rules `condition_check` and condition-mode `wait` steps take the same rule tree as [blast audiences](/api-reference/managing-email-blasts#audience-rules), plus one extra source that is only legal inside flows: `email_step`, which tests engagement with an earlier `send_email` step. ```json theme={null} { "kind": "group", "operator": "and", "conditions": [ {"kind": "condition", "source": "email_step", "nodeId": "send-welcome", "metric": "opened", "operator": "has_activity"} ] } ``` `metric` is one of `sent`, `delivered`, `opened`, `clicked`, `bounced`, or `unsubscribed`, and `nodeId` references the `step_id` of a `send_email` step in the same flow. ### Editing Steps ```bash theme={null} PUT /v1/email-flows/{flowId}/steps # replace the full list POST /v1/email-flows/{flowId}/steps # insert one step PATCH /v1/email-flows/{flowId}/steps/{stepId} # merge a partial update DELETE /v1/email-flows/{flowId}/steps/{stepId} # delete one step ``` `PUT` takes `{"steps": [...]}` and replaces the whole list. `POST` inserts one step, wrapped in a `step` key, plus at most one placement selector — `position` (`start` or `end`, default `end`), `before_step_id`, or `after_step_id`: ```json theme={null} { "step": {"step_id": "send-reminder", "type": "send_email", "email_id": "...", "next_step_id": "check-opened"}, "after_step_id": "wait-3-days" } ``` Inserting does not rewrite edges — set the `*_step_id` fields yourself to wire the step in. Duplicate step IDs return `409 Conflict` with `error_code: "campaign_flow_duplicate_step_id"`. `PATCH` also wraps the partial update in a `step` key; only the provided fields are merged onto the existing step, and `step_id` cannot be changed: ```json theme={null} {"step": {"duration_amount": 5, "duration_unit": "days"}} ``` Deleting a step that other steps still reference fails with `409 Conflict` (`campaign_flow_step_referenced`) unless you pass `repair_edges`: ```json theme={null} {"repair_edges": {"replace_with_step_id": "send-followup"}} ``` Use `null` as the replacement to detach the dangling edges instead. ### Set a Step's Email ```bash theme={null} PUT /v1/email-flows/{flowId}/steps/{stepId}/email ``` Only valid for `send_email` steps. Takes the same body as a blast's [Set the Email](/api-reference/managing-email-blasts#set-the-email): `{"source": "existing", "email_id": "..."}` or an inline email seeded from a `template_id`, your own `html`, or a blank branded scaffold. Inline emails start as drafts and must be [approved](/api-reference/approving-emails#approve-and-unapprove) before the flow can activate — the created `email_id` appears on the step in the response. ## Lifecycle ```bash theme={null} POST /v1/email-flows/{flowId}/activate POST /v1/email-flows/{flowId}/pause POST /v1/email-flows/{flowId}/archive ``` Activation requires a **Pro or Max** plan. Free workspaces can create and edit flows through the API, but `POST .../activate` returns `403 Forbidden` with `error_code: "paid_plan_required"`: ```json theme={null} { "error": "Activating flows requires a Pro or Max plan. Upgrade to activate this flow.", "error_code": "paid_plan_required" } ``` Activation also validates the whole flow. If anything is incomplete, the API returns `400 Bad Request` with `error_code: "campaign_flow_not_ready"` and the missing fields: ```json theme={null} { "error": "Triggered flow is missing required activation fields: flow_nodes.0.email_approval", "error_code": "campaign_flow_not_ready", "details": { "activation_readiness": { "ready": false, "missing": ["flow_nodes.0.email_approval"], "warnings": [] } } } ``` Pausing stops new work without dropping members — they resume when you reactivate. Active flows must be paused before archiving or deleting. Triggers fire on new events only; contacts already on a list do not enter an `added_to_static_list` flow retroactively. ## Members ### Add a Contact ```bash theme={null} POST /v1/email-flows/{flowId}/contacts/{contactId} ``` Manually enrolls a contact into an **active** flow. Optionally pass per-member context that emails and webhooks in this run can reference as `{{campaign.dynamic.}}` merge tokens: ```json theme={null} {"dynamic_context": {"couponCode": "SAVE20"}} ``` Merge tokens — `{{contact.*}}` and `{{campaign.dynamic.*}}` alike — accept an optional case format, e.g. `{{campaign.dynamic.couponCode:uppercase}}`. See [Formatting values](/email/personalization#formatting-values). Enrollment respects the flow's rules: `409 Conflict` if the contact is already in the flow (`campaign_flow_already_in_flow`) or blocked by `no_reentry` (`campaign_flow_reentry_denied`), and `400 Bad Request` if disqualification rules match (`campaign_flow_contact_disqualified`). `201 Created` ```json theme={null} { "membership": { "membership_id": "9e2f4a6b-8c1d-4e3f-a5b7-c9d1e3f5a7b9", "flow_id": "7c1b9e2a-4f3d-4b6a-9e8c-2d5f7a1b3c4e", "contact_id": "22222222-2222-4222-8222-222222222222", "status": "active", "current_step_id": "send-welcome", "waiting_until": null, "entry_source": "public_api", "entered_at": "2026-07-02T10:00:00.000Z", "completed_at": null, "dropped_at": null, "drop_reason": null, "updated_at": "2026-07-02T10:00:00.000Z" } } ``` ### Trigger a Flow (API trigger) ```bash theme={null} POST /v1/email-flows/{flowId}/trigger ``` Enrolls a contact through the flow's `tented_api` trigger — `409 Conflict` (`flow_api_trigger_not_configured`) if the flow doesn't have one. Identify the contact with **exactly one** of `contact_id`, `email`, or `phone` (the latter two are looked up against existing contacts), and optionally pass token values: ```json theme={null} {"email": "ada@example.com", "tokens": {"coupon_code": "SAVE20"}} ``` Each token becomes a `{{campaign.dynamic.}}` merge token for the run's emails and webhooks — `{{campaign.dynamic.coupon_code}}` above. The rules: * Values must be JSON scalars (string, number, or boolean); keys start with a letter and use letters/numbers/underscores. `"trigger"` is reserved. * If the flow's `tented_api` trigger declares `fields`, keys are restricted to the declared names and values must match the declared types (`campaign_flow_trigger_unknown_token` / `campaign_flow_trigger_invalid_token_type`). A trigger with no declared fields accepts any keys. * The resolved payload must stay under 64 KB (`campaign_flow_trigger_payload_too_large`). Enrollment follows the same rules as [Add a Contact](#add-a-contact) (reentry policy, disqualification) and returns `201 Created` with the membership. ### Remove a Contact ```bash theme={null} DELETE /v1/email-flows/{flowId}/contacts/{contactId} ``` Drops the contact's current run. Returns `200 OK` with the updated membership, or `204 No Content` if the contact was not in the flow. ### List Members ```bash theme={null} GET /v1/email-flows/{flowId}/members ``` | Parameter | Type | Default | Notes | | ----------------------------- | ---------- | ---------- | --------------------------------------------------------- | | `status` | `string` | `any` | `active`, `waiting`, `completed`, `dropped`, or `any` | | `run_status` | `string` | `any` | `in_flow`, `completed`, `failed`, `exited_flow`, or `any` | | `entered_from` / `entered_to` | `datetime` | — | Filter by entry time | | `exited_from` / `exited_to` | `datetime` | — | Filter by exit time | | `page` / `limit` | `number` | `1` / `25` | Page-based pagination, limit `1`-`100` | Each member entry includes the contact identity, run status, current step, timestamps, and the steps executed so far. ### Run Details ```bash theme={null} GET /v1/email-flows/{flowId}/members/{membershipId} ``` Returns one member's full run: status, current position, failure info, and a `timeline` of events (entered flow, step executed, email sent/delivered/opened/clicked, dropped) with timestamps. ## List Flows ```bash theme={null} GET /v1/email-flows ``` Takes the same shape as [listing blasts](/api-reference/managing-email-blasts#list-blasts): `status` (`draft`, `active`, `paused`, `archived`, or `any`), `archived`, `sort_by` (`updated_at`, `created_at`, `name`), `sort_order`, `page`, `limit`, and `search`. Responses contain `flows` and a `pagination` object. Each flow includes aggregate `details` — `total_entered`, `active_count`, `waiting_count`, `completed_count`, `dropped_count`, and `most_recent_entry_at`. ## Update, Archive, Delete * `PATCH /v1/email-flows/{flowId}` patches `name`, `description`, `reentry_policy`, `triggers`, `steps`, `disqualification_rules`, or `settings`. `triggers` and `steps` are full replacements — use the step endpoints for incremental edits. Setting `disqualification_rules` on an active flow immediately drops current members that match. * `POST /v1/email-flows/{flowId}/archive` archives a non-active flow. * `DELETE /v1/email-flows/{flowId}` deletes it and returns `{"flow_id": "...", "deleted": true}`. Active flows must be paused first. ## Common Errors | Status | Cause | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `400 Bad Request` | Invalid JSON body, malformed trigger/step/rule config | | `400 Bad Request` | The ID belongs to a blast, not a flow (`campaign_not_triggered_flow`) | | `400 Bad Request` | Activation with incomplete config (`campaign_flow_not_ready`, with missing fields) | | `400 Bad Request` | Contact matches the disqualification rules (`campaign_flow_contact_disqualified`) | | `401 Unauthorized` | Missing or invalid bearer token | | `403 Forbidden` | Activation on the Free plan (`paid_plan_required`) — flows activate on Pro/Max only | | `404 Not Found` | Flow, step, or membership does not exist (`campaign_not_found`, `campaign_flow_step_not_found`) | | `409 Conflict` | Structural edit while the flow is `active` or `archived` (`campaign_not_editable`) | | `409 Conflict` | Invalid lifecycle transition (`campaign_invalid_state_transition`) | | `409 Conflict` | Duplicate or still-referenced step (`campaign_flow_duplicate_step_id`, `campaign_flow_step_referenced`) | | `409 Conflict` | Enrolling into a non-active flow (`campaign_flow_not_active`), a contact already in the flow, or one blocked by `no_reentry` | Review the full public API endpoint map. # Publishing Tents Source: https://docs.tented.ai/api-reference/publishing-tents Publish the latest completed generation of a tent through the public API. ## Endpoint ```bash theme={null} POST /v1/tents/{tentId}/publish ``` Use this endpoint to publish a tent manually after generation has already completed. ## Request Body | Field | Type | Required | Notes | | ------------------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------- | | `custom_page_alias` | `string \| null` | No | Sets or replaces the published alias. Use `null` to clear an existing alias and publish at the default tent URL | | `remove_branding` | `boolean` | No | Requests a branding-free published experience when available for the workspace | ## Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1/publish \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "custom_page_alias": "launch-page" }' ``` ## Successful Response ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "updated_at": "2026-04-08T12:40:00.000Z", "message": "Generation published successfully", "publication": { "status": "published", "published_url": "https://your-workspace.tented-pages.com/launch-page", "requested_custom_page_alias": "launch-page", "applied_custom_page_alias": "launch-page" } } ``` ## No-Op Response If the tent is already live with the latest completed generation, publish still returns `200 OK` with an explanatory message: ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "updated_at": "2026-04-08T12:41:00.000Z", "message": "No changes since last publish. This tent is already live with the latest completed generation.", "publication": { "status": "published", "published_url": "https://your-workspace.tented-pages.com/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1" } } ``` ## Clearing an Alias Send `custom_page_alias: null` to remove a previously assigned alias and publish at the default tent URL: ```json theme={null} { "custom_page_alias": null } ``` ## Alias Rules * Maximum `100` characters * Lowercase letters, numbers, hyphens, underscores, and dots only * Must start with a letter or number * Must not be a UUID * Must not use reserved words such as `api`, `admin`, `submit`, or `assets` * Use `/` to publish at the domain root ## Publish Limit Response If the workspace cannot publish another tent, the API returns `429 Too Many Requests`: ```json theme={null} { "error": "Publish limit exceeded", "message": "Free users can only have 1 published tent. Upgrade to publish more.", "limit": 1, "current": 1 } ``` The same failure is also persisted into `GET /v1/tents/{tentId}` under the `publication` object. ## Common Errors | Status | Cause | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400 Bad Request` | Invalid request body or invalid `custom_page_alias` | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | The tent does not exist | | `409 Conflict` | The tent's mutation lease is briefly held — another publication is in progress, or a manual edit or a generation's final commit is landing at that moment. Retry shortly. A running generation does not otherwise block publishing (you publish the latest completed version) | | `429 Too Many Requests` | Publish limit exceeded | Remove a published tent from its public URL. # Retrieving Bulk Job Status Source: https://docs.tented.ai/api-reference/retrieving-bulk-job-status Poll a bulk tent creation job until all accepted items complete, fail, or finish publishing. ## Endpoint ```bash theme={null} GET /v1/bulk-jobs/{bulkJobId} ``` Use this endpoint to monitor a bulk tent creation request submitted through `POST /v1/tents/bulk`. ## Bulk Job Status Values | Status | Meaning | | ------------------------- | ---------------------------------------------------------- | | `accepted` | The bulk job was admitted and is about to start processing | | `running` | One or more accepted items are still queued or generating | | `completed` | All accepted items finished successfully | | `completed_with_failures` | Some accepted items completed and some failed | | `failed` | No accepted items completed successfully | ## Item Status Values | Status | Meaning | | ------------ | -------------------------------------------------------- | | `rejected` | The item was rejected during admission and never queued | | `queued` | The item was accepted but generation has not started yet | | `generating` | Tented is actively generating the tent | | `completed` | The tent generation finished successfully | | `failed` | Generation failed after the item was accepted | ## Request Example ```bash theme={null} curl --request GET \ --url https://api.tented.ai/v1/bulk-jobs/01JQZB4H8A7T5B3R2W1X9Y6Z0K \ --header "Authorization: Bearer $TENTED_API_KEY" ``` ## Response Examples ### In-Progress Response ```json theme={null} { "bulk_job_id": "01JQZB4H8A7T5B3R2W1X9Y6Z0K", "status": "running", "template_id": "tpl_01JABC123XYZ", "auto_publish": true, "total": 3, "queued": 1, "generating": 1, "completed": 1, "failed": 0, "items": [ { "index": 0, "client_item_id": "acme-row-001", "tent_id": "b6f8d617-7cfe-4d63-a8e9-5d971780d57f", "tent_name": "Acme Onboarding Page 1", "status": "completed", "publication_status": "published", "published_url": "https://your-workspace.tented-pages.com/acme-onboarding-jane" }, { "index": 1, "client_item_id": "acme-row-002", "tent_id": "85833f2b-40ba-4af4-b820-cf13f7356d7a", "tent_name": "Acme onboarding 2", "status": "generating", "publication_status": "pending" }, { "index": 2, "client_item_id": "acme-row-003", "tent_id": "53e8237e-0c87-495d-8d10-946db64dbf8d", "tent_name": "Acme onboarding 3", "status": "queued", "publication_status": "pending" } ], "credits": { "reserved": 3, "consumed": 1, "refunded": 0, "pending_reconciliation": 2 }, "created_at": "2026-03-25T14:30:00.000Z", "updated_at": "2026-03-25T14:31:10.000Z" } ``` ### Completed Response ```json theme={null} { "bulk_job_id": "01JQZB4H8A7T5B3R2W1X9Y6Z0K", "status": "completed", "template_id": "tpl_01JABC123XYZ", "auto_publish": true, "total": 3, "queued": 0, "generating": 0, "completed": 3, "failed": 0, "items": [ { "index": 0, "client_item_id": "acme-row-001", "tent_id": "b6f8d617-7cfe-4d63-a8e9-5d971780d57f", "tent_name": "Acme Onboarding Page 1", "status": "completed", "publication_status": "published", "published_url": "https://your-workspace.tented-pages.com/acme-onboarding-jane" }, { "index": 1, "client_item_id": "acme-row-002", "tent_id": "85833f2b-40ba-4af4-b820-cf13f7356d7a", "tent_name": "Acme onboarding 2", "status": "completed", "publication_status": "published", "published_url": "https://your-workspace.tented-pages.com/acme-onboarding-michael" }, { "index": 2, "client_item_id": "acme-row-003", "tent_id": "53e8237e-0c87-495d-8d10-946db64dbf8d", "tent_name": "Acme onboarding 3", "status": "completed", "publication_status": "published", "published_url": "https://your-workspace.tented-pages.com/acme-onboarding-sarah" } ], "credits": { "reserved": 3, "consumed": 3, "refunded": 0, "pending_reconciliation": 0 }, "created_at": "2026-03-25T14:30:00.000Z", "updated_at": "2026-03-25T14:32:10.000Z" } ``` ### Completed With Failures Response ```json theme={null} { "bulk_job_id": "01JQZB4H8A7T5B3R2W1X9Y6Z0K", "status": "completed_with_failures", "items": [ { "index": 0, "client_item_id": "acme-row-001", "tent_id": "b6f8d617-7cfe-4d63-a8e9-5d971780d57f", "tent_name": "Acme Onboarding Page 1", "status": "completed", "publication_status": "published", "published_url": "https://your-workspace.tented-pages.com/acme-onboarding-jane" }, { "index": 1, "client_item_id": "acme-row-002", "tent_id": "85833f2b-40ba-4af4-b820-cf13f7356d7a", "tent_name": "Acme onboarding 2", "status": "failed", "message": "Upstream generation failed" }, { "index": 2, "client_item_id": "acme-row-003", "status": "rejected", "error_code": "INSUFFICIENT_CREDITS", "message": "Insufficient credits for generation" } ], "credits": { "reserved": 2, "consumed": 1, "refunded": 1, "pending_reconciliation": 0 } } ``` ## Response Fields ### Top-Level Fields | Field | Type | Notes | | -------------- | --------- | ----------------------------------------------------- | | `bulk_job_id` | `string` | Bulk job identifier returned by `POST /v1/tents/bulk` | | `status` | `string` | Bulk job status | | `template_id` | `string` | Template used for this batch | | `auto_publish` | `boolean` | Whether the batch requested auto-publish | | `total` | `integer` | Total number of submitted items | | `queued` | `integer` | Accepted items waiting to start | | `generating` | `integer` | Accepted items currently generating | | `completed` | `integer` | Accepted items that completed successfully | | `failed` | `integer` | Accepted items that failed after admission | | `items` | `array` | Per-item statuses and tent references | | `credits` | `object` | Aggregate bulk job credit accounting | | `created_at` | `string` | Bulk job creation timestamp | | `updated_at` | `string` | Last aggregate update timestamp | ### Item Fields | Field | Type | Notes | | -------------------- | --------- | ---------------------------------------------------------------- | | `index` | `integer` | Zero-based index from the original request | | `client_item_id` | `string` | Your per-item identifier, when supplied | | `tent_id` | `uuid` | Present for accepted items | | `tent_name` | `string` | Tent name used for generation | | `status` | `string` | Item status | | `publication_status` | `string` | Present when publication state exists for the current generation | | `published_url` | `string` | Present when the tent has been published | | `error_code` | `string` | Present for rejected items | | `message` | `string` | Present for rejected or failed items | ## Publication Behavior If the original bulk request used `auto_publish: true`, item responses can include: * `publication_status: pending` * `publication_status: published` * `publication_status: published_with_warnings` * `publication_status: failed` `published_url` appears when a tent is successfully published. ## Common Errors | Status | Cause | | ------------------ | ------------------------------------------------------------------------- | | `400 Bad Request` | `bulkJobId` is missing or malformed | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | The bulk job does not exist in the workspace associated with your API key | Review the bulk request format and admission response. # Retrieving Org Analytics Source: https://docs.tented.ai/api-reference/retrieving-org-analytics Read aggregate analytics across all published tents in a workspace through the public API. ## Endpoint ```bash theme={null} GET /v1/analytics/org ``` ## Query Parameters | Parameter | Type | Required | Notes | | ---------- | --------- | -------- | ------------------------------------------------------------------------------------------ | | `daysBack` | `integer` | No | Number of days to query. Valid values are `1` through `7`. Invalid values fall back to `7` | ## Request Example ```bash theme={null} curl --request GET \ --url "https://api.tented.ai/v1/analytics/org?daysBack=7" \ --header "Authorization: Bearer $TENTED_API_KEY" ``` ## Response Example ```json theme={null} { "tent_id": "org", "visits": 24, "pageviews": 39, "date_range": { "start_date": "2026-04-01", "end_date": "2026-04-07", "days": 7 }, "daily_pageviews": [ { "date": "2026-04-07", "pageviews": 39 } ], "by_country": [ { "country": "Ireland", "views": 21 } ], "by_device": [ { "device": "desktop", "views": 28 } ], "by_browser": [ { "browser": "Chrome", "views": 30 } ], "by_os": [ { "os": "macOS", "views": 27 } ], "by_referrer": [ { "referrer": "google.com", "views": 19 } ], "by_path": [ { "path": "/launch-page", "views": 9 } ], "recent_pageviews": [ { "timestamp": "2026-04-07T10:00:00.000Z", "country": "Ireland", "device": "desktop", "browser": "Chrome", "os": "macOS", "referrer": "google.com" } ] } ``` ## Notes * `tent_id` is always `"org"` for this endpoint * `by_path` is only present on the organization-wide response * Invalid `daysBack` values fall back to `7` ## Common Errors | Status | Cause | | ------------------ | ------------------------------- | | `401 Unauthorized` | Missing or invalid bearer token | Review the single-tent analytics query endpoint. # Retrieving Tent Analytics Source: https://docs.tented.ai/api-reference/retrieving-tent-analytics Read detailed analytics for a single tent by ID or alias through the public API. ## Endpoint ```bash theme={null} GET /v1/analytics/tent ``` ## Query Parameters | Parameter | Type | Required | Notes | | --------------- | --------- | -------- | ------------------------------------------------------------------------------------------ | | `tentIdOrAlias` | `string` | Yes | Tent UUID, current published alias, or `/` for a root page | | `daysBack` | `integer` | No | Number of days to query. Valid values are `1` through `7`. Invalid values fall back to `7` | ## Request Example ```bash theme={null} curl --request GET \ --url "https://api.tented.ai/v1/analytics/tent?tentIdOrAlias=launch-page&daysBack=7" \ --header "Authorization: Bearer $TENTED_API_KEY" ``` ## Response Example ```json theme={null} { "tent_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "visits": 11, "pageviews": 17, "date_range": { "start_date": "2026-04-01", "end_date": "2026-04-07", "days": 7 }, "daily_pageviews": [ { "date": "2026-04-07", "pageviews": 17 } ], "by_country": [ { "country": "Ireland", "views": 17 } ], "by_device": [ { "device": "desktop", "views": 17 } ], "by_browser": [ { "browser": "Chrome", "views": 17 } ], "by_os": [ { "os": "macOS", "views": 17 } ], "by_referrer": [ { "referrer": "google.com", "views": 17 } ], "recent_pageviews": [ { "timestamp": "2026-04-07T10:00:00.000Z", "country": "Ireland", "device": "desktop", "browser": "Chrome", "os": "macOS", "referrer": "google.com" } ] } ``` ## Notes * `tentIdOrAlias` accepts a tent UUID or the current published alias * Use `/` to query a tent published at the domain root * Invalid `daysBack` values do not error; they fall back to `7` ## Common Errors | Status | Cause | | ------------------ | --------------------------------------------------- | | `400 Bad Request` | `tentIdOrAlias` was not provided | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | The tent or alias does not resolve in the workspace | Read aggregate analytics across the whole workspace. # Retrieving Tent Status Source: https://docs.tented.ai/api-reference/retrieving-tent-status Poll a tent until generation completes, fails, or finishes publishing. ## Endpoint ```bash theme={null} GET /v1/tents/{tentId} ``` Use this endpoint to poll the status of a tent created or edited through the Tented API. ## Status Values | Status | Meaning | | ------------ | --------------------------------------------------------- | | `queued` | The generation record exists but work has not started yet | | `generating` | Tented is actively generating the HTML | | `completed` | Generation finished successfully | | `failed` | Generation failed | While the tent is still processing, Tented returns: ```http theme={null} Retry-After: 10 ``` Poll no more frequently than every 10 seconds. ## Request Example ```bash theme={null} curl --request GET \ --url https://api.tented.ai/v1/tents/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1 \ --header "Authorization: Bearer $TENTED_API_KEY" ``` ## Response Examples ### In-Progress Response ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "status": "queued", "created_at": "2026-04-08T12:30:00.000Z", "updated_at": "2026-04-08T12:30:00.000Z" } ``` ### Completed Response ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "status": "completed", "created_at": "2026-04-08T12:30:00.000Z", "updated_at": "2026-04-08T12:31:02.000Z" } ``` ### Failed Response If generation fails, the response includes an `error` field: ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "status": "failed", "created_at": "2026-04-08T12:30:00.000Z", "updated_at": "2026-04-08T12:30:47.000Z", "error": "Upstream generation failed." } ``` ## Publication Object If the create or edit request used `auto_publish: true`, the status response can also include a `publication` object. | Field | Type | Notes | | ----------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------- | | `status` | `pending \| published \| published_with_warnings \| failed` | Publication lifecycle state | | `published_url` | `string` | Present when the tent was published | | `requested_custom_page_alias` | `string \| null` | The alias you asked for | | `applied_custom_page_alias` | `string \| null` | The alias that was actually applied. `null` means publish fell back or failed | | `warnings` | `array` | Returned when publish succeeded with warnings | | `error` | `object` | Returned when publish failed | ### Published Response ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "status": "completed", "created_at": "2026-04-08T12:30:00.000Z", "updated_at": "2026-04-08T12:31:02.000Z", "publication": { "status": "published", "published_url": "https://your-workspace.tented-pages.com/launch-page", "requested_custom_page_alias": "launch-page", "applied_custom_page_alias": "launch-page" } } ``` ### Published With Warning Response If the requested alias is already taken, Tented still publishes the tent at its default path and returns a warning: ```json theme={null} { "publication": { "status": "published_with_warnings", "published_url": "https://your-workspace.tented-pages.com/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "requested_custom_page_alias": "launch-page", "applied_custom_page_alias": null, "warnings": [ { "code": "CUSTOM_PAGE_ALIAS_UNAVAILABLE", "message": "Requested alias is already in use. Published using default tent URL." } ] } } ``` ### Publication Failure Response If generation succeeds but publishing fails, the generation status remains `completed` and the failure is reported under `publication`: ```json theme={null} { "publication": { "status": "failed", "applied_custom_page_alias": null, "error": { "code": "PUBLISH_LIMIT_EXCEEDED", "message": "Free users can only have 1 published tent. Upgrade to publish more." } } } ``` ### Root Page Publishing Response If you requested `/` as the alias, the status response returns `/`: ```json theme={null} { "publication": { "status": "published", "published_url": "https://your-workspace.tented-pages.com/", "requested_custom_page_alias": "/", "applied_custom_page_alias": "/" } } ``` ## Common Errors | Status | Cause | | ------------------ | --------------------------------------------------------------------- | | `400 Bad Request` | `tentId` is missing or not a valid UUID | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | The tent does not exist in the workspace associated with your API key | Review the full Tented API workflow and endpoint map. # Approve a tent template Source: https://docs.tented.ai/api-reference/tent-templates/approve-a-tent-template /api-reference/openapi.json post /v1/tent-templates/{templateId}/approve Mark the newest completed version as approved and usable to seed new tents. Requires at least one completed generation. Pass `version` as an optimistic-concurrency precondition; a mismatch with the newest completed version fails with `409 approval_version_stale`. Approval works while a generation is running — it pins the latest already-completed version. # Clone a tent template Source: https://docs.tented.ai/api-reference/tent-templates/clone-a-tent-template /api-reference/openapi.json post /v1/tent-templates/{templateId}/clone Duplicate the template — its chat, generation history, and assets — as a fresh draft. # Create a tent template Source: https://docs.tented.ai/api-reference/tent-templates/create-a-tent-template /api-reference/openapi.json post /v1/tent-templates Create a tent template from caller-supplied HTML. Seeds a completed v1 that the editor loads directly. Use `POST /v1/tent-templates/from-tent/{tentId}` to create from an existing tent, and `POST /v1/tent-templates/{templateId}/messages` to iterate with AI. # Create a tent template from a tent Source: https://docs.tented.ai/api-reference/tent-templates/create-a-tent-template-from-a-tent /api-reference/openapi.json post /v1/tent-templates/from-tent/{tentId} Create a tent template from an existing tent's latest completed render — its HTML and assets are copied into the new template. # Delete a template asset Source: https://docs.tented.ai/api-reference/tent-templates/delete-a-template-asset /api-reference/openapi.json delete /v1/tent-templates/{templateId}/assets/{assetId} Remove an asset from the tent template and delete the underlying file. # Delete a tent template Source: https://docs.tented.ai/api-reference/tent-templates/delete-a-tent-template /api-reference/openapi.json delete /v1/tent-templates/{templateId} Delete a tent template and all of its versions. Tents already seeded from the template are unaffected. # Get a template asset Source: https://docs.tented.ai/api-reference/tent-templates/get-a-template-asset /api-reference/openapi.json get /v1/tent-templates/{templateId}/assets/{assetId} Retrieve one asset's metadata and a `download_url`. # Get a tent template Source: https://docs.tented.ai/api-reference/tent-templates/get-a-tent-template /api-reference/openapi.json get /v1/tent-templates/{templateId} # Get generation HTML Source: https://docs.tented.ai/api-reference/tent-templates/get-generation-html /api-reference/openapi.json get /v1/tent-templates/{templateId}/generations/{generationId}/content Returns the HTML produced by one specific generation as `text/html`. # Get generation status Source: https://docs.tented.ai/api-reference/tent-templates/get-generation-status /api-reference/openapi.json get /v1/tent-templates/{templateId}/generations/{generationId} Poll an AI generation. `completed` generations include the version produced and a content path; `failed` ones include `error_code` and `error_message`. # Get template HTML Source: https://docs.tented.ai/api-reference/tent-templates/get-template-html /api-reference/openapi.json get /v1/tent-templates/{templateId}/content Returns the HTML of the latest completed version as `text/html` (not JSON). # Iterate with AI Source: https://docs.tented.ai/api-reference/tent-templates/iterate-with-ai /api-reference/openapi.json post /v1/tent-templates/{templateId}/messages Queue an AI iteration on the template against its current HTML. Only one generation can run per template at a time — a second request fails with `409 generation_in_progress`. Returns `202` with `status: "generating"`; poll the returned generation for completion. # List template assets Source: https://docs.tented.ai/api-reference/tent-templates/list-template-assets /api-reference/openapi.json get /v1/tent-templates/{templateId}/assets List every asset attached to a tent template. Each item includes a `download_url`. # List tent templates Source: https://docs.tented.ai/api-reference/tent-templates/list-tent-templates /api-reference/openapi.json get /v1/tent-templates List tent templates with cursor pagination. Responses contain metadata only — fetch HTML via each template's `content_path`. Follow `next_cursor` until it is absent. # Save HTML directly Source: https://docs.tented.ai/api-reference/tent-templates/save-html-directly /api-reference/openapi.json post /v1/tent-templates/{templateId}/save-code Replace the template's HTML with your own code (1 byte – 2 MB), creating a new version synchronously. Blocked while an AI generation is running. # Unapprove a tent template Source: https://docs.tented.ai/api-reference/tent-templates/unapprove-a-tent-template /api-reference/openapi.json post /v1/tent-templates/{templateId}/unapprove Return the template to draft. Never blocked — tents already seeded from the template are unaffected, but new tents can no longer be seeded from it until it is approved again. # Upload a template asset Source: https://docs.tented.ai/api-reference/tent-templates/upload-a-template-asset /api-reference/openapi.json post /v1/tent-templates/{templateId}/assets Upload one file (max 4 MB) to a tent template as `multipart/form-data` with a single `file` field. Reference the returned `asset_id` in `asset_ids` when iterating the template with AI. # Clone a tent Source: https://docs.tented.ai/api-reference/tents/clone-a-tent /api-reference/openapi.json post /v1/tents/{tentId}/clone Duplicate a tent — its latest content and assets — into a new idle tent. The clone starts unpublished; publish it separately. # Create a tent Source: https://docs.tented.ai/api-reference/tents/create-a-tent /api-reference/openapi.json post /v1/tents Create a tent and queue its first generation from a prompt, an approved template, or both. Generation is asynchronous: poll `GET /v1/tents/{tentId}` for progress. Creating from a template without a prompt copies the template synchronously and returns `201`. # Create tents in bulk Source: https://docs.tented.ai/api-reference/tents/create-tents-in-bulk /api-reference/openapi.json post /v1/tents/bulk Create up to 25 tents from one approved template in a single request. Admission supports partial success: valid items are accepted and queued, invalid ones are rejected item-by-item. Poll `GET /v1/bulk-jobs/{bulkJobId}` for progress. # Delete a tent Source: https://docs.tented.ai/api-reference/tents/delete-a-tent /api-reference/openapi.json delete /v1/tents/{identifier} Delete a tent and its published assets, alias reservations, form-submission delivery configs, and scheduled automations. The identifier may be the tent ID, its current published alias, or `__index__` for a tent published at the domain root. # Delete a tent asset Source: https://docs.tented.ai/api-reference/tents/delete-a-tent-asset /api-reference/openapi.json delete /v1/tents/{tentId}/assets/{assetId} Remove an asset from the tent and delete the underlying file. Idempotent from the caller's perspective; the stored object is cleaned up best-effort. # Edit a tent Source: https://docs.tented.ai/api-reference/tents/edit-a-tent /api-reference/openapi.json post /v1/tents/{tentId}/edit Queue a new generation for a tent that already has at least one generation. Asynchronous: poll `GET /v1/tents/{tentId}`. To create the first version, use `POST /v1/tents` instead. # Get a tent asset Source: https://docs.tented.ai/api-reference/tents/get-a-tent-asset /api-reference/openapi.json get /v1/tents/{tentId}/assets/{assetId} Retrieve one asset's metadata and a `download_url`. # Get bulk job status Source: https://docs.tented.ai/api-reference/tents/get-bulk-job-status /api-reference/openapi.json get /v1/bulk-jobs/{bulkJobId} Poll a bulk tent creation job for per-item progress, publication results, and credit accounting. # Get tent status Source: https://docs.tented.ai/api-reference/tents/get-tent-status /api-reference/openapi.json get /v1/tents/{tentId} Poll the generation status of a tent. While work is in progress the response carries `Retry-After: 10` — poll no more than every 10 seconds. When `auto_publish` was requested, the `publication` object reports the publish outcome. # List tent assets Source: https://docs.tented.ai/api-reference/tents/list-tent-assets /api-reference/openapi.json get /v1/tents/{tentId}/assets List every asset attached to a tent. Each item includes a time-limited `download_url`. # Publish a tent Source: https://docs.tented.ai/api-reference/tents/publish-a-tent /api-reference/openapi.json post /v1/tents/{tentId}/publish Publish the latest completed generation of a tent. Re-publishing an already-live tent with no changes is a no-op. # Unpublish a tent Source: https://docs.tented.ai/api-reference/tents/unpublish-a-tent /api-reference/openapi.json post /v1/tents/{tentId}/unpublish Take a published tent offline. The tent and its generation history are kept, and it can be republished at any time. # Upload an asset Source: https://docs.tented.ai/api-reference/tents/upload-an-asset /api-reference/openapi.json post /v1/tents/{tentId}/assets Upload one file (max 4 MB) to a tent as `multipart/form-data` with a single `file` field. Pass the literal tent ID `new` to create an idle tent and get back its `actual_tent_id` for use with `POST /v1/tents`. Supported types: images (PNG, JPEG, GIF, SVG, WebP, ICO, TIFF, AVIF), documents (PDF, Word, Markdown, plain text, Excel, CSV, PowerPoint), and media (MP4, WebM, QuickTime, MP3, WAV). # Activate a flow Source: https://docs.tented.ai/api-reference/triggered-flows/activate-a-flow /api-reference/openapi.json post /v1/email-flows/{flowId}/activate Validate and activate the flow. Incomplete configuration (e.g. an unapproved email on a `send_email` step) fails with `400 campaign_flow_not_ready` and an `activation_readiness` breakdown. Triggers fire on new events only — no retroactive enrollment. # Add a contact to a flow Source: https://docs.tented.ai/api-reference/triggered-flows/add-a-contact-to-a-flow /api-reference/openapi.json post /v1/email-flows/{flowId}/contacts/{contactId} Manually enroll a contact into an active flow. Optional `dynamic_context` values are available in the run as `{{campaign.dynamic.}}` tokens. Fails with `409` if the contact is already in the flow (`campaign_flow_already_in_flow`), blocked by `no_reentry` (`campaign_flow_reentry_denied`), or the flow is not active (`campaign_flow_not_active`). # Add a contact to a flow (campaign alias) Source: https://docs.tented.ai/api-reference/triggered-flows/add-a-contact-to-a-flow-campaign-alias /api-reference/openapi.json post /v1/email-campaigns/{campaignId}/contacts/{contactId} Alias for `POST /v1/email-flows/{flowId}/contacts/{contactId}` that addresses the flow by its campaign ID. Manually enrolls the contact into an active flow; this alias takes no request body, so use the email-flows route when you need `dynamic_context`. Fails with `409` if the contact is already in the flow (`campaign_flow_already_in_flow`), blocked by `no_reentry` (`campaign_flow_reentry_denied`), or the flow is not active (`campaign_flow_not_active`). # Add a step Source: https://docs.tented.ai/api-reference/triggered-flows/add-a-step /api-reference/openapi.json post /v1/email-flows/{flowId}/steps Insert a single step without rewriting the rest of the graph. Position it with at most one of `position`, `before_step_id`, or `after_step_id`. Inserting does not rewire edges — update neighboring steps’ links separately if needed. # Archive a flow Source: https://docs.tented.ai/api-reference/triggered-flows/archive-a-flow /api-reference/openapi.json post /v1/email-flows/{flowId}/archive Archive a non-active flow. Pause an active flow first. # Create a flow Source: https://docs.tented.ai/api-reference/triggered-flows/create-a-flow /api-reference/openapi.json post /v1/email-flows Create a triggered flow, optionally with its triggers and step graph inline. Flows start as drafts; activate when ready. # Delete a flow Source: https://docs.tented.ai/api-reference/triggered-flows/delete-a-flow /api-reference/openapi.json delete /v1/email-flows/{flowId} Delete a flow. Active flows must be paused first. # Delete a step Source: https://docs.tented.ai/api-reference/triggered-flows/delete-a-step /api-reference/openapi.json delete /v1/email-flows/{flowId}/steps/{stepId} Delete a step. If other steps still reference it, the call fails with `409 campaign_flow_step_referenced` unless you pass `repair_edges` with a replacement step (or `null` to detach the dangling edges). # Get a flow Source: https://docs.tented.ai/api-reference/triggered-flows/get-a-flow /api-reference/openapi.json get /v1/email-flows/{flowId} # Get a member run Source: https://docs.tented.ai/api-reference/triggered-flows/get-a-member-run /api-reference/openapi.json get /v1/email-flows/{flowId}/members/{membershipId} One member’s full run: current position, failure info, and a chronological timeline of events (entered, step executed, email sent/opened/clicked, dropped). # List flow members Source: https://docs.tented.ai/api-reference/triggered-flows/list-flow-members /api-reference/openapi.json get /v1/email-flows/{flowId}/members # List flows Source: https://docs.tented.ai/api-reference/triggered-flows/list-flows /api-reference/openapi.json get /v1/email-flows # Pause a flow Source: https://docs.tented.ai/api-reference/triggered-flows/pause-a-flow /api-reference/openapi.json post /v1/email-flows/{flowId}/pause Stop new enrollments and step execution without dropping members. Paused flows can be edited and re-activated. # Remove a contact from a flow Source: https://docs.tented.ai/api-reference/triggered-flows/remove-a-contact-from-a-flow /api-reference/openapi.json delete /v1/email-flows/{flowId}/contacts/{contactId} Drop the contact’s current run. Returns `204` when the contact was not in the flow. # Remove a contact from a flow (campaign alias) Source: https://docs.tented.ai/api-reference/triggered-flows/remove-a-contact-from-a-flow-campaign-alias /api-reference/openapi.json delete /v1/email-campaigns/{campaignId}/contacts/{contactId} Alias for `DELETE /v1/email-flows/{flowId}/contacts/{contactId}` that addresses the flow by its campaign ID. Drops the contact’s current run. Returns `204` when the contact was not in the flow. # Replace steps Source: https://docs.tented.ai/api-reference/triggered-flows/replace-steps /api-reference/openapi.json put /v1/email-flows/{flowId}/steps Replace the full step graph. Duplicate step IDs fail with `409 campaign_flow_duplicate_step_id`. # Replace triggers Source: https://docs.tented.ai/api-reference/triggered-flows/replace-triggers /api-reference/openapi.json put /v1/email-flows/{flowId}/triggers Replace the full trigger list. # Set a step’s email Source: https://docs.tented.ai/api-reference/triggered-flows/set-a-step’s-email /api-reference/openapi.json put /v1/email-flows/{flowId}/steps/{stepId}/email Attach the email for a `send_email` step — an existing email or one created inline (same body as setting a blast’s email). Inline emails start as drafts and must be approved before the flow can be activated. Fails with `400 campaign_flow_step_type_invalid` on non-email steps. # Trigger a flow for a contact Source: https://docs.tented.ai/api-reference/triggered-flows/trigger-a-flow-for-a-contact /api-reference/openapi.json post /v1/email-flows/{flowId}/trigger Fire the flow’s `tented_api` trigger for one contact, enrolling them with optional `tokens` stored as dynamic context and usable as `{{campaign.dynamic.}}` merge tokens. Identify the contact with exactly one of `contact_id`, `email`, or `phone` — `email` and `phone` are looked up against existing contacts (`404 contact_not_found` when no match). Fails with `409 flow_api_trigger_not_configured` when the flow has no `tented_api` trigger, and with `400` when tokens don’t match the trigger’s declared fields (`campaign_flow_trigger_unknown_token`, `campaign_flow_trigger_invalid_token_type`) or the resolved context exceeds 64 KB (`campaign_flow_trigger_payload_too_large`). Enrollment conflicts mirror the add-contact endpoint (`campaign_flow_already_in_flow`, `campaign_flow_reentry_denied`, `campaign_flow_not_active`). # Update a flow Source: https://docs.tented.ai/api-reference/triggered-flows/update-a-flow /api-reference/openapi.json patch /v1/email-flows/{flowId} Patch flow fields. `triggers` and `steps` are full replacements when provided. Setting `disqualification_rules` on an active flow immediately drops matching members. Structure is editable only while `draft` or `paused`. # Update a step Source: https://docs.tented.ai/api-reference/triggered-flows/update-a-step /api-reference/openapi.json patch /v1/email-flows/{flowId}/steps/{stepId} Merge a partial update into one step. Only provided fields change; `step_id` cannot be changed. # Unpublishing Tents Source: https://docs.tented.ai/api-reference/unpublishing-tents Remove a tent from its public URL through the public API. ## Endpoint ```bash theme={null} POST /v1/tents/{tentId}/unpublish ``` Use this endpoint to take a published tent offline while keeping the tent and its generation history in Tented. ## Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents/f11ef3cf-8664-4fe5-a261-c5b4d647b7d1/unpublish \ --header "Authorization: Bearer $TENTED_API_KEY" \ --header "Content-Type: application/json" \ --data '{}' ``` ## Response Example ```json theme={null} { "id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "status": "draft", "updated_at": "2026-04-08T12:50:00.000Z", "message": "Tent unpublished successfully" } ``` ## Important Behavior * Unpublishing clears the public publication projection returned by `GET /v1/tents/{tentId}` * The tent remains in Tented and can be published again later * If the tent had a custom alias, that alias reservation is removed from the live published surface ## Common Errors | Status | Cause | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400 Bad Request` | `tentId` is missing or not a valid UUID | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | The tent does not exist | | `409 Conflict` | The tent's mutation lease is briefly held — another publication is in progress, or a manual edit or a generation's final commit is landing at that moment. Retry shortly. A running generation does not otherwise block publishing (you publish the latest completed version) | Review manual publishing behavior and alias handling. # Uploading Assets Source: https://docs.tented.ai/api-reference/uploading-assets Upload, list, retrieve, and delete files on tents, emails, email templates, and tent templates. ## Endpoints ```bash theme={null} POST /v1/tents/{tentId}/assets GET /v1/tents/{tentId}/assets GET /v1/tents/{tentId}/assets/{assetId} DELETE /v1/tents/{tentId}/assets/{assetId} ``` Use this endpoint when your prompt should reference uploaded files such as: * Logos and product images * PDFs and Word documents * CSV or spreadsheet files * Slide decks * Video and audio files ## Two Supported Patterns ### Upload to a new tent Use `new` as the path parameter: ```bash theme={null} POST /v1/tents/new/assets ``` Tented creates an idle tent and returns: * `actual_tent_id` * `asset_id` Use those values in your later `POST /v1/tents` request. ### Upload to an existing tent If you already have a `tentId`, attach more files to the same tent: ```bash theme={null} POST /v1/tents/{existingTentId}/assets ``` The upload endpoint only checks that the tent exists in your workspace. If you plan to use that `tent_id` with `POST /v1/tents`, the tent must still have no generation yet. ## Request Format Send `multipart/form-data` with a single field named `file`. ## Request Example ```bash theme={null} curl --request POST \ --url https://api.tented.ai/v1/tents/new/assets \ --header "Authorization: Bearer $TENTED_API_KEY" \ --form "file=@./brand-brief.pdf" ``` ## Response Example `201 Created` ```json theme={null} { "requested_tent_id": "new", "actual_tent_id": "f11ef3cf-8664-4fe5-a261-c5b4d647b7d1", "asset_id": "01JPAEWP9PY5SCX1V6X03XK9M2", "filename": "brand-brief.pdf", "content_type": "application/pdf", "file_size": 248392, "uploaded_at": "2026-03-13T15:35:00.000Z" } ``` ## File Limits * Maximum file size: `4 MB` per upload * Maximum referenced assets on create: `5` * One file per request ## Supported Content Types ### Images * `image/png` * `image/jpeg` * `image/gif` * `image/svg+xml` * `image/webp` * `image/x-icon` * `image/tiff` * `image/avif` ### Documents * `application/pdf` * `application/msword` * `application/vnd.openxmlformats-officedocument.wordprocessingml.document` * `text/markdown` * `text/plain` * `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` * `application/vnd.ms-excel` * `text/csv` * `application/vnd.ms-powerpoint` * `application/vnd.openxmlformats-officedocument.presentationml.presentation` ### Media * `video/mp4` * `video/webm` * `video/quicktime` * `audio/mpeg` * `audio/wav` ## List, Retrieve, and Delete Assets ```bash theme={null} GET /v1/tents/{tentId}/assets GET /v1/tents/{tentId}/assets/{assetId} DELETE /v1/tents/{tentId}/assets/{assetId} ``` `GET /v1/tents/{tentId}/assets` returns every asset on the tent as `{"assets": [...]}`. `GET .../{assetId}` returns a single asset. Each asset carries a `download_url`: ```json theme={null} { "asset_id": "01JPAEWP9PY5SCX1V6X03XK9M2", "filename": "brand-brief.pdf", "content_type": "application/pdf", "file_size": 248392, "uploaded_at": "2026-03-13T15:35:00.000Z", "uploaded_by": "tented-api", "download_url": "https://assets.tented.ai/tent-assets/..." } ``` `DELETE .../{assetId}` removes the asset and deletes the underlying file, returning `{"asset_id": "...", "deleted": true}`. Deleting an asset that a published tent references does not alter the already-rendered page. ## Assets on Emails, Templates, and Tent Templates The same four operations work on emails, email templates, and tent templates — the only difference is the parent segment of the path: ```bash theme={null} POST /v1/emails/{emailId}/assets GET /v1/emails/{emailId}/assets GET /v1/emails/{emailId}/assets/{assetId} DELETE /v1/emails/{emailId}/assets/{assetId} POST /v1/email-templates/{templateId}/assets GET /v1/email-templates/{templateId}/assets GET /v1/email-templates/{templateId}/assets/{assetId} DELETE /v1/email-templates/{templateId}/assets/{assetId} POST /v1/tent-templates/{templateId}/assets GET /v1/tent-templates/{templateId}/assets GET /v1/tent-templates/{templateId}/assets/{assetId} DELETE /v1/tent-templates/{templateId}/assets/{assetId} ``` Upload is `multipart/form-data` with a single `file` field (max 4 MB), exactly like tent uploads, and returns the asset object shown above (with `download_url`). Reference the returned `asset_id` in the `asset_ids` array when iterating the parent with AI — see [iterating an email](/api-reference/editing-emails#iterate-with-ai), [an email template](/api-reference/managing-email-templates#iterate-with-ai), or [a tent template](/api-reference/managing-tent-templates#iterate-with-ai). Unlike tent uploads, there is no `new` shortcut for these parents — the email, email template, or tent template must already exist. Uploading to one that does not exist in your workspace returns `404 Not Found`. ## Common Errors | Status | Cause | | ------------------ | --------------------------------------------------------------------- | | `400 Bad Request` | `Content-Type` is not `multipart/form-data` | | `400 Bad Request` | Request body is missing | | `400 Bad Request` | No `file` field was provided | | `400 Bad Request` | File is larger than `4 MB` | | `400 Bad Request` | Unsupported MIME type | | `400 Bad Request` | `tentId` is not `new` and is not a valid UUID | | `401 Unauthorized` | Missing or invalid bearer token | | `404 Not Found` | You referenced an existing tent that does not exist in your workspace | ## Next Step After uploading, call `POST /v1/tents` and pass: * `tent_id`: the `actual_tent_id` * `asset_ids`: an array of the returned `asset_id` values Use your uploaded asset IDs in the tent creation request. # Usage Logs Source: https://docs.tented.ai/api-reference/usage-logs Every API request is logged in the web app — inspect recent calls, response statuses, and credits consumed under Settings > API. ## Where to find your logs Every request made with an API key is recorded automatically. In the web app, open **Settings → API** and switch to the **Usage** tab to see your recent API activity. Each log entry shows: | Column | Meaning | | ------------------- | --------------------------------------------------------------------------------- | | **Timestamp** | When the request was received, shown in your local timezone | | **Key Name** | Which API key made the call — useful when multiple integrations share a workspace | | **API Request URL** | The endpoint path that was called | | **Credits** | Generation credits consumed by the request, where applicable (see below) | | **Method** | HTTP method (`GET`, `POST`, `PATCH`, `DELETE`) | | **Status** | Response status code, color-coded — hover for details on non-2xx responses | Filter by key and date range to debug a specific integration or trace a spike in traffic. ## When credits apply Most API calls consume no generation credits — reads, contact writes, list management, imports, and email sends are all credit-free. Credits are consumed by **AI generation** requests: * `POST /v1/tents` — creating a tent from a prompt or template * `POST /v1/tents/{tentId}/edit` — AI edits to an existing tent * `POST /v1/tents/bulk` — bulk tent jobs (credits reconcile once the job finishes, so a just-accepted job may briefly show no credit count) All other rows show `-` in the Credits column. Your plan's remaining credit balance is visible on the same **Settings → API** page. ## Debugging tips * A `401` with a key you believe is valid usually means the key was revoked or regenerated — check the key list on the same page. * `429` responses mean you've hit the API rate limit; back off and retry with jitter. * For asynchronous work (tent generation, bulk jobs, imports), the logged request is the *submission* — poll the corresponding status endpoint to follow the job itself. How to create, rotate, and revoke API keys, and monitor usage as a workspace admin. # AI Credits & the Add-on Source: https://docs.tented.ai/billing-subscriptions/ai-credits How AI credits work, what they cost, and how to buy more ## What credits pay for **AI credits** power generation — everything else in Tented is free to use. Publishing, sending, importing contacts, and collecting form submissions never consume a credit. | Action | Credits | | ------------------------------------------------------------ | -------------------------------------------- | | Generating a new tent or email | **1** | | Iterating on an existing tent or email | **0.5** | | [AI image generation](/working-with-tents/generating-images) | **0.5** per image | | Chatting without changing the content | **0.5** on Free — **included** on paid plans | ## Your monthly allotment * **Free** — 5 credits per day (the daily pool resets at 12am UTC), up to 40 per rolling month. * **Pro and Max** — a flat **100 credits per month**, on every tier. Plan tiers change your email volume and contact allowance, not your credits — and plan credits reset at the start of each billing period. The **Credits** meter at the bottom of the sidebar always shows what's left; hover it for the period breakdown. See [Viewing Current Usage](/billing-subscriptions/viewing-usage) for the full usage picture. ## Need more? The AI Credits Add-on Paid plans can bolt extra credits onto the subscription in **100-credit blocks** — **$25/month** per block on monthly plans, **$270/year** on annual. Click **Plans & Billing** in the sidebar to open **Manage Subscription**; the add-on lives at the bottom of the dialog. A screenshot of the Manage Subscription dialog showing the Free, Pro, and Max plan cards with the AI Credits Add-on panel at the bottom. Open the dropdown and pick your **total** extra credits — the price for each option is shown right in the list, and your current selection is marked: A screenshot of the AI Credits Add-on dropdown open, listing options from No extra credits to 1000 extra credits with monthly prices. Pick a higher amount and the button turns into **Add credits**, with the exact billing consequence spelled out underneath — you're charged a prorated amount today, and renewals bill the new total: A screenshot of the AI Credits Add-on panel with 300 extra credits selected, an Add credits button, and a note reading Prorated charge today, renewals bill 75 dollars per month. The extra credits are available immediately, and the sidebar meter grows to match. The add-on tops out at **5,000 extra credits** (50 blocks). The dropdown always offers headroom above what you currently hold, so you can step up as you grow. ## Adjusting or removing the add-on The same dropdown scales the add-on **down** as well: * **Decrease** — pick a lower amount and click **Decrease credits**. You keep everything you've already paid for until the end of the current billing period; renewals bill the smaller amount from then on. * **Remove** — pick **No extra credits** and click **Remove add-on**. Same grace: paid credits stay usable through the period, and the add-on simply doesn't renew. * **Changed your mind?** Stepping back up within credits you've already paid for is free — the button reads **Restore credits** and there's no new charge. ## When you run out mid-flight Hitting your credit limit doesn't block you silently — Tented tells you at the moment of generation: * **Free plans** see a *Daily Limit Reached* or *Monthly Free Limit Reached* dialog with the option to upgrade. * **Paid plans** see an *AI Credit Limit Reached* dialog showing when your plan credits reset — with the same add-on purchase panel right there, so you can top up and keep working without leaving the flow. Track credits, sends, and generations across your workspace. # Managing Your Subscription Source: https://docs.tented.ai/billing-subscriptions/managing-subscription Change plans, view invoices, and manage billing details ## Where Billing Lives Click **Plans & Billing** in the bottom-left of the sidebar (or **Workspace Settings > Team & Billing > Plans & Billing**) to open the **Manage Subscription** dialog. A screenshot of the Manage Subscription dialog with the current plan banner and plan cards. At the top you'll see your current plan and renewal date — for example, *"You're currently on Max 1 Monthly (renews 11/20)"*. ## Common Tasks * **Change plans** — pick a different plan card. Upgrades take effect immediately. * **Switch to annual billing** — flip the **Annual** toggle on the Pro or Max card. * **Adjust your volume tier** — on Pro and Max, use the **emails / mo** dropdown on the card to right-size your email volume and contact allowance. * **Add AI credits** — every paid tier includes 100 monthly AI credits; buy more in 100-credit blocks with the [AI Credits Add-on](/billing-subscriptions/ai-credits) at the bottom of the dialog. * **Manage payment details and invoices** — click the **Click here** link in the banner to open the billing portal, where you can update your payment method, view past invoices, and cancel or renew your subscription. Billing changes are workspace-wide and require an [Admin role](/configuring-tented/user-permissions). ## Keeping an Eye on Usage Your remaining credits are always visible in the **Credits** meter at the bottom of the sidebar — hover it to see your current billing period. For the full picture, see [Viewing Current Usage](/billing-subscriptions/viewing-usage). Track credits, sends, and generations across your workspace. # Upgrading Your Plan Source: https://docs.tented.ai/billing-subscriptions/upgrading-plan Compare Tented plans and upgrade when you're ready ## Plans Overview Tented has three plans. Every plan includes **unlimited users, emails, and tent creation** — the differences are in email volume, contact allowance, publishing limits, and power features. Open the plan picker anytime: click **Plans & Billing** in the bottom-left of the sidebar (or via **Workspace Settings > Team & Billing**). A screenshot of the Manage Subscription dialog showing the Free, Pro, and Max plans with their features and the AI Credits Add-on panel. * 100 emails / day, 1,000 contacts * 5 daily credits (up to 40/month) * 1 published tent * 100 monthly form submissions * Full API access * Pick your volume: 5k–100k emails/mo with a matching contact allowance * **100 AI credits / month** * 250 published tents * Unlimited form submissions * Remove "Made with Tented" branding * Password-protected tents * Branded CNAME domains * Everything in Pro * Pick your volume: 20k–5M emails/mo * **100 AI credits / month** * **Unlimited** published tents * Dedicated IP (add-on) **What's a credit?** Credits power AI generation — creating and iterating on tents and emails. Every paid tier includes the same **100 credits per month**; if you need more, add them in 100-credit blocks with the [AI Credits Add-on](/billing-subscriptions/ai-credits). ## How to Upgrade 1. Click **Plans & Billing** in the sidebar. 2. Pick your plan — and flip the **Annual** toggle if you prefer annual billing. 3. On Pro and Max, choose your **email volume** tier from the dropdown on the card — the contact allowance scales with it. 4. Complete checkout. Your new limits and features are available immediately. ## Which Plan Is Right? * **Free** — trying Tented out, or running a single evergreen page. * **Pro** — marketing teams shipping campaigns: multiple live pages, unlimited form capture, brand control, and custom domains. * **Max** — high-volume teams: unlimited published tents, heavy API usage, and priority support. Change plans, view invoices, and manage billing details. # Viewing Current Usage Source: https://docs.tented.ai/billing-subscriptions/viewing-usage Track credits, email sends, and AI generations across your workspace ## Three Places to Check Usage ### 1. The sidebar credits meter The **Credits** bar at the bottom-left of every page shows your remaining AI credits at a glance. Hover it for the details — plan credits left and your current billing period. ### 2. Workspace Settings The **Usage** card in [Workspace Settings](/configuring-tented/workspace-settings) shows your headline numbers against plan limits: * **Published Tents** — live tents vs. your plan's limit * **Daily Free Credits Used** — today's free credits * **Plan Credits Used** — paid credits used this billing period ### 3. The Analytics area For trends over time, click **Analytics** in the sidebar. It's your workspace-wide dashboard: A screenshot of the Analytics area showing Published Tents, Total Page Views, an Emails Sent chart, and an AI Generations chart with a Credit Usage tab. * **Published Tents** and **Total Page Views** (last 7 days) * **Emails Sent** — total emails across [blasts](/email/email-blasts) and [flows](/email/triggered-flows) by month * **AI Generations / Credit Usage** — initial, iterative, and image generations across tents and emails by week, with a tab to see the same data as credits * **Page Views by Day** — traffic across all your tents Each chart has an export button, so you can pull the data into your own reporting. ## What Uses Credits? AI generation: creating and iterating on tents, emails, and templates, plus [AI image generation](/working-with-tents/generating-images) (0.5 credits per image). Viewing, publishing, sending, and collecting form submissions don't consume AI credits (form submissions have their own monthly limit on the Free plan). Consistently running out of credits? Add more in 100-credit blocks with the [AI Credits Add-on](/billing-subscriptions/ai-credits) — or see [Upgrading Your Plan](/billing-subscriptions/upgrading-plan) if you're still on Free. # Workspace Analytics Source: https://docs.tented.ai/configuring-tented/analytics Track tent traffic, email sends, and AI usage across your whole workspace ## The Analytics area Click **Analytics** in the sidebar for a workspace-wide dashboard covering three things: how your published tents are performing, how much email you're sending, and how you're using AI credits. This is the *workspace* view. For a single tent's numbers, use **View Tent Analytics** on its [Tent Details](/working-with-tents/viewing-tent-details) page; for a specific campaign, open the [blast](/email/email-blasts) or [flow](/email/triggered-flows). ## Traffic At the top you'll find **Published Tents** and **Total Page Views** (last 7 days), plus a **Page Views by Day** chart. Below that, traffic is broken down every way you'd want: A screenshot of the analytics breakdowns: Views by URL, Country, Device Type, Browser, Operating System, and Referrer. * **Views by URL** — which of your pages get the traffic * **Views by Country**, **Device Type**, **Browser**, and **Operating System** — who's visiting and on what * **Views by Referrer** — where they came from (Direct, a social network, a search engine, etc.) ## Email * **Emails Sent** — total emails across [blasts](/email/email-blasts) and [flows](/email/triggered-flows), by month, so you can see your sending volume trend. ## AI usage * **AI Generations** — initial, iterative, and image generations across tents and emails, by week. * **Credit Usage** — the same activity expressed as credits (plan credits vs. free daily credits), by month. This is the best place to understand where your [credits](/billing-subscriptions/viewing-usage) go. ## Exporting Every chart has a share icon in its corner — click it to download that chart as a PNG for a report or deck. Check credits, sends, and limits against your plan. # API Keys & Usage Source: https://docs.tented.ai/configuring-tented/api-keys Create and manage API keys, and monitor your API usage ## About the API Tented has a public API for creating and managing tents and contacts programmatically — handy for bulk-generating personalized pages or syncing your CRM. Everything starts with an **API key**. Manage keys and watch usage under **Settings > API**. API access is a paid-plan feature: Pro includes 100 API credits/month and Max includes 1,000. See [Upgrading Your Plan](/billing-subscriptions/upgrading-plan). ## Creating an API key On the **Credit and Key** tab, click **Create API Key**, give it a name (like "Production" or "Zapier"), and confirm. The key currently has **All Access** scope. Tented shows you the key **once**: A screenshot of the Create API Key dialog showing a newly generated key and a warning that it won't be shown again. Copy your key and store it somewhere safe **right away** — for security, Tented will never show the full value again. If you lose it, just delete the key and create a new one. Use the key as a bearer token in your API requests. See the [API Reference](/api-reference/authentication) for details. ## Managing keys Back on the **Credit and Key** tab, each key shows its name, status, a masked prefix, scope, last-used time, and creation date. A screenshot of the API Keys list showing a key with its masked value, status, scope, and dates. * **Rename** a key with the pencil icon. * **Delete** a key with the trash icon — this can't be undone, and any requests using it will immediately start failing. Deleting is how you revoke a key you think may be exposed. ## Monitoring usage The **Usage** tab logs every API request. Filter by key and date range to see method, status code, response time, and credits consumed per request — useful for debugging an integration or tracking what's eating your API credits. A screenshot of the API Usage tab with filters for key and date range. Authentication, creating tents, managing contacts, and more. # Custom Domains Source: https://docs.tented.ai/configuring-tented/custom-domains Serve your published tents from your own branded domain ## Custom Domains Overview By default, published tents live on your workspace's subdomain (`your-slug.tented-pages.com`). On paid plans you can serve them from a **branded custom domain** instead — like `pages.yourcompany.com` — using a CNAME alias, while Tented keeps handling hosting, SSL, and the CDN. Looking for your **email** sending domain? That's a separate setup — see [Setting Up Email](/email/email-setup). ## How It Works 1. You choose a subdomain on a domain you own (for example, `pages.yourcompany.com`). 2. You add a **CNAME record** at your DNS provider pointing that subdomain to Tented. 3. Tented links the alias to your workspace and provisions SSL. 4. Your published tents become available at your branded URLs. ## Setting It Up Custom domain setup is currently handled with our team's help: 1. Go to **Workspace Settings** and find **Branded Domain** under **Workspace Details**. 2. Click **Contact support** (or email [support@tented.ai](mailto:support@tented.ai)) with the domain you'd like to use. 3. We'll send you the exact CNAME record to add at your DNS provider and confirm once it's verified. Once configured, your branded domain appears in the **Branded Domain** field, and newly published tents use it automatically. ## Nice URLs Without a Custom Domain Even without a custom domain, you can make URLs shareable with **custom slugs** — set one in the [publish dialog](/working-with-tents/publishing-tents) or via **Tent Actions > Customize URL**: ``` https://your-slug.tented-pages.com/launch-webinar ``` ## Troubleshooting * **DNS not verifying?** CNAME changes can take a while to propagate (up to 48 hours with some providers). Also make sure the record isn't being proxied by your DNS provider. * **Old URLs still shared?** Your `tented-pages.com` URLs continue to work — nothing breaks when you add a branded domain. Branded domains are a paid-plan feature — see what each plan includes. # Inviting New Users Source: https://docs.tented.ai/configuring-tented/inviting-new-users Bring teammates into your Tented workspace ## Inviting Users Overview Tented workspaces are built for teams — invite as many teammates as you need (every plan includes unlimited users). Admins handle invitations from **Team Management**. ## Sending an Invitation 1. Click your **profile icon** in the bottom-left corner and select **Workspace Settings**. 2. In the **Team & Billing** card, click **Manage Users** to open **Team Management**. 3. Click **Invite New User**. A screenshot of the Team Management page with the All Users table and the Invite New User button. 4. Fill in the invitation: * **Full name** — how they'll appear in the workspace * **Email address** — where the invitation is sent * **Role** — **Standard User** or **Admin** (see [User Permissions](/configuring-tented/user-permissions)) 5. Click **Invite**. A screenshot of the Invite New User dialog with full name, email address, and role fields. Your teammate receives an email with a **magic link** — no password setup required. One click and they're in. ## After the Invite New users appear in the **All Users** table with their role and status. From there you can [manage them](/configuring-tented/managing-users) — change roles or update details with the **Edit** action. Not sure which role to pick? Default to **Standard User** — you can always promote someone to **Admin** later. Learn how to manage existing users in your workspace. # Managing Users Source: https://docs.tented.ai/configuring-tented/managing-users Manage your team from the Team Management page ## Team Management Overview Everything about your team lives on the **Team Management** page: click your **profile icon > Workspace Settings**, then **Manage Users** in the **Team & Billing** card. A screenshot of the Team Management page showing the All Users table with name, email, role, status, created date, and an Edit action. ## The All Users Table For each member you'll see: * **Full Name** and **Email Address** * **Role** — **Admin** or **Standard User** (see [User Permissions](/configuring-tented/user-permissions)) * **Status** — whether they're active in the workspace * **Created Date** — when they joined * **Actions** — click **Edit** to update their details or change their role ## Common Tasks * **Add someone** — click **Invite New User** (full guide: [Inviting New Users](/configuring-tented/inviting-new-users)) * **Change a role** — click **Edit** on their row and pick the new role * **Check your seat count** — the total is shown under the table; every Tented plan includes unlimited users, so invite freely Only **Admins** can access Team Management, send invitations, and change roles. Understand what Admins and Standard Users can each do. # Switching Workspaces Source: https://docs.tented.ai/configuring-tented/switching-workspaces Move between the Tented workspaces you belong to without signing out ## Workspaces Overview Your Tented account (your email address) can belong to more than one workspace — for example, your own company's workspace plus a client workspace you've been [invited to](/configuring-tented/inviting-new-users). Each workspace keeps its own tents, contacts, emails, branding, team, and plan. ## Choosing a Workspace at Login If your email belongs to multiple workspaces, Tented asks which one you want to use right after you log in. Pick a workspace from the list and you'll land in it. A screenshot of the Select an organization page at login, listing three workspaces to choose from. ## Switching While Logged In If you belong to more than one workspace, you don't need to sign out to move between them: Click your **profile icon** in the bottom-left corner of the sidebar, and select **Switch Workspace** from the menu (just above **Sign out**): A screenshot of the profile menu open in the sidebar, showing User Settings, Workspace Settings, Integrations, API, Switch Workspace, and Sign out. Then pick the workspace you want from the list. Your current workspace is marked **Current** and can't be re-selected: A screenshot of the Switch workspace dialog listing three workspaces, with the current one marked Current and the others offering an arrow to switch. Tented switches your session to the selected workspace and reloads the app, so everything you see — tents, people, emails, analytics, and settings — belongs to that workspace. **Switch Workspace** only appears when your email address belongs to more than one workspace, and only those workspaces appear in the list. If a workspace is missing, ask an admin of that workspace to [invite you](/configuring-tented/inviting-new-users); once you're invited it shows up automatically. ## Roles Are Per Workspace Your [role](/configuring-tented/user-permissions) is set independently in each workspace — you can be an Admin in one and a Standard User in another. After switching, you have the role that workspace assigned to you. Configure workspace-wide branding, company info, and more. # User Permissions Source: https://docs.tented.ai/configuring-tented/user-permissions What Admins and Standard Users can do in a workspace ## Roles in Tented Tented keeps permissions simple with two roles, assigned when you [invite a user](/configuring-tented/inviting-new-users) and changeable anytime from [Team Management](/configuring-tented/managing-users): Full control of the workspace: everything a Standard User can do, plus team management (inviting users, changing roles), workspace settings, billing, and integrations. The day-to-day creator role: build and edit tents and emails, manage contacts and lists, run campaigns, and view analytics. ## What Each Role Can Do | Capability | Standard User | Admin | | -------------------------------------------- | ------------- | ----- | | Create and edit tents & emails | ✅ | ✅ | | Publish tents, send campaigns | ✅ | ✅ | | Manage People, lists, and form data | ✅ | ✅ | | View analytics | ✅ | ✅ | | Invite users and change roles | ❌ | ✅ | | Workspace settings (brand, company info) | ❌ | ✅ | | Email setup (sending domain, sender profile) | ❌ | ✅ | | Plans, billing, and subscription | ❌ | ✅ | | Connect integrations (Slack, Teams) | ❌ | ✅ | Your own role is shown on your [User Settings](/configuring-tented/user-settings) page. If an option in the docs seems missing from your account, check your role first — it's usually an Admin-only setting. Configure your personal profile and notification preferences. # User Settings Source: https://docs.tented.ai/configuring-tented/user-settings Configure your personal profile and notification preferences ## User Settings Overview User Settings is your personal corner of Tented — separate from workspace-wide settings. Open it by clicking your **profile icon** in the bottom-left corner and selecting **User Settings**. A screenshot of the User Settings page with profile photo, full name, email, created date, role badge, and a Notifications panel. ## Profile * **Profile photo** — click **Choose New Photo** to upload one. * **Full Name** — the name associated with your account, shown across the workspace. * **Email** — the address you signed up with. This can't be changed. * **User Created Date** — when your account was created. * **User Role** — your current role badge ([Admin or Standard User](/configuring-tented/user-permissions)). ## Notifications The **Notifications** panel controls your personal preferences: * **Marketing Email** — toggle whether you receive product updates, best practices, and event announcements from Tented. Looking for form-submission notifications? Those are configured per tent as [form automations](/working-with-tents/form-automations), not here. Configure workspace-wide branding, company info, and more. # Workspace Settings Source: https://docs.tented.ai/configuring-tented/workspace-settings Configure your workspace settings and team preferences ## Workspace Settings Overview Workspace settings control your company information, branding, team, integrations, and Tented plan — everything shared by your whole workspace. Click your **profile icon** in the bottom-left corner, then select **Workspace Settings** from the menu that opens. A screenshot of the Workspace Settings menu item. A screenshot of the Workspace Settings page showing Workspace Details, Company Information, Team & Billing, and Usage sections. ## Workspace Details * **Workspace Name**: The internal name of your workspace, used in system notifications like user invites. * **Workspace Slug**: A unique identifier used as the prefix for your published tents (e.g., `t8rvby15uq.tented-pages.com/...`). Contact support to request a change. * **Branded Domain**: The CNAME alias from your custom domain for published tents. See [Custom Domains](/configuring-tented/custom-domains). ## Company Information Give Tented context for on-brand AI generation: * **Company Name** and **Website** * **Company Description**: A brief description of what your company does. ## Brand * **Primary Brand Color**: Your main brand color (used for buttons and accents). * **Brand Logo**: PNG, JPG, or SVG with a 3:1 aspect ratio and transparent background (max 2MB). * **Brand Icon**: PNG, SVG, or ICO with a 1:1 (square) aspect ratio (max 1MB). * **Brand Guidelines**: Free-text guidance the AI follows when generating content — fonts to prefer, tone, things to avoid. A screenshot of the Brand section on the Workspace Settings page. ## People A quick view of your contact database, with **People database size** and **Custom fields used**. Click **Manage Fields** to add and manage [standard and custom contact fields](/people/contact-fields). ## Email Shows your email sending status (with a **Verified** badge once your domain checks out). Click **Manage Email** to configure your sending domain, sender profile, default headers, and unsubscribe footer — the full walkthrough is in [Setting Up Email](/email/email-setup). ## Team & Billing * **Active Users** and **Manage Users** — open [Team Management](/configuring-tented/managing-users) to invite and manage teammates. * **Current Plan** and **Plans & Billing** — see [Managing Your Subscription](/billing-subscriptions/managing-subscription). ## Usage * **Published Tents**: How many tents you've published against your plan's limit. * **Daily Free Credits Used**: Free AI generation credits used today. * **Plan Credits Used**: Paid AI credits used this billing period. For deeper usage analytics, see [Viewing Current Usage](/billing-subscriptions/viewing-usage). ## API Click **Manage API** to create API keys and monitor credits for REST API and agent access. See [API Keys & Usage](/configuring-tented/api-keys) to manage keys, and the [API Reference](/api-reference/introduction) for endpoints. Serve your published tents from your own branded domain. # A/B Testing Source: https://docs.tented.ai/email/ab-testing Test up to five variations of a blast, then send the winner automatically ## What's an A/B test? An **A/B test** takes the guesswork out of a blast: instead of betting the whole audience on one subject line (or send time, or sender), Tented sends **up to five variations** to a small sample, measures which one performs best, and delivers the winning version to everyone else — automatically, or on your say-so. Every A/B test lives inside an [email blast](/email/email-blasts). Open a draft blast, head to the **Schedule** step, and find **AB Test** under **Additional Settings**. Click **Add Test**. A screenshot of the blast Schedule step showing the Additional Settings card with the Set as operational toggle and the AB Test section with an Add Test button. Once a test is active, the blast's schedule follows the **test's** start time, and **Send now** is unavailable — a test needs time to collect results before the winner can go out. ## Choose what to test The **What should we test?** dropdown offers five test types: A screenshot of the What should we test dropdown open, listing Subject line, Whole email, From address, Day of week, and Time of day. | Test type | Each variation is… | Good for | | ---------------- | ------------------------------------------------- | ------------------------------------ | | **Subject line** | A different subject on the same email | The classic — biggest lever on opens | | **Whole email** | A different **approved** email entirely | Layout, copy, or offer showdowns | | **From address** | A different from name, from address, and reply-to | "Founder vs. brand" sender tests | | **Day of week** | The same local time on a different weekday | Finding your audience's day | | **Time of day** | A different local time | Morning vs. evening sends | Changing the test type starts a fresh set of variations — your existing variation setup is discarded. ## Build the variations **Variation A** is always the **control**: your campaign's current setup. Add up to four more with **Add variation** (a test needs at least two). A screenshot of the Variations section with Variation A marked Control and prefilled with the campaign subject, an empty Variation B, an Add variation button, and a Suggest variations button. ### Let AI write the alternatives For **Subject line** and **From address** tests, click **Suggest variations** and Tented generates alternatives alongside your control — each with a one-line rationale explaining what it's testing, so the test is a real experiment rather than five shots in the dark. Keep them, edit them, or **Regenerate variations** for a new set. A screenshot of AI-suggested variations, each with an AI suggested badge and a rationale line beneath the variation name. ### Whole-email variations For a **Whole email** test, Variation A sends the approved campaign email. For each other variation, pick another **approved** email — or click **Clone original email** to duplicate the control and edit the copy in the email editor. Clones must be approved before the test can be saved. A screenshot of a Whole email test where Variation A sends the approved campaign email with an Edit control email link, and Variation B has a Variation email picker. ### Timing variations * **Day of week** — set one **Send time for every variation**, then give each variation its own weekday. * **Time of day** — give each variation its own local time, interpreted as wall-clock time in the test's timezone. A **Winner delivery weekdays** setting controls which days the winning time may be delivered on — after winner selection, delivery waits for the winning local time on an eligible weekday. A screenshot of a Time of day test showing per-variation local time inputs and the Winner delivery weekdays radio options. ## Split the audience **Audience allocation** decides how much of the audience is the experiment and how much gets the winner: * **Test sample** (default **20%**) — divided **evenly** across your variations. * **Winner audience** (default **80%**) — held back until a winner is chosen, then receives the winning version. The two always total 100% (editing one rebalances the other), and both must be whole percentages between 1 and 99. A screenshot of the Audience allocation section with Test sample at 20 percent and Winner audience at 80 percent, above the Winner selection radios. The audience needs at least **one more qualified contact than you have variations** — every variation gets at least one sample recipient, and at least one contact is always held back for the winner send. ## Pick the winner (or let Tented) **Winning metric** is how variations are ranked: | Metric | Measured as | | ----------------- | ---------------------------- | | **Opens** | Unique opens ÷ delivered | | **Clicks** | Unique clicks ÷ delivered | | **Click-to-open** | Unique clicks ÷ unique opens | **Winner selection** has two modes: * **Choose automatically** — at the **Winner decision time**, Tented ranks the variations by your metric and sends the best performer to the winner audience. On a perfect tie, the variation with more data wins, and the control wins over a challenger that merely matched it. * **Declare manually** — you pick the winner yourself from the results view once the sample has collected. Tented emails you reminders after 6 and 24 hours, and a final reminder after 48 hours. The winner audience waits until you declare — nothing is sent without your pick. ## Set the timing Two datetime fields close out the setup: * **Test start time** — when the sample sends go out. This *is* the blast's send time. It can stay blank while you draft, but scheduling requires a start at least **2 minutes** in the future. * **Winner decision time** (automatic selection only) — when Tented evaluates results and sends the winner. It must land **after every variation has sent**. For subject-line, whole-email, and from-address tests the winner goes out right at decision time; day-of-week and time-of-day winners wait for the next eligible occurrence of the winning slot. A screenshot of the bottom of the AB Test configurator with Winner selection radios, Test start time and Winner decision time pickers, and the Save AB test button. ## Save it, then schedule it Click **Save AB test** — the badge in the AB Test header flips from **Unsaved changes** to **Saved**. Then launch from the top of the Schedule step, where the usual Schedule send button now reads **Schedule test**: it approves the blast and schedules the test to begin at your start time. A screenshot of the Schedule step with a saved AB test, showing the Schedule the AB test card with a Schedule test button and the AB Test section marked Saved. Back on the **Campaigns** tab, blasts with a test carry a violet **AB TEST** badge next to their status: A screenshot of the Campaigns list where the draft blast Terra Collection Launch has a violet AB TEST badge beside its Draft status. ## Watch the results roll in Once scheduled, the campaign page gains an **AB Test** card that tracks the whole lifecycle — it refreshes itself, so you can leave it open: A screenshot of a completed AB test overview showing test timing tiles, the audience split, winning metric, selection mode, and the selected winner Variation C. | Status | What's happening | | ---------------------- | --------------------------------------------------- | | **Scheduled** | Waiting for the test start time | | **Sending test** | Sample sends are going out | | **Collecting results** | Samples landed; opens and clicks are accumulating | | **Winner scheduled** | A winner is chosen and its send is queued | | **Sending winner** | The winning version is going to the winner audience | | **Completed** | Done — the winner reached everyone else | The results table shows every variation with its recipients, opens, clicks, and your winning metric. The winner is tinted green and wears the trophy: A screenshot of the AB test results table with five subject line variations, where Variation C is highlighted with a Winner badge at a 53 percent open rate. For a **Declare manually** test, each row gains a **Declare winner** button once results are collecting. Declaring sends that variation to the winner audience — for content tests you also pick the **Send winner at** time. The choice can't be changed after winner selection. Second thoughts mid-test? **Cancel winner send** is available while the test is sending, collecting, or has a winner scheduled. The remaining audience simply never receives the blast, and the test completes with the sample results only — this can't be undone. The blast's regular [results dashboard](/email/email-blasts#watch-the-results-roll-in) keeps working throughout, aggregating the sample sends and the winner send together, with every metric clickable for a per-contact drill-down. **Free plan:** the daily 100-email limit and 1,000-contact allowance are re-checked at each send moment — the sample sends and the winner send each count against the day they fire. ## What's next? See how an email performed across every blast and flow it was sent in. # Creating Emails Source: https://docs.tented.ai/email/creating-emails Generate beautiful, personalized marketing emails by chatting with AI ## Emails, the Tented way If you've built a tent, you already know how to build an email: describe what you want, iterate in chat, and watch the preview update live. Every email Tented generates uses battle-tested HTML — bulletproof tables, MSO conditionals for Outlook, dark-mode hooks — so it renders cleanly across Gmail, Outlook, Apple Mail, Yahoo, and friends. ## Create your first email 1. Go to **Email** in the sidebar. 2. Click **Add > Create Email**. 3. The **Choose Email Template** gallery opens. Pick a starting point: a design from the [Tented example template collection](#tented-example-templates) (every one already rendered in your branding), one of [your own approved templates](/email/email-templates), or **Blank Email** for a clean branded scaffold. 4. Name your email and click **Create Email**. A screenshot of the Choose Email Template gallery with category filters and example templates rendered in the workspace's branding. You'll land in the email editor: chat on the left, live preview on the right, with your default from name and address already filled in from [email settings](/email/email-setup). A screenshot of the email editor showing the chat panel, header fields for from, reply-to, subject and preheader, and a starter email preview. ## Tented Example Templates The gallery's headline act is the **Tented example template collection**: dozens of professionally designed emails covering welcomes, newsletters, product news, promotions, receipts, events, and win-backs. The collection **automatically enables your branding**. Before you touch a prompt, every design in the gallery renders with the logo, brand color, and company details from your [workspace settings](/configuring-tented/workspace-settings), so nothing in it looks like a placeholder. Browse by category (Welcome & Onboarding, Newsletters & Digests, Sales & Promotions, and more), search by name, or click **Preview** on any card to see it full size. You can also reach the collection any time under **Email > Templates**, in the **Tented Starter Templates** row: hover a card and click **Use** to start a new email from it on the spot. A screenshot of the Email Templates tab showing the Tented Starter Templates row, with every example template rendered in the workspace's Vibes Co. branding. An email created from an example template opens ready to customize: * The template's full design, rendered with your logo, brand color, and company footer already in place * Your default from name, from address, and reply-to prefilled from [email settings](/email/email-setup) * A chat pill recording which template the email started from A screenshot of the email editor right after creating an email from the Photo Welcome example template, with an "Email created from template" pill in the chat and branded content in the preview. Creating an email from an example template spends no generation credits. Credits are only used when you ask the AI to generate or edit content. The same gallery has a **My Templates** section listing the [email templates](/email/email-templates) your team has uploaded and approved, right next to Tented's examples. From here on, a template-created email behaves like any other: refine it in chat, personalize it, and approve it, exactly as described below. ## Ask for what you want Describe the email in the chat box, the same way you'd brief a colleague: > "Write a friendly launch announcement for our new feature 'Predictive Scheduling'. Include a hero headline, 3 short benefit bullets, and a call-to-action button linking to our site. Personalize the greeting with the recipient's first name." A screenshot of a generated launch announcement email with a personalized greeting, benefit bullets, and a call-to-action button, alongside the AI's summary of what it created. Keep chatting to refine it — "make the tone more playful," "swap the bullets for a comparison table," "add a P.S. line." Each request creates a new generation you can review or roll back. Need visuals? Ask for those too — "add a hero photo of fresh pastries on a marble counter" — and Tented **generates the image** and codes it in. See [Generating Images with AI](/working-with-tents/generating-images). ### Edit sections directly Hover over any part of the email and you'll see **Click to edit** — handy for quick text tweaks without a round-trip through the AI. A screenshot of the email preview showing the Click to edit affordance on a paragraph. ## Subject lines, preheaders, and personalization Fill in the **Subject** and **Preheader** fields in the header bar. A few niceties built in: * The subject field has a **50-character guide** — stay under it and your subject won't get clipped on mobile. * The `{}` **Personalize** button inserts **personalization tokens** — any standard or [custom contact field](/people/contact-fields), in the form `{{contact.firstName}}`. They work in the subject, preheader, and anywhere in the body. (Full token reference: [Personalization Tokens](/email/personalization).) * Tokens resolve to an empty string when a contact is missing the value, so add a fallback for anything visible: `{{contact.firstName:default=there}}` renders as "there" when there's no first name. * Tokens can also reformat the value's casing on the way out — `{{contact.firstName:titlecase}}` renders "ada" as "Ada". Four options are available (`:lowercase`, `:uppercase`, `:sentencecase`, `:titlecase`), and they combine with a fallback in either order. (Details: [Formatting values](/email/personalization#formatting-values).) * A **"View in browser" link** is one token away: point any link at `{{tented.viewAsWebpageLink}}` and each recipient gets a personal link to a hosted copy of their exact email, live for 12 months after the send. (Details: [The view-as-webpage link](/email/personalization#the-view-as-webpage-link).) * Per-email **From/Reply-to overrides**: any email can use its own sender details instead of the workspace defaults. Click **Personalize** and you'll get a small builder — pick the field, choose optional formatting, set an optional default, and it inserts the token for you: A screenshot of the personalization popover with a Field dropdown set to First Name, a Default value field, and the resulting token. To see personalization with real data, click **Preview as** and pick a contact — Tented uses their contact and activity data as context. A screenshot of the Preview as menu showing Contact Personalization with a searchable list of contacts. ## Check it on mobile (and in code) * The **mobile preview** button renders your email at real device dimensions, with a device picker. * **Code mode** (`<>`) shows the full HTML and a plain-text view. Advanced users can edit the code directly. A screenshot of the mobile preview showing the email rendered at iPhone dimensions. A screenshot of code mode showing the email's HTML with MSO conditionals and meta tags. The truest test is a real inbox: click **Send Sample** in the top bar to send yourself a one-off copy — see [Sending Sample Emails](/email/sample-emails). ## Generation history The clock icon opens **Generation History** — every AI generation of this email, with **Preview** and **Revert** so you can compare and restore past versions, just like tents. A screenshot of the Generation History panel listing generations with Preview and Revert actions. ## Approve it Here's the one concept that's different from tents: **emails must be approved before they can send**. Approval freezes a known-good version — the one your blasts and flows will actually deliver. Click **Approve** in the top-right corner and confirm. A screenshot of the Approve email confirmation dialog. A few things to know about approval: * Your Emails list shows each email's status — **Draft** or **Approved**. * After approving, you can keep editing safely: draft changes **aren't included in sends** until you click **Approve Update**. * Blasts and flows only offer approved emails (or brand-new ones you compose inline). A screenshot of the Emails list showing an email with an APPROVED status badge. ## What's next? Pick a list, pick a time, and launch your first email blast. Send it automatically when contacts hit a trigger, as part of a multi-step flow. # Email Blasts Source: https://docs.tented.ai/email/email-blasts Send a one-time email to an audience — scheduled, or right now ## What's a blast? An **email blast** is a one-time send to an audience: product launches, newsletters, event invites, announcements. You pick who gets it, what they get, and when — Tented handles delivery, unsubscribes, and the results dashboard. First time sending? Make sure your sending domain is verified — see [Setting Up Email](/email/email-setup). Head to **Email > Campaigns** and click **Create Email Blast**. Name your blast (this is just for your team — recipients never see it), and you're dropped into a three-step wizard: **Audience → Email → Schedule**. Your progress saves automatically as you go. A screenshot of the Campaigns tab with cards for Create Email Blast and Create Triggered Flow. ## Step 1: Pick the audience Choose an existing [static or dynamic list](/people/working-with-lists), or build a one-off list just for this blast (it's deleted along with the blast, keeping your Lists page tidy). A screenshot of the audience step showing the choice between using an existing audience and creating a new one, with a list picker below. Once you pick a list, Tented shows you the **qualified** count (people who will actually receive the email) and the **blocked** count (people excluded because they've unsubscribed). Click **View all** to inspect exactly who qualifies. A screenshot of a selected audience showing 8,412 qualified contacts and 147 blocked. ## Step 2: Pick the email Use an existing **approved** email, or compose a one-off email inline just for this blast. A screenshot of the email step showing the choice between using an existing approved email and creating a new one for this blast. Blasts send the **approved version** of an email. If you've made draft edits since approving, they won't go out until you click **Approve Update** on the email. ## Step 3: Schedule it (or don't) Two ways to launch: * **Schedule send** — pick a day and time; the blast is approved now and sends automatically. * **Send now** — confirm, and it goes to everyone qualified immediately. This can't be undone, so the confirmation shows you the exact recipient count first. A screenshot of the schedule step with options to schedule for a specific day and time or send now, plus Additional Settings with the Set as operational toggle and the AB Test section. **Set as operational** is for service notices — password resets, security announcements, terms changes. Operational sends go to unsubscribed contacts too and skip the unsubscribe footer — though contacts whose email was marked invalid by a hard bounce are still excluded (that address can't receive mail). Never use it for marketing. ### Or make it a test Not sure which subject line (or send time, or sender) will win? The **AB Test** section under Additional Settings turns the blast into an experiment: up to five variations go to a sample of the audience, and the winner is sent to everyone else. See [A/B Testing](/email/ab-testing). Scheduled the wrong thing? A scheduled blast can be **unscheduled** any time before it sends — it returns to draft (with your original send time remembered) so you can edit the audience or email and reschedule. A single blast can reach up to **250,000 recipients**. If your audience is larger, the schedule step warns you before you launch — trim the list or split the send. **Free plan:** you can send up to **100 emails per day**. The Audience and Schedule steps remind you of this — and it's checked again when a scheduled blast actually fires. If at that moment the recipient count exceeds what's left of the day's allotment (or your People database is over the free plan's 1,000-contact allowance), the blast is **automatically cancelled** and nothing is sent: the campaigns list shows it as Cancelled with "0 sent (Daily Usage Limit)", and its Results card explains what happened with an upgrade option. **Send now** is friendlier — it warns you up front instead of cancelling anything. ## Watch the results roll in The moment a blast sends, its page becomes a live results dashboard: recipients, sent, delivered, opened, clicked, bounced, unsubscribed, and spam reports. A screenshot of a completed blast showing its status, audience, email, and a results dashboard with sent and delivery metrics. ### Drill into any metric Every number on the dashboard is clickable. Click a metric — say **Delivered** or **Bounced** — to open the **Activity** view: the exact contacts in that bucket, with timestamps (delivered at, opened at, and so on). A left-hand rail lets you jump between buckets, and there's a search box to find a specific person. A screenshot of the blast Activity view showing the Delivered bucket with one contact, the metric rail on the left, and an Export button. Click **Export** to download the contacts in the current bucket as a CSV — handy for pulling your bounced addresses for cleanup, or your openers for a follow-up. All your blasts (and flows) live together on the **Campaigns** tab, with status and headline results at a glance. A screenshot of the Campaigns list showing a completed email blast with 1 sent. ## Clone a blast Running the same newsletter every month? Clone last month's instead of rebuilding it: hover a blast in the Campaigns list and click the **Clone** icon, or use the **⋯ menu > Clone campaign** inside the blast. A screenshot of the Clone Blast dialog with a New Blast Name field prefilled with Copy of Terra Collection Launch and a note that cloned blasts always start as drafts. Cloned blasts **always start as drafts** — the audience, email, and settings carry over, but a blast that was already scheduled or sent is cloned *without* a send time, so nothing fires until you schedule it. Cloning works from any state, including completed and archived blasts, and drops you straight into the new draft. ## Archive a campaign Campaigns that have actually run aren't deleted — they're **archived**, so their send history stays intact. Hover a completed (or failed) blast in the Campaigns list and click the **Archive Campaign** icon, or use **⋯ menu > Archive campaign** on the campaign's page. A screenshot of the Campaigns list with the Archive Campaign tooltip showing on a completed blast's hover action. Confirm, and the campaign moves out of your campaigns list — its results are preserved and it stays reachable forever: A screenshot of the Archive confirmation dialog explaining that the blast will move out of the campaigns list and its send results are preserved. To find archived campaigns, open **Filter**, pick a **Type** first, then choose **Archived** under Status: A screenshot of the campaigns Filter dropdown with Type set to Email Blast and the Status list showing the Archived option. A few rules worth knowing: * **Drafts delete, ran-campaigns archive.** A blast that never sent (and a flow nobody entered) offers **Delete** instead — there's no history to preserve. * **In-flight blasts offer neither.** A scheduled blast must be unscheduled first, and a blast that's mid-send can't be touched. * **Flows archive too** — a [triggered flow](/email/triggered-flows) that has run contacts is archived once it's paused. Active flows must be paused first. * **Archiving is permanent.** There's no unarchive — but everything about the campaign stays viewable under the Archived filter, and you can [clone](#clone-a-blast) an archived blast into a fresh draft any time. ## What's next? Go beyond one-time sends — build multi-step journeys that run automatically. # Tracking Email Performance Source: https://docs.tented.ai/email/email-performance See how an email performed across every blast and flow it was sent in ## One email, every send Campaign dashboards tell you how a *send* went. The email's own detail page answers a different question: **how has this email performed across everything it's ever been sent in?** Open **Email > Emails** and click an email's name (not Edit) to land on its detail page. Alongside the preview and metadata you'll find two cards built from the email's full sending history: A screenshot of an email detail page showing the Email Metadata card, a Performance card with realistic send and engagement totals, and a Sent in card listing a flow and a blast. ## The Performance card Lifetime totals for this email, rolled up across every blast and triggered-flow send: | Metric | Rate shown as a percent of | | ---------------- | -------------------------- | | **Sends** | — | | **Delivered** | Sends | | **Opened** | Delivered | | **Clicked** | Delivered | | **Bounced** | Sends | | **Unsubscribed** | Delivered | | **Spam reports** | Delivered | The same denominators apply everywhere results appear in Tented: delivery outcomes are measured against what was sent, engagement against what actually reached an inbox. A **Last sent** line at the bottom tells you how fresh the numbers are; an email that's never gone out shows *"This email hasn't been sent yet."* [Sample emails](/email/sample-emails) don't count here — their opens and clicks are deliberately excluded from results, so testing your own email doesn't pollute the stats. ## The Sent in card **Sent in** lists every campaign that delivered this email, newest first. Each row shows: * The campaign name, linking straight to its results * A **Blast** or **Flow** badge * The send date and **Gen #N** — which approved generation of the email actually went out That generation stamp matters: emails send their **approved version**, so if you've iterated since a campaign ran, Gen #2 in the list and Gen #5 in your editor are different emails. The [generation history](/email/creating-emails#generation-history) in the editor lets you view any past version. ## Drilling into a specific send For per-campaign numbers and per-contact activity, click through to the campaign itself: * **Blasts** open a [results dashboard](/email/email-blasts#watch-the-results-roll-in) where every metric is clickable — see exactly who opened, clicked, bounced, or unsubscribed, with timestamps, and export any bucket as a CSV. * **Flows** show per-step stats on their [Overview tab](/email/triggered-flows#track-performance), plus a per-contact run history. ## What's next? Stop guessing — test up to five variations and send the winner automatically. # Setting Up Email Source: https://docs.tented.ai/email/email-setup Verify your sending domain and sender details so you can start sending marketing email ## Before you send Tented sends marketing email from **your own domain** — which means better deliverability and emails that are unmistakably yours. Before your first campaign, there's a one-time setup: verify a sending domain and fill out your sender profile. Sending is blocked until your domain is verified, so this is step one. Everything lives in one place: click your profile in the bottom-left corner, choose **Workspace Settings**, then click **Manage Email** (or go straight to **Settings > Email**). ## 1. Verify your sending domain Add a domain you own, and Tented generates the DNS records that let mailbox providers verify your email is legit — SPF, DKIM, and DMARC. A screenshot of the Sending Domain section showing a verified domain with its DNS records for Mail/SPF, DKIM, and DMARC all marked Verified. Sending domains are available on **every plan, Free included** — verifying a domain is part of getting set up, not a paid feature. Free-plan limits apply to sending volume instead: 100 emails per day, and [flows require Pro or Max to activate](/email/triggered-flows#activate-it). 1. Enter your domain (for example, `yourcompany.com`). 2. Copy each generated record — a few CNAMEs and a TXT — into your DNS provider (GoDaddy, Cloudflare, Namecheap, etc.). 3. Click **Re-check**. DNS changes can take a little while to propagate, so don't panic if it's not instant. Once every record shows **Verified**, you can send from any address on that domain — `hello@yourcompany.com`, `news@yourcompany.com`, whatever fits. Sending is blocked until your domain is fully verified. If a record stays unverified, double-check for typos and make sure your DNS provider isn't proxying the CNAME records. ## 2. Complete your sender profile Anti-spam laws like CAN-SPAM and CASL require every promotional email to include your contact information — including a physical mailing address. Your sender profile is where Tented gets it: * **Legal company name** and **support contact email** * **Street address**, city, postal code, state, and country * **Privacy policy URL** (optional, but a nice touch) A screenshot of the Sender Profile form with legal company name, support email, and mailing address fields, marked Complete. These details are automatically merged into your email footers at send time, so you're compliant without thinking about it. ## 3. Set your default email headers Save yourself some typing: set a default **from name**, **from address**, and **reply-to**. Every new email starts pre-filled with these, and you can always override them on a specific email before sending. A screenshot of the Default Email Headers section and the Unsubscribe Footer editor with its HTML template. ## 4. Review the unsubscribe footer Marketing emails must include a working unsubscribe link. Tented auto-appends a footer to any email that doesn't already include its own — and you can customize its HTML here. The footer supports tokens that are filled from your sender profile at send time: * `{{tented.company}}` — your legal company name * `{{tented.address}}` — your mailing address * `{{tented.privacyUrl}}` — your privacy policy URL * `{{tented.unsubscribeOneClickLink}}` — **required** — each recipient's personal one-click unsubscribe link When a contact unsubscribes, Tented flips their **Unsubscribed** field and quietly excludes them from future marketing sends. You don't need to manage suppression lists yourself. ## Deliverability & suppression A few behaviors worth knowing so your sends stay healthy: * **Unsubscribes are honored automatically.** An unsubscribed contact is excluded from every future marketing blast and flow — you'll see them counted as **blocked** when you pick an audience. There's no re-subscribe flow, so if someone opts out, it sticks (they can be re-added only with fresh consent on your side). * **Hard bounces suppress themselves.** When an address hard-bounces, Tented marks the contact's email invalid and excludes it from **every** future send — operational included, since the address simply can't receive mail. The flag is system-managed: it can't be set by hand or by imports, and it clears automatically if the contact's email address is updated. * **Bounces and spam reports are tracked.** They show up in your [blast](/email/email-blasts) and flow results so you can spot problem addresses, and you can export the bounced bucket to clean your list. * **One-click unsubscribe** is built in via the `List-Unsubscribe` header, so Gmail and Apple Mail show their native unsubscribe button — which protects your sender reputation. * **Operational sends bypass the unsubscribe preference only** (password resets, security notices) — invalid, hard-bounced addresses stay excluded even there. Keep that toggle off for anything promotional. ## You're ready to send 🎉 Describe the email you want and watch Tented write and design it for you. # Email Templates Source: https://docs.tented.ai/email/email-templates Give every email a consistent, on-brand starting point ## Why templates? If your team sends a lot of email, you probably don't want every message starting from a blank canvas. **Email templates** let you upload a designed HTML shell once — your header, footer, colors, and layout — and use it as the starting point whenever anyone creates a new email. Find them under **Email > Templates**, alongside the built-in [Tented example template collection](/email/creating-emails#tented-example-templates), which automatically renders every starter design in your branding. ## Adding a template 1. Click **Add > Add Email Template**. 2. Name your template. 3. Upload your HTML file. A screenshot of the Add Email Template dialog with a template name and an HTML file selected. The HTML file must include a `` declaration. If you don't have a designed template handy, create an email you love in the [email editor](/email/creating-emails) first, grab its HTML from code mode, and upload that. ## Editing and approving Your template opens in the same AI editor used for emails — chat to refine it, edit the code directly, and set optional **default headers** (from name, from address, reply-to, and a default subject) that flow into every email created from it. A screenshot of the template editor showing the uploaded HTML rendered in the preview with template-level default header fields. Templates follow the same **approval** model as emails: click **Approve** when it's ready, and only approved templates show up in the template picker when creating a new email. Want to see the template in a real inbox before your team builds on it? Click **Send Sample** in the template editor's top bar — see [Sending Sample Emails](/email/sample-emails). A screenshot of the Templates list showing a template with an APPROVED status badge. ## Using a template When you create a new email (**Add > Create Email**), the **Choose Email Template** gallery opens. Your approved templates appear in its **My Templates** section, next to [Tented's example templates](/email/creating-emails#tented-example-templates). Pick one, name the email, and click **Create Email**. The new email inherits the template's layout and default headers. You customize from there with chat, and the template stays untouched. ## What's next? Send a one-time email to a list — scheduled, or right now. # Personalization Tokens Source: https://docs.tented.ai/email/personalization Every token you can use to tailor emails to each recipient ## What are tokens? Tokens are placeholders wrapped in double curly braces — like `{{contact.firstName}}` — that Tented swaps for real values when each email is sent. They work in the **subject**, **preheader**, and anywhere in the **body**, so every recipient gets a message that feels written for them. This page is the full reference. For the how-to of inserting them, see [Creating Emails](/email/creating-emails#subject-lines-preheaders-and-personalization). ## Contact tokens Reference any [standard or custom contact field](/people/contact-fields) by its API name: ``` {{contact.firstName}} {{contact.company}} {{contact.plan_tier}} ← a custom field ``` **Fallbacks.** A token resolves to an empty string when a contact is missing that value. For anything a reader will see, add a default so you never ship "Hi ," : ``` Hi {{contact.firstName:default=there}}, ``` The easiest way to get the syntax right is the **Personalize** button in the editor — pick the field, choose optional formatting, set an optional default, and it inserts the token for you. ## Formatting values CRM data doesn't always arrive in the casing you want to send. Add a **case format** option to any contact or campaign dynamic token to fix it at send time: | Option | What it does | `"ada LOVELACE"` becomes | | --------------- | ---------------------------------------- | ------------------------ | | `:lowercase` | everything lowercase | `ada lovelace` | | `:uppercase` | everything uppercase | `ADA LOVELACE` | | `:sentencecase` | first letter capitalized, rest lowercase | `Ada lovelace` | | `:titlecase` | each word capitalized | `Ada Lovelace` | ``` Hi {{contact.firstName:titlecase}}, Your promo code: {{campaign.dynamic.couponCode:uppercase}} ``` A few rules worth knowing: * **One format per token**, added anywhere after the field name. It combines with a fallback in either order — `{{contact.firstName:titlecase:default=friend}}` and `{{contact.firstName:default=friend:titlecase}}` are equivalent, and the format applies to the fallback too (a missing first name renders as "Friend"). * **Text values only.** Number, date, and true/false fields render unchanged — a format on those is simply ignored, never an error. * **Use the keywords exactly as shown above** — `:lowercase`, `:uppercase`, `:sentencecase`, `:titlecase`. Anything else is flagged as an invalid token. * `{{tented.*}}` sender-profile tokens don't take formats (or any other option). ## Sender-profile tokens These `{{tented.*}}` tokens are filled by Tented into the email **body** (HTML and plain text — not the subject line) at send time. The first three pull from your [Sender Profile](/email/email-setup) and are mainly used in the unsubscribe footer; the last two become personal, per-recipient links: | Token | Resolves to | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | | `{{tented.company}}` | Your legal company name | | `{{tented.address}}` | Your mailing address | | `{{tented.privacyUrl}}` | Your privacy policy URL | | `{{tented.unsubscribeOneClickLink}}` | Each recipient's one-click unsubscribe link (**required** in the footer) | | `{{tented.viewAsWebpageLink}}` | Each recipient's [view-as-webpage link](#the-view-as-webpage-link) — a hosted web copy of the exact email they received | ## The view-as-webpage link Add a "View this email in your browser" link — typically small text above the header or in the footer — by pointing any link at `{{tented.viewAsWebpageLink}}`: ```html theme={null} View this email in your browser ``` When a [blast](/email/email-blasts) or [triggered flow](/email/triggered-flows) email containing the token is sent, every recipient's copy carries their own private link to a web version of that exact email. A few things worth knowing: * **It's a frozen copy.** The web version shows the email exactly as that recipient received it — their personalization values, their working unsubscribe link, everything captured at the moment of sending. Editing the email, changing the contact's fields, or even deleting the email afterwards doesn't change what the link shows. * **Links stay live for 12 months.** After that, the page politely explains that the email is no longer available. The recipient's inbox copy is unaffected. * **Treat the link like the email itself.** Anyone who has it can view that recipient's copy — including its live unsubscribe link — so a forwarded link is equivalent to a forwarded email. * **It's never rewritten by click tracking.** Like the unsubscribe link, it bypasses click-tracking redirects so it keeps working on its own; clicks on it don't appear in campaign metrics. * **[Sample sends](/email/sample-emails) get real links too.** A sample's view-as-webpage link opens a hosted copy of that exact sample — a handy way to share a test render — with the same 12-month lifetime. You can also just ask the editor's AI — "add a view-in-browser link above the header" — and it inserts the token for you. ## Campaign dynamic tokens (flows only) Inside a [triggered flow](/email/triggered-flows), `{{campaign.dynamic.*}}` tokens carry values that belong to a contact's *run* of the flow rather than to the contact record. Values arrive from three places: **Form Submitted triggers.** When a flow starts from a form submission, every answer becomes a token named after the form field, with spaces and punctuation turned into underscores (capitalization kept): a "first name" field is available to every email and webhook in that run as ``` {{campaign.dynamic.first_name}} ``` Multi-choice answers arrive comma-separated. **API triggers.** A flow with a **Tented API** trigger is started by an external system [calling the public API](/api-reference/managing-triggered-flows#trigger-a-flow-api-trigger), which can pass token values along — `{"tokens": {"coupon_code": "SAVE20"}}` makes `{{campaign.dynamic.coupon_code}}` available to the run. The same goes for the `dynamic_context` payload when [adding a contact to a flow by API](/api-reference/managing-triggered-flows#add-a-contact). **Flow steps.** Earlier steps can hand values to later emails. The headline example is the **Create Tent** step: with **Auto-approve** on, it publishes a personalized page and exposes its URL as `{{campaign.dynamic.tentURL}}`. Link a later **Send Email** step's button to that token and every contact's email points to *their own* generated page — a second Create Tent step would expose `{{campaign.dynamic.tentURL2}}`, and so on. Full walkthrough in [The Create Tent step](/email/triggered-flows#the-create-tent-step). **Send Webhook** steps can likewise capture fields from the webhook's response into tokens for later steps. Campaign dynamic tokens accept the same [case formats](#formatting-values) as contact tokens — `{{campaign.dynamic.couponCode:uppercase}}` — but not `:default=`; a missing value renders blank. ## Before you send * **Preview with real data** — click **Preview as** in the editor and pick a contact to see tokens resolved. * **Flows validate tokens** — when you activate a flow, Tented flags emails that reference invalid or inactive fields, so a typo can't ship. Fix the flagged token and re-activate. * **Missing values are silent** — without a `:default=`, an absent value simply disappears, which can leave odd spacing or punctuation. Default anything visible. The full email editor workflow. # Sending Sample Emails Source: https://docs.tented.ai/email/sample-emails Send a one-off test of any email or template to a real inbox before it goes to an audience ## Why send a sample? The editor's preview is faithful, but nothing beats the real thing: your actual mail client, your actual dark mode, your actual spam filter. **Send Sample** delivers one copy of an email — or an [email template](/email/email-templates) — to any address you choose, so you can look it over in a real inbox before a [blast](/email/email-blasts) or [flow](/email/triggered-flows) sends it to people who matter. ## Where to find it The same dialog is available wherever you work with emails and templates. For emails: * **Email editor** — the **Send Sample** button in the top bar, next to **Approve**. * **Emails list** — select an email, then choose **Send Sample** from the **Email Actions** dropdown. * **Email details page** — the **Email Actions** dropdown, top-right. A screenshot of the email editor's top-bar controls showing the Send Sample button between Preview as and Approve. A screenshot of the Email Actions dropdown on the Emails list with a Send Sample item. For templates: * **Template editor** — the **Send Sample** button in the top bar. * **Templates list** — select a template, then choose **Send Sample** from the **Template Actions** dropdown. Reviewing an old version in **Generation History**? The history toolbar has its own **Send Sample** — it opens the dialog with that generation pre-selected. This works in both the email and template editors. ## Send a sample 1. **Send to** — enter any email address. It doesn't need to belong to a contact. 2. **Version** — defaults to the latest generation. Pick any past generation from the dropdown; your approved version is marked **Approved**. 3. **Personalize as contact** *(optional)* — search by name, email, or phone — or paste a contact ID — and [personalization tokens](/email/personalization) render with that contact's real values. The sample still goes only to the **Send to** address, never to the contact. A screenshot of the Send Sample Email dialog with a recipient field, a version picker showing the latest generation, an optional contact personalization search, and a note that the subject is prefixed with TEST. Without a contact, tokens render the way they would for a contact with no data: defaults like `{{contact.firstName:default=there}}` kick in, and everything else renders blank. Click **Send Sample**, and go check the inbox. ## Drafts welcome Samples work for **drafts and approved emails** alike — no need to approve first. Two things to know: * If a draft doesn't have a from address yet, the sample falls back to your workspace's [default email headers](/email/email-setup). * A **verified sending domain** is still required. Samples travel the same delivery pipeline as real sends — that's what makes them a meaningful test — so an unverified domain blocks them too. See [Setting Up Email](/email/email-setup). ## Sampling a template Templates don't send to audiences, but a sample is a great way to check how one renders in a real inbox before your team builds emails on top of it. Since a template only carries optional [default headers](/email/email-templates), the sample resolves its subject and sender in two steps: 1. The template's own defaults — default subject, from name, from address, and reply-to — are used wherever you've set them. 2. Your workspace's [default email headers](/email/email-setup) fill in anything left blank. If neither the template nor your workspace defaults provide a from address, the send is blocked until you add one. Everything else — [personalization tokens](/email/personalization), sender-profile tokens, the version picker — works exactly as it does for email samples. ## How a sample differs from a real send A sample is built to match a real send as closely as possible — same footer, same link handling, same rendering. If the email includes a [view-as-webpage link](/email/personalization#the-view-as-webpage-link), it works for real: the sample's link opens a hosted copy of that exact sample. The differences: * **The subject is prefixed with "TEST: "** so nobody mistakes it for the real thing. * **Opens and clicks are never recorded.** Links are still rewritten through your click-tracking domain — so you can verify they work end to end — but nothing ever shows up in a results dashboard. * **The unsubscribe link is inert.** It's present and clickable, but leads to a page explaining that unsubscribe is not supported for sample emails. Clicking it never unsubscribes anyone. * **Nothing is counted.** Samples don't count toward your send usage, and they never appear in campaign metrics. ## What's next? Pick a list, pick a time, and launch your first email blast. # Triggered Flows Source: https://docs.tented.ai/email/triggered-flows Build automated, multi-step email journeys on a visual canvas ## What's a triggered flow? A **triggered flow** is marketing automation on autopilot: contacts enter when they match a trigger, then move through the steps you've laid out — sending emails, waiting, branching, updating fields — until they reach the end. Welcome series, onboarding sequences, re-engagement campaigns: this is where they live. Head to **Email > Campaigns** and click **Create Triggered Flow**. Name it, and you land on the visual flow builder with three tabs: **Overview** (analytics), **Edit Flow** (the canvas), and **History**. ## Set the trigger Click the **Trigger** node to choose how contacts enter: A screenshot of the flow builder showing the Setup Flow Trigger panel with the trigger types and the trigger node on the canvas. * **Added to Static List** — the classic welcome-series trigger * **Removed from Static List** * **Date Based** — on, before, or after a contact date field * **Contact Created** — the moment someone new hits your database * **Contact Updated** — when a contact field changes * **Form Submitted** — when a visitor submits a form on one of your tents 🏕️. Each answer is also handed to the run as a [`{{campaign.dynamic.*}}` token](/email/personalization#campaign-dynamic-tokens-flows-only), so later emails can echo what was submitted. * **Tented API** — an external system enrolls the contact by [calling the public API](/api-reference/managing-triggered-flows#trigger-a-flow-api-trigger), optionally passing values that become `{{campaign.dynamic.*}}` tokens (a coupon code, an order number, …) You can stack multiple triggers on one flow with **+ Add trigger**. ### Qualification rules Below the triggers you'll find **Qualification rules** — the fine print of who gets in: * **Re-entry policy**: allow contacts to go through the flow again after they finish (or block them to once ever). * **Disqualification rules**: audience rules that keep matching contacts *out* — for example, exclude anyone whose lifecycle stage is already "Customer" from a lead-nurture flow. A screenshot of the Qualification rules panel showing the re-entry policy options and disqualification rules. ## Build the steps Click **+** anywhere on the canvas to add a step. You've got a full automation toolkit: A screenshot of the step palette showing Send Email, Wait, Update Contact, Add to List, Remove from List, Condition, Create Tent, Send Webhook, and Drop from Flow. | Step | What it does | | ---------------------------------- | ---------------------------------------------------- | | **Send Email** | Send an approved (or inline) email | | **Wait** | Pause for a duration, or until a contact date field | | **Update Contact** | Set contact field values | | **Add to List / Remove from List** | Manage static list membership | | **Condition** | Branch the journey based on audience rules | | **Create Tent** | Generate a personalized landing page for the contact | | **Send Webhook** | Call an external endpoint | | **Drop from Flow** | Exit the contact early | For a Send Email step, pick any **approved** email — the same approval rule as blasts — or compose one inline. There's also an operational-send toggle for non-marketing notices. A screenshot of the Configure Send Email panel with an approved email selected and the flow canvas showing Trigger, Send Email, and End nodes. Wait steps keep journeys humane — space your sends out instead of firing everything at once: A screenshot of a flow canvas showing Trigger, Send Email, and a Wait step configured for 1 day. ### The Create Tent step The **Create Tent** step generates a *personalized landing page for each contact* as they pass through the flow — a unique page per person, built from their contact data. A screenshot of the Configure Create Tent panel showing Source, a personalized Prompt, an Auto-approve checkbox, a URL path field, and a Published URL token. Configure it with: * **Source** — **From scratch** (generate with AI from a prompt) or **From template** (copy an approved tent template, no generation credits). A screenshot of the Source dropdown showing From scratch and From template options. * **Prompt** — what to generate. Click **Personalize** to drop in [contact tokens](/email/creating-emails#subject-lines-preheaders-and-personalization) like `{{contact.firstName}}`, so every contact gets a page tailored to them. * **Tent name** and **URL path** — both optional and both personalizable; a blank URL path falls back to the tent ID. * **Auto-approve** — publish the generated tent automatically, so it's live the moment it's created. **Use the new page's URL in a later email.** When **Auto-approve** is on, the step publishes the tent and stores its live URL in a token — shown in the panel as the **Published URL token**, `{{campaign.dynamic.tentURL}}`. Reference that token in any **Send Email** step *after* the Create Tent step, and each contact's email links to their own personalized page. A typical shape: **Create Tent** (auto-approve, prompt personalized with `{{contact.firstName}}`) → **Send Email** whose CTA links to `{{campaign.dynamic.tentURL}}`. If you have more than one Create Tent step, the second becomes `{{campaign.dynamic.tentURL2}}`, and so on. ## Activate it Click **Activate** and your flow goes live: from now on, contacts matching a trigger are enrolled automatically and start moving through the steps. Activating flows requires a **Pro or Max** plan. On the Free plan you can build, edit, and save flows — the canvas shows a reminder, and an **Upgrade to Unlock** gem appears in place of the Activate button. If a workspace downgrades to Free, its active flows are **paused automatically** (members stay put, and resume if you upgrade and reactivate). See [Upgrading Your Plan](/billing-subscriptions/upgrading-plan). A screenshot of an active flow with the ACTIVE badge and a banner noting the flow must be paused before making changes. Active flows are locked for editing — hit **Pause** first if you need to make changes, then reactivate. Pausing doesn't kick anyone out: contacts already in the flow simply wait, and pick up where they left off when you reactivate. Triggers fire on *new* events after activation (evaluated about once a minute); contacts already on a list won't enter an "Added to Static List" flow retroactively. Retiring a flow for good? Pause it, then **Archive** it from the header's overflow menu — the flow moves out of your active campaigns with its run history preserved, and you can find it under the **Archived** status filter on the Campaigns tab. Archiving can't be undone: contacts still waiting in the flow stay in its history, but the flow never resumes. See [Archive a campaign](/email/email-blasts#archive-a-campaign) for the full rules. ## Track performance The **Overview** tab shows your flow's vitals: total contacts entered, how many are currently waiting, the most recent entry, who's parked at each Wait step, and per-email stats (sent, delivered, opened, clicked, bounced, unsubscribed, spam). A screenshot of flow analytics showing one contact entered, one currently waiting at the 1-day Wait step, and the Send Email step at 100 percent delivered. ### See every run The **History** tab lists every contact who's been through the flow — their status (In Flow, Completed, Exited), when they entered, and when they left. Filter by status or date range, and click **Export** for a CSV. A screenshot of the flow History tab showing one run with an In Flow status and a View Run Details button. Click **View Run Details** on any run to see that contact's exact path through the flow — a step-by-step timeline of what happened and when (entered flow, email sent, wait, email delivered), with the contact's current position highlighted. A screenshot of the Run details timeline showing Entered flow, Email sent, Wait, and Email delivered events with timestamps. ## Flow or blast? Reach for a **blast** when it's one message, one audience, one moment — a launch, a newsletter issue. Reach for a **flow** when the sending should happen *by itself* whenever someone qualifies — welcomes, nurtures, onboarding. Most teams end up with both. # Getting Started Source: https://docs.tented.ai/getting-started Launch your first landing page and email campaign with Tented ## Welcome to Tented 🏕️ Tented is an AI marketing automation platform: instant landing pages, AI-written marketing email, a built-in contact database, and automated campaigns — all driven by natural language. This guide gets you from a fresh account to a live page *and* your first email campaign. A screenshot of the Tented home page with the prompt box asking "What should we make for your customers today?" ## The lay of the land Your sidebar has everything: * **Home** — the prompt box. Describe something and Tented builds it. * **Tents** — your AI-generated landing pages. (A **tent** is a complete, standalone page you can publish instantly.) * **Email** — your emails, templates, and campaigns (blasts and triggered flows). * **People** — your contact database and lists. * **Analytics** — how it's all performing. ## Step 1: Add your branding Upload your logo, icon, and brand color when prompted at signup (or anytime in [Workspace Settings](/configuring-tented/workspace-settings)). Tented uses them everywhere — tents *and* emails — so everything comes out on-brand automatically. A screenshot of the dialog box for adding a Primary Brand Color, Brand Logo, and Brand Icon. ## Step 2: Create your first tent On the **Home** page, describe the landing page you want: > "Generate a landing page for our new feature, 'Predictive Scheduling'. The page must be high-converting and focused solely on capturing early access signups. Include a hero section, features, and pricing." Tented generates a complete, responsive page in about a minute. Refine it by chatting ("make the CTA bigger", "add testimonials"), then click **Publish** to put it live on the web. A screenshot of the prompt and the resulting tent preview. For the full tour — editing, code mode, form submissions, publishing — see [Creating Tents](/working-with-tents/creating-tents). ## Step 3: Set up email One-time setup, about ten minutes, and most of that is waiting for DNS: 1. Go to **Settings > Email** and add your **sending domain**. Copy the generated DNS records into your DNS provider and verify. 2. Fill out your **sender profile** (company name and mailing address — required by anti-spam law). 3. Set your **default from name and address**. A screenshot of the Sending Domain section showing a verified domain with SPF, DKIM, and DMARC records. Full walkthrough: [Setting Up Email](/email/email-setup). ## Step 4: Add your people Head to **People** and bring in your contacts — one at a time with **Quick Add**, or in bulk with **Import from CSV** (Tented auto-maps your columns). Then organize them into [lists](/people/working-with-lists): hand-picked **static lists**, or rule-based **dynamic lists** that keep themselves current. A screenshot of the People area showing a table of contacts. Tents and People are connected: form submissions from your published tents can flow straight into your contact database. ## Step 5: Send your first campaign 1. Go to **Email > Add > Create Email**, and describe the email you want — Tented writes and designs it, personalization tokens and all. 2. Click **Approve** when you're happy (only approved emails can send). 3. Go to **Campaigns > Create Email Blast**: pick your list, pick your email, and **Send now** (or schedule it). A screenshot of a generated launch announcement email in the email editor. Then watch opens, clicks, and deliveries roll in on the blast's results dashboard. ## Step 6: Put it on autopilot Once the manual version feels good, automate it: [triggered flows](/email/triggered-flows) enroll contacts automatically — when they're added to a list, submit a tent form, or hit a date — and walk them through multi-step journeys with emails, waits, and branches. ## Where to next? Master the tent editor, publishing, and form submissions. Everything about the People area, imports, and profiles. The AI email editor, personalization, and approval. Build automated journeys on the visual canvas. **Need help?** Contact support at [support@tented.ai](mailto:support@tented.ai). # Introduction Source: https://docs.tented.ai/index Welcome to Tented - the AI marketing automation platform ## Welcome to Tented 🏕️ Tented is an **AI marketing automation platform** that handles your whole go-to-market motion — landing pages, marketing email, and your contact database — through one simple interface: describe what you want, and AI builds it. Spin up a landing page in a sentence. Write a personalized email campaign in a prompt. Import your contacts, segment them into lists, and put your follow-up on autopilot with triggered flows. No coding, no drag-and-drop wrestling, no six-tool Franken-stack. ## What can you build with Tented? Describe your page and get a beautiful, conversion-ready tent in seconds — with forms, analytics, and instant publishing built in. Generate on-brand, personalized emails that render perfectly in every inbox. Send from your own verified domain. Import, organize, and segment your people with static lists and rule-based dynamic lists that keep themselves up to date. Launch one-time campaigns or build multi-step journeys that enroll contacts automatically — welcome series, nurtures, and more. ## One platform, end to end Everything in Tented is designed to work together: * A visitor submits a form on your **tent** → they land in your **People** database * A new contact joins a **list** → a **triggered flow** welcomes them automatically * Your **flow** can even generate a **personalized tent** for each contact it touches That loop — pages that capture, a database that organizes, and email that converts — is what used to take four separate tools and a lot of duct tape. ## Use cases Tented is perfect for: * **Marketing teams** running campaigns end to end — landing page, audience, email, follow-up * **Sales teams** building personalized prospect pages and outreach * **Startups** launching products with a waitlist page and an announcement blast * **Agencies** delivering full campaigns for clients, faster * **Anyone** who wants marketing automation without the enterprise-tool learning curve ## Get started in minutes Create your first tent, set up email, and launch your first campaign. ## Key workflows Create, edit, publish, and track AI-generated landing pages. Build your contact database and segment it into audiences. Set up your domain, create AI emails, and send blasts and flows. Customize your workspace, team, branding, and domains. Manage your subscription, upgrade your plan, and track usage. Automate tents and contacts from your own code. ## Ready to transform your go-to-market? Join the teams using Tented to launch pages, campaigns, and automations in minutes instead of weeks. Sign up and launch your first campaign in under 15 minutes. **Using an AI assistant?** These docs are agent-friendly. Connect your tool to our MCP server at `https://docs.tented.ai/mcp` to search the docs directly, fetch [llms.txt](https://docs.tented.ai/llms.txt) for a machine-readable index (or [llms-full.txt](https://docs.tented.ai/llms-full.txt) for everything at once), and append `.md` to any page URL for raw markdown. # Connecting Microsoft Teams Source: https://docs.tented.ai/integrations/connecting-microsoft-teams Learn how to connect Microsoft Teams to your Tented workspace for automated form submission notifications. Tented integrates with Microsoft Teams to send real-time notifications when forms are submitted on your published tents. This guide walks you through connecting your Teams workspace to Tented. ## Prerequisites Before you begin, ensure you have: * **Admin or Owner access** to your Tented workspace * **Permission to install apps** in your Microsoft Teams workspace (or access to request app installation from your Teams admin) ## Step 1: Find Tented in the Teams App Store 1. Open **Microsoft Teams** (desktop or web app) 2. Click on **Apps** in the left sidebar 3. Search for **"Tented"** in the app search bar 4. Click on the **Tented** app to view its details ## Step 2: Add Tented to Your Team 1. Click the **Add** button on the Tented app page 2. Select **Add to a team** from the dropdown options 3. Choose the **team** where you want to receive notifications 4. Select a **channel** where the Tented bot will be added (this is typically your General channel or a dedicated notifications channel) 5. Click **Set up a bot** to complete the installation When you add Tented to a team via a standard channel, the app gains access to **all standard channels** in that team. You'll be able to send notifications to any standard channel without additional setup. ## Step 3: Complete the Connection via Deep Link After adding the app to your team, Tented will send a **welcome message** to the channel you selected during installation. 1. Look for the welcome message from **Tented** in your channel 2. Click the **Connect to Tented** button in the welcome message 3. You'll be redirected to Tented where you can select which **Tented workspace** to link with your Teams tenant 4. Confirm the connection Once connected, your Tented workspace is linked to your Microsoft Teams tenant. You can now set up automations to send notifications to any standard channel in the connected team. ## Step 4: Set Up a Teams Automation in Tented Now that your Teams workspace is connected, you can create automations to send notifications when forms are submitted. 1. In Tented, open the **Tent Details** page of the tent you want to add an automation to 2. On the **Automations** card, click **Add Automation** (or **Manage Automations**), then **Add Automation** in the window that opens 3. Under **Trigger**, select **Form Submission** and choose the **form** that should trigger the notification 4. Under **Action**, select **Send Microsoft Teams Message** 5. Select the **channel** where notifications should be sent from the dropdown list 6. Click **Save** to activate the automation The channel dropdown will show all standard channels available in your connected team. Simply select the channel where you want form submission notifications to appear. ## How It Works When a visitor submits a form on your published tent: 1. Tented receives the form submission 2. The automation triggers and sends a formatted message to your selected Teams channel 3. The message includes: * Form name and tent details * Submission timestamp * A link to the tent * All submitted form data ## Automatic Cleanup & Disconnection Tented automatically manages your Teams connection to ensure everything stays in sync: **If the Tented app is removed from your team**, Tented will automatically: * Disable all Teams automations for that workspace * Clean up the connection between Tented and your Teams tenant You'll need to re-add the app and complete the connection process again to restore Teams notifications. **If the app loses access to a specific channel** (for example, if the channel is deleted or converted to a private channel), Tented will automatically disable automations targeting that channel. You'll need to update your automation to use a different channel. ## Private Channels **Private channels require separate installation.** When you add Tented to a team, it only has access to standard channels. To send notifications to a private channel, you must add the Tented app directly to that private channel: 1. Open the private channel in Teams 2. Click the **+** button or go to channel settings 3. Select **Manage apps** or **Get more apps** 4. Search for and add **Tented** to the private channel Once installed, the private channel will appear in your channel selection dropdown in Tented. ## Troubleshooting ### I don't see any channels in the dropdown * Ensure you've completed the connection process by clicking the deep link in the welcome message * Verify the Tented app is still installed in your team * Check that your Tented workspace has an active Teams connection in workspace settings ### Notifications aren't being sent * Confirm the automation is **enabled** (toggle should be on) * Verify the tent is **published** * Check that the selected channel still exists and the app has access to it ### The welcome message didn't appear * Check the channel where you added the app—the message is sent there * Look in the channel's chat history; it may have been sent earlier * Try removing and re-adding the Tented app to the team ## Disconnecting Teams To disconnect Microsoft Teams from your Tented workspace: 1. Remove the **Tented** app from your team in Microsoft Teams 2. Tented will automatically detect the removal and clean up the connection Alternatively, you can manage your integrations in your **Workspace Settings**. *** Need help? Contact us at [support@tented.ai](mailto:support@tented.ai). Learn how to set up Slack notifications for form submissions. # Connecting Slack Source: https://docs.tented.ai/integrations/connecting-slack Learn how to connect Slack to your Tented workspace for automated form submission notifications Tented integrates with Slack to send real-time notifications when forms are submitted on your published tents. This guide walks you through connecting your Slack workspace to Tented and configuring a Slack automation. ## Prerequisites Before you begin, ensure you have: * **Admin access** to your Tented workspace * **Permission to install apps** in your Slack workspace (or access to request app installation from your Slack admin) ## Step 1: Connect Slack in Integrations Settings 1. In Tented, click on your **profile icon** in the bottom-left corner. 2. Select **Integrations** from the menu. 3. On the **Integrations** page, select **Install** on the **Slack** tile. A screenshot of the Integrations page with Slack and Microsoft Teams tiles, each with an Install button. 4. A Slack page opens in a new tab. Select the Slack workspace and channel the notifications should go to. 5. Review the permissions information and select **Allow** if you agree. A screenshot of the Slack permissions page with the Allow button. 6. A confirmation message that Slack is connected to Tented appears. Close the tab and return to Tented. You can also connect Slack when setting up an automation for a specific tent. If Slack is not yet connected to your Tented workspace, you will be prompted to connect it during the automation setup process. ## Step 2: Set Up a Slack Automation in Your Tent Now that Slack is connected to your Tented workspace, set up a [form automation](/working-with-tents/form-automations) to send notifications for form submissions. **Set Up Form Submission Notifications:** 1. From the **Tent Details** page of the tent you want to set up notifications for, select the **Add Automation** button. (If you already set up an automation, the button says **Manage Automations**.) 2. In the **Tent Automations** window, select **Add Automation**. 3. On the left, under **Trigger**, select **Form Submission**. 4. Under the trigger, select the form you want notifications for from the dropdown list. The list shows all forms in the tent. 5. On the right, under **Action**, select **Send Slack Message**. 6. Select the **Slack Channel** you want notifications sent to. 7. Select **Save** to finish. 8. Select **Close** to exit the **Tent Automations** window. A screenshot of the Tent Automations window showing the options for setting up form submission notifications in Slack. *** Need help? Contact us at [support@tented.ai](mailto:support@tented.ai). Learn how to set up email notifications for form submissions. # Receiving Form Submission Notifications by Email Source: https://docs.tented.ai/integrations/setting-up-email-notifications Get form submissions in your inbox — per submission, or as a scheduled digest Tented can email you when forms are submitted on your published tents. There are two flavors, both set up as [form automations](/working-with-tents/form-automations): an instant email for every submission, or a scheduled digest with your form data attached as a CSV. ## Instant Notifications (Per Submission) 1. From the **Tent Details** page, select **Add Automation** (or **Manage Automations** if you already have some). 2. In the **Tent Automations** window, select **Add Automation**. 3. On the left, under **Trigger**, select **Form Submission**, then pick the form to watch. 4. On the right, under **Action**, select **Send Email For Each Submission**. 5. Enter the **email address** that should receive the notifications. 6. Select **Save** to finish. A screenshot of a configured automation: Form Submission triggers Send Email For Each Submission to a chosen address. ## Scheduled Digest (Daily, Weekly, or Monthly) Scheduled notifications deliver your form data on a cadence — great for a Monday-morning leads review. Times follow the timezone associated with your Tented workspace. 1. From the **Tent Details** page, select **Add Automation** (or **Manage Automations**). 2. In the **Tent Automations** window, select **Add Automation**. 3. On the left, under **Trigger**, select **On a Schedule**. 4. Choose the **frequency** — **Daily**, **Weekly**, or **Monthly** — and the specific time (and day, if applicable). Monthly offers handy presets: first day, last day, or middle of the month. 5. On the right, under **Action**, select **Send Email With Form Data CSV** and enter the recipient address. 6. Select **Save** to finish. A screenshot of the On a Schedule trigger with Daily, Weekly, and Monthly frequency options. Saved automations are active immediately. You can pause or delete them anytime from the **Tent Automations** window, or see them all across tents under **Tents > Form Automations**. The full guide to triggers, actions, and managing automations. # Webhooks Source: https://docs.tented.ai/integrations/webhooks Send tent form submissions and flow events to your own endpoints ## Two places webhooks fire Tented can call your own HTTP endpoint in two situations — great for pushing leads into a CRM, kicking off a Zapier/Make scenario, or notifying a custom service: 1. **Form automations** — a **Send Webhook Request** action fires every time a [tent form](/working-with-tents/form-automations) is submitted. 2. **Triggered flows** — a **Send Webhook** step fires as a contact reaches that point in a [flow](/email/triggered-flows). Both let you choose the HTTP method (POST, PUT, or GET), optionally add **JWT authentication** (Tented stores your token securely and sends it as a bearer token), and add your own **custom fields** to the payload. ## Form-automation webhook payload When a form is submitted, Tented POSTs JSON like this to your endpoint: ```json theme={null} { "tentId": "45977a6f-87b6-48a4-8607-11727bee2020", "formId": "registerForm", "timestamp": "2026-07-02T20:10:00.000Z", "formData": { "name": "Justin Cooperman", "email": "justin.cooperman@tented.ai", "company": "Tented, Inc." } } ``` * `formData` holds the submitted fields, keyed by your form's field names. * Any **custom fields** you configure on the webhook are merged into the top level of the payload alongside these. ## Flow webhook payload A **Send Webhook** step in a flow sends a richer payload with the flow and contact context: ```json theme={null} { "event": "flow_step", "orgId": "…", "campaignId": "…", "membershipId": "…", "contactId": "…", "nodeId": "…", "nodeType": "send_webhook", "timestamp": "2026-07-02T20:10:00.000Z", "contact": { "contactId": "…", "email": "justin.cooperman@tented.ai", "displayName": "Justin Cooperman" } } ``` * If the flow was entered via a form submission, the form data travels along in the payload too. * **Custom fields** on the webhook node are rendered (with [contact tokens](/email/creating-emails#subject-lines-preheaders-and-personalization) like `{{contact.firstName}}`) and merged into the top level. ## Setting one up * **Form automation:** on a tent's **Tent Details** page, **Add Automation > Trigger: Form Submission > Action: Send Webhook Request**, then enter your endpoint URL, method, and any auth or custom fields. Full steps in [Form Automations](/working-with-tents/form-automations). * **Flow step:** in the [flow builder](/email/triggered-flows), add a **Send Webhook** step and configure it the same way. Testing? Point the webhook at a request-inspector service (like a RequestBin URL) first to see the exact payload your endpoint will receive, then swap in your real endpoint. # Standard & Custom Fields Source: https://docs.tented.ai/people/contact-fields Understand the contact fields Tented gives you — and add your own ## Where fields live Every contact in your People database is built from **fields**. Tented ships with a rich set of standard fields, and you can extend them with up to **200 custom fields** of your own. Manage them all in one place: **Workspace Settings > People > Manage Fields** (or **Settings > People**). From here you can also **Export** your full field schema. A screenshot of the Contact Fields page showing the custom fields usage bar, an empty custom fields section, and the standard fields list. ## Standard fields Standard fields are built in — they can't be renamed or removed, and every contact has them: A screenshot of the standard fields list including identity, address, source, lifecycle, and subscription fields with their API names and types. | Group | Fields | | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | | **Identity** | Email, Phone, First Name, Last Name, Display Name, Company, Job Title | | **Address** | Address, Address 2, City, State, Country, Zip Code | | **Enrichment & source** | Email Domain, Original Source, Original Source Detail | | **Lifecycle** | Lead Status, Lifecycle Stage, Tented Score | | **Email & SMS status** | Unsubscribed, Marketing Email Subscribed, Marketing SMS Subscribed, Marketing Email Invalid, Marketing SMS Invalid | | **Timestamps** | Last Activity, Created, Updated | Each field has an **API name** (like `firstName` or `lifecycleStage`) you'll use with the [API](/api-reference/managing-contacts), see in CSV exports, and reference in [email personalization tokens](/email/creating-emails#subject-lines-preheaders-and-personalization) — `{{contact.firstName}}`. The subscription fields (like **Unsubscribed**) are managed by Tented's email pipeline — they update automatically when contacts unsubscribe, and marketing sends respect them without any work on your part. ## Custom fields Anything your business cares about that isn't standard — plan tier, account owner, renewal date — becomes a custom field. Custom fields show up on contact profiles, as mapping targets during [CSV import](/people/importing-contacts), and in [audience rules](/people/working-with-lists) for filters and dynamic lists. To add one, click **Add field**: 1. Give it a **display name** — Tented auto-generates the API name (e.g., "Plan Tier" → `plan_tier`). 2. Pick a **type**: String, Email, Phone, Number, Boolean, Date, Date & time, or Select Dropdown. Email and Phone validate the value's format, and Date & time stores a full timestamp where Date is just a calendar day. 3. Decide if it's **editable in the Tented UI** — turn this off for fields that should only be written by imports or the API, and they'll show as read-only on contact profiles. The Editable toggle only locks manual edits: the [API](/api-reference/managing-contacts), CSV imports, and automations can always write the field, so integration-managed values stay safe from hand-edits without ever blocking the integration itself. Picking **Select Dropdown** adds an **Accepted values** editor. Each option pairs a **display name** (what Tented shows everywhere) with a lowercase **internal name** (what the field actually stores) — and API updates and CSV imports must use the internal name: `active_customer`, not "Active Customer". You can reorder options, give each a color, choose a default for newly created people, and edit the set later from the field's pencil icon. A screenshot of the Add custom field dialog with a display name of Plan Tier, an auto-generated API name, and the type dropdown showing String, Number, Boolean, and Date. Your custom fields appear at the top of the fields page, where you can rename them, toggle editability, or archive ones you no longer use. A screenshot of the custom fields list showing a Plan Tier field with its Editable toggle and edit and archive controls. You can also create custom fields on the fly during a [CSV import](/people/importing-contacts) — map an unmatched column to a new field without leaving the import flow. ## What's next? Put your fields to work with a bulk import. # Importing Contacts via CSV Source: https://docs.tented.ai/people/importing-contacts Bring your whole contact list into Tented with a CSV import ## Before you import Any CSV with a header row works. A typical file looks like this: ```csv theme={null} email,first_name,last_name,company,job_title diego.ramirez@northwindsupply.com,Diego,Ramirez,Northwind Supply,VP of Marketing priya.patel@brightlabs.co,Priya,Patel,Bright Labs,Demand Gen Manager ``` A few things that make imports smooth: * Every contact needs an **email or phone number** — rows without one can't be created. * Column names don't need to match Tented's field names exactly; you'll get a mapping step. * Columns can map to [custom fields](/people/contact-fields) too — and you can even create new custom fields during the import. ## Step 1: Upload your file Go to **People > Add > Import from CSV** and drag your file in (or click to browse). A screenshot of the Import CSV tab with a drag-and-drop area for the CSV file. ## Step 2: Map your columns Tented reads your header row and auto-maps each CSV column to a contact field. For each column you can: * **Change the target field** — any [standard or custom field](/people/contact-fields) * **Choose the overwrite behavior** — if a contact already exists (matched by email), decide whether the imported value should **overwrite** a field that's already filled or leave it alone A screenshot of the Configure Import screen showing CSV columns auto-mapped to Tented fields with per-column overwrite settings. Imports **update** existing contacts rather than creating duplicates — a row whose email matches an existing contact updates that contact according to your overwrite settings. ## Step 3: Start the import Click **Start Import** and watch rows process in real time. When it finishes you get a full report: * **Created / Updated / Skipped / Failed** counts, with a filterable row-by-row breakdown * A **reason** for every skipped or failed row * **Export CSV** to download the results for your records A screenshot of a completed import showing 6 rows created and a row results table with statuses. ## Reviewing past imports The **Past Imports** tab in the Add People dialog keeps a history of every import — handy when you're trying to work out where a contact came from. Each contact's [Activities timeline](/people/managing-contacts#viewing-a-contact) also records "Person Created — Source: List Import." ## What's next? Turn your imported contacts into targeted audiences. Add custom fields so your CSV columns have a proper home. # Managing Contacts Source: https://docs.tented.ai/people/managing-contacts Build and manage your contact database in the People area ## Meet the People area The **People** area is your contact database inside Tented. Every person you market to — leads from tent forms, imported lists, customers you add by hand — lives here, ready to be organized into lists and reached through [email blasts](/email/email-blasts) and [triggered flows](/email/triggered-flows). Click **People** in the sidebar to see everyone in your database. You can sort by any column, search by name or email, and customize which columns appear with the **Columns** button. A screenshot of the All People tab showing a table of contacts with Display Name, Email, Phone, Company, and Updated columns. Next to the total count you'll see a small **capacity ring** that fills as your database grows toward your plan's People allowance (1,000 contacts on Free; higher on Pro and Max tiers). Hover it for the exact percentage. The ring turns **yellow** from 90% and **red** once you're over the allowance — going over doesn't block imports or form submissions, but on the Free plan it does pause email blasts, and workspace admins are notified by email. ## Adding contacts Click the **Add** button in the top-right corner to see your options: A screenshot of the Add menu with options to Add Contact, Import from CSV, Create Dynamic List, and Create Static List. ### Quick Add Perfect for adding one person at a time. An email *or* phone number is required — everything else is optional: * **Email** and **Phone** * **First Name** and **Last Name** * **Company** and **Job Title** A screenshot of the Quick Add form filled in with a contact named Maya Chen from Acme Robotics. Click **Create** and the contact appears in your database instantly. ### Import from CSV Got a whole list? Choose **Import from CSV** and drag in your file. Tented reads your column headers, auto-maps them to contact fields, and gives you a row-by-row report when it's done. A screenshot of the Configure Import screen showing CSV columns auto-mapped to Tented fields with overwrite options. For the full walkthrough — column mapping, overwrite behavior, custom fields, and import history — see [Importing Contacts via CSV](/people/importing-contacts). ## Viewing a contact Click any contact's name to open their profile. The **Details** tab shows all their [standard and custom fields](/people/contact-fields) — name, email, company, job title, address, lead status, lifecycle stage, and their **Unsubscribed** status for email marketing. You'll also see which [lists](/people/working-with-lists) they belong to and any [triggered flows](/email/triggered-flows) they're enrolled in. A screenshot of a contact's profile showing standard fields like name, email, company, job title, and email domain. The **Activities** tab is a running timeline of everything that's happened with this contact — when they were created, which fields were updated, forms they submitted, and lists they joined or left. A screenshot of a contact's Activities tab showing a Person Created event with its source and filled fields. ## Filtering people Click **Filters** to slice your database with audience rules. Rules can match on: * **Contact property** — any standard field, like *Job Title contains "Marketing"* * **Activity** — things that happened, like *Form Submitted in the last 7 days* * **List membership** — whether someone is (or isn't) on a specific list Combine multiple rules and groups to get as specific as you need. A screenshot of the Filter People dialog showing the audience rule builder. These are the same audience rules used to build [dynamic lists](/people/working-with-lists#dynamic-lists) — so if you find yourself applying the same filter over and over, save it as a dynamic list instead. ## Bulk actions Select one or more contacts with the checkboxes and the **People Actions** menu lights up. From there you can: * **Edit** — bulk-update a field across every selected contact (see below) * **Add to List** — drop them into an existing static list or create a new one on the spot * **Export as CSV** * **Delete** contacts you no longer need A screenshot of the People Actions menu with Edit, Add to List, Export as CSV, and Delete options. ### Bulk-editing a field Select your contacts, choose **People Actions > Edit**, pick a field (any [standard or custom field](/people/contact-fields)), enter a new value, and apply it to everyone in the selection at once. Perfect for setting a lifecycle stage or lead status on a whole segment. A screenshot of the Edit dialog for 8 people with a field picker to choose which field to update. ## What's next? Organize your contacts into static and dynamic lists — the audiences behind your campaigns. # Working with Lists Source: https://docs.tented.ai/people/working-with-lists Organize contacts into static and dynamic lists to power your campaigns ## Why lists? Lists are how you turn a big database of people into targeted audiences. Every [email blast](/email/email-blasts) sends to a list, and [triggered flows](/email/triggered-flows) can enroll contacts the moment they join one — so getting comfortable with lists pays off everywhere else in Tented. Head to **People > Lists** to see them all, along with each list's type, member count, and when it was last updated. A screenshot of the Lists tab showing a Dynamic list called Marketing Leaders and a Static list called VIP Customers. There are two kinds of lists, and picking the right one is the whole game: A fixed roster you manage by hand. Members stay put until you add or remove them. Great for hand-picked groups like VIP customers, event invitees, or a test audience. A saved set of audience rules. Membership updates automatically as contacts change — anyone who matches is in, anyone who stops matching is out. Great for segments like "everyone with a marketing job title." ## Creating a static list 1. Click **Add > Create Static List**. 2. Give it a name and an optional description. 3. Click **Create**. A screenshot of the Create Static List dialog with a name and description filled in. Your new list starts empty. To fill it, go to the **All People** tab, select some contacts, and choose **People Actions > Add to List**. Pick an existing list — or spin up a new one right from the dialog. A screenshot of the Add to List dialog with tabs for choosing an existing list or creating a new one. To remove people later, open the list, select them, and use **People Actions > Remove from List**. ## Creating a dynamic list 1. Click **Add > Create Dynamic List**. 2. Name it, then build your **audience rules** — the same rule builder you know from [filtering people](/people/managing-contacts#filtering-people). Match on contact properties, activities, or membership in other lists. 3. Click **Create**. A screenshot of the Create Dynamic List dialog with a rule matching contacts whose Job Title contains Marketing. That's it — Tented finds everyone who matches right away, and keeps the list in sync as contacts are created, updated, or deleted. Here's our "Job Title contains Marketing" list, automatically pulling in three matching contacts: A screenshot of a dynamic list's members, showing three contacts with marketing job titles that matched the rule automatically. You can't manually add or remove members from a dynamic list — the rules decide. If you need hand-picked control, use a static list. Use **Edit List** on a dynamic list to change its rules at any time. ## Managing lists From a list's page you can: * **Search** and sort its members * **Export List** to CSV * **Edit List** (dynamic lists) to tweak the rules * Run **People Actions** on selected members, just like in All People ## Static vs. dynamic: a quick cheat sheet | You want to... | Use | | -------------------------------------------------- | ------------------------------------------ | | Hand-pick a test audience for a blast | Static | | Target everyone at companies in a certain industry | Dynamic | | Trigger a welcome flow when someone is added | Static (flows trigger on static list adds) | | Keep a segment fresh without maintenance | Dynamic | ## What's next? Verify your sending domain and sender details so your lists have somewhere to go. # Best Practices for AI Prompting Source: https://docs.tented.ai/working-with-tents/best-practices-prompting Write effective prompts for better AI-generated results ## The Art of AI Prompting Writing effective prompts is crucial for getting the best results from Tented's AI. A well-crafted prompt can mean the difference between a generic landing page and a perfectly tailored, conversion-optimized page. ## Prompt Structure The most effective way to communicate with the AI and achieve great results can be summarized by this formula: **Context + Goal + Details + Constraints**. This means your prompt should clearly establish the **Context** (who you are and what you do), define the **Goal** (what you want to achieve with the page), specify the **Details** (the specific features and content required), and outline any necessary **Constraints** (technical or design limitations). Using all four elements ensures the AI has the full scope necessary to deliver a high-quality tent. ### Example Comparison **Poor Prompt:** "Create a landing page" **Good Prompt:** "Create a landing page for a B2B SaaS project management tool targeting remote teams of 10-50 people. Include a hero section with clear value proposition, key features highlighting collaboration and productivity, pricing tiers for small teams and enterprises, customer testimonials, and a contact form. Use a professional blue and white color scheme with modern, clean design." A screenshot of an example prompt, the AI's response, and the resulting tent generation. ## Context Setting To ensure the AI generates a highly relevant and effective landing page, you must first provide detailed context setting. This initial step involves defining the core environment of the page, specifically detailing your industry and business type and clarifying your target audience. Providing this foundational information ensures the AI understands not only what you sell but also who you're selling to and why they need your solution. ### Industry and Business Type Providing precise information about the nature of your business helps the AI select appropriate language, features, and design elements specific to your sector. **Be Specific About Your Industry:** * "B2B SaaS productivity tool" vs. "software" * "Online fitness coaching service" vs. "fitness" * "AI-powered mortgage rate comparison tool" vs "mortgage software" **Include Your Business Model:** * Subscription-based service * One-time purchase product * Service-based business * E-commerce store ### Target Audience Accurately identifying your ideal user is paramount, as it dictates the tone, messaging, and design of your tent. By focusing on your target audience, you enable the AI to craft content that directly addresses the needs and challenges of the people you want to reach. **Define Your Ideal Customer:** * "Busy professionals aged 25-45" * "Small business owners with 5-20 employees" * "Tech-savvy millennials in urban areas" * "Parents looking for family-friendly services" **Include Pain Points:** * "Teams struggling with remote collaboration" * "Businesses overwhelmed by manual processes" * "Consumers seeking convenience and speed" ## Goal Definition After setting the context of your business and audience, the next crucial step is goal definition. Specifying your goals clarifies the desired outcome of the landing page, allowing the AI to prioritize elements that drive user action. ### Primary Objectives **Conversion Goals:** * Lead generation through contact forms * Product sales and purchases * Newsletter signups * Demo requests or consultations **Brand Goals:** * Establish credibility and trust * Showcase expertise and experience * Build brand awareness * Drive traffic to other channels ### Success Metrics **What Success Looks Like:** * "High conversion rate for demo requests" * "Clear path from visitor to customer" * "Mobile-optimized for on-the-go users" * "Professional appearance for enterprise clients" ## Content Specifications Content specifications dictate exactly what information needs to be present on the tent, ensuring all necessary selling points, features, and evidence are included. This involves detailing the essential sections and outlining the specific type of social proof required to build trust and drive conversions. ### Common Sections **Hero Section:** The hero section should include a compelling headline and subheadline, a clear value proposition, the primary call-to-action, and a suitable hero image or video. **Features/Benefits:** For the main body, the features/benefits section must clearly articulate the key product features, corresponding customer benefits, and any unique selling propositions. **Social Proof:** To build trust, the Social Proof elements should consist of customer testimonials, case studies or success stories, logos of well-known clients, and user reviews or ratings. ### Content Tone and Style Defining the content tone and style ensures the AI's language aligns with your brand identity and communication strategy. This involves selecting a distinct brand personality, which could be professional and corporate, friendly and approachable, innovative and cutting-edge, or trustworthy and established. You should also specify the communication style. Is the content meant to be technical and detailed, simple and accessible, urgent and action-oriented to drive immediate action, or primarily educational and informative? Clarifying both elements helps the AI write copy that resonates perfectly with your target audience. You can specify your communication style in the **Brand** area of your [Workspace Settings](/configuring-tented/workspace-settings). ## Design Preferences To ensure the final tent is visually appealing and aligns with your brand identity, communicate your design preferences to the AI. This involves specifying the desired visual style and detailing your color preferences. ### Visual Style For visual style, you should choose an aesthetic that matches your brand, such as modern and minimalist, bold and attention-grabbing, elegant and sophisticated, or playful and creative. Regarding color preferences, be precise by listing brand colors (specifying hex codes), noting any requirements for industry-appropriate colors or specific psychological color choices, and ensuring all accessibility considerations are met. ### Layout Preferences Communicating your layout preferences is essential for guiding the AI on the structural organization of your tent, which impacts user experience and visual hierarchy. You should specify whether you prefer a traditional or unconventional layout. Also tell the AI whether the design priority is mobile-first or desktop-first. Consider defining the content flow, or how the narrative unfolds on the page. Choose between a linear storytelling approach, a problem-solution format, a feature-benefit progression, or a design that prioritizes immediate social proof integration. ## Technical Requirements Outlining the technical requirements ensures the generated tent is fully functional and connects seamlessly with your existing technology stack. Detail your functionality needs, such as including specific interactive elements like contact forms with specific fields, newsletter signup integration, social media links, and live chat or support options. Additionally, specify all required integration requirements. These requirements might include connections to your CRM system, email marketing platforms, analytics tracking, and any necessary payment processing capabilities. ## Common Prompt Patterns Here are some common prompt patterns you can use as templates for different types of landing pages. Customize the placeholders to fit your specific needs. ### E-commerce Landing Pages **Template:** "Create a landing page for \[product type] targeting \[audience]. Include hero section with product showcase, key benefits, customer reviews, pricing options, and secure checkout. Use \[brand colors] with \[design style]." **Example:** "Create a landing page for premium wireless headphones targeting audiophiles and music professionals. Include hero section with product imagery, sound quality benefits, professional reviews, pricing tiers, and secure checkout. Use black and gold colors with sleek, modern design." ### SaaS Product Pages **Template:** "Create a landing page for \[SaaS product] targeting \[business type]. Include hero with value proposition, key features, pricing plans, customer testimonials, and free trial signup. Use \[colors] with professional, trustworthy design." **Example:** "Create a landing page for a team collaboration platform targeting remote teams of 5-50 people. Include hero with productivity benefits, key features like video calls and file sharing, pricing for small teams and enterprises, customer success stories, and free trial signup. Use blue and white colors with clean, professional design." ### Service Business Pages **Template:** "Create a landing page for \[service type] targeting \[location/audience]. Include hero with service overview, process explanation, portfolio examples, testimonials, and contact form. Use \[brand colors] with \[personality] design." **Example:** "Create a landing page for a local web design agency targeting small businesses in the Denver area. Include hero with service overview, design process explanation, portfolio of recent projects, client testimonials, and consultation booking form. Use green and white colors with creative, professional design." ## Advanced Prompting Techniques When creating complex or highly customized tents, you can move beyond simple, one-shot requests. The following methods involve strategically structuring your commands to achieve precise results, incorporating external design influences, and applying strict rules. ### Iterative Refinement **Start Broad, Then Specific:** 1. "Create a landing page for a fitness app" 2. "Make it target busy professionals who want quick workouts" 3. "Add a 7-day free trial and highlight mobile convenience" 4. "Include success stories from users who lost weight" ### Visual References **Include Design Inspiration:** * "Use a design similar to Apple's product pages" * "Make it look like Stripe's landing page but for fitness" * "Apply Material Design principles with our brand colors" ### Constraint-Based Prompting **Set Clear Boundaries:** * "Keep the page under 5 sections" * "Use only our brand colors: #2563eb and #ffffff" * "Include exactly 3 pricing tiers" * "Make it mobile-first responsive" Learn how to effectively preview and test your tents before publishing. # Creating Tents Source: https://docs.tented.ai/working-with-tents/creating-tents Learn how to create AI-generated landing pages with your brand assets ## Creating Your First Tent Creating a tent in Tented is as simple as describing what you want in natural language. Our AI will generate a complete, modern landing page based on your description. ## The Creation Process ### 1. Set Your Branding Before you create your tent, customize your brand identity. You're prompted to do this when you first sign up. Here's the information you'll need to provide: * **Primary Brand Color**: Your main brand color (used for buttons) * **Brand Logo**: The logo that represents your brand * **Brand Icon**: A smaller icon version of your logo Logos should have a 3:1 aspect ratio and a transparent background. They should be saved as a PNG or SVG file for best results. Icons should have a 1:1 (square) aspect ratio. They should also be saved as a transparent PNG or SVG file. After you sign up for Tented, you'll see a prompt like this: A screenshot of the prompt for adding brand assets. It appears after you first sign up for Tented. 1. Set your primary brand color by entering a hex code or using the color picker. 2. Upload your logo and icon. 3. Click **Save** to finish. Or, you can skip this step and add your branding later in your [Workspace Settings](/configuring-tented/workspace-settings). ### 2. Write a Prompt Navigate to the **Home** page in your dashboard. In the prompt interface, describe your landing page. **Good prompts include:** * Your industry or business type * Target audience * Key features or benefits to highlight * Desired page sections (hero, features, pricing, contact, etc.) * Brand personality (professional, playful, modern, etc.) **Example prompts:** * "Create a landing page for a SaaS productivity tool targeting busy professionals, with a hero section, key features, pricing tiers, and a contact form" * "Build a professional landing page for a 'Predictive Scheduling' feature with a sign-up form to capture early access leads" For more information, see our [Best Practices for AI Prompting](/working-with-tents/best-practices-prompting) guide. A screenshot of a detailed prompt for an early-access signup page for a new feature called "Predictive Scheduling" ### 3. Generate Your Tent Once you've set your branding and written your prompt: 1. **Click the send icon** to start the AI creation process. 2. **Wait for generation** (typically 60-70 seconds). 3. **Preview** your new tent. For details, see [Previewing Tents](/working-with-tents/previewing-tents). A screenshot of a generated early-access signup page for a "Predictive Scheduling" feature. ### 4. Refine and Iterate Refine your tent through natural conversation in the chat interface. Request changes like: * "Add a testimonials section" * "Make the call-to-action button larger" * "Add a sign-up form at the bottom" ### 5. Add Images and files Consider enhancing your tent with images and downloadable content like brochures, reports, or white papers. There are two ways to add an image or file. **From the chat input area:** 1. In the chat input area, click the **plus button > Add Images or Files**. 2. Select the image or file from your computer. 3. A thumbnail for the file appears above the chat input area. Reference it in your prompt. For example, "Add this AI scheduling graphic centered at the bottom of the hero section, below the stats." 4. Click the **send icon** to submit your prompt with the image or file reference. A screenshot of a prompt to upload and insert an image with the final result. **Through the Manage Assets option:** 1. In the tent editor, click the **image icon** in the top menu. 2. In the **Tent Assets** window that appears, click **Add File**. 3. Select the image or file from your computer. 4. The file is added to the **Tent Assets** list. You can now reference it in your prompts. For example, "Add this brochure download link below the pricing section." A screenshot of the Tent Assets window with an uploaded PDF file. Don't want to start from a prompt? Create from a template instead: **Tents > Add > Create Tent** opens a gallery of [Tented example templates](#tented-example-templates), every one already rendered in your branding. And when you've made something you'll want to reuse, save it as a [tent template](/working-with-tents/tent-templates) so future tents can start from the same design. ### 6. Publish Your Tent To make your tent live on the web, follow the steps in [Publishing Tents](/working-with-tents/publishing-tents) — you can customize the URL, add password protection, and remove the Tented badge along the way. Your tent is now accessible worldwide! A screenshot of the confirmation that a tent was published, showing the live URL. ## Tented Example Templates Prompting from scratch isn't the only way in. Every workspace includes the **Tented example template collection** for tents: dozens of professionally designed landing pages covering product launches, event pages, webinars, ABM plays, lead magnets, demo signups, waitlists, and careers pages. The collection **automatically enables your branding**. Every design in the gallery renders with the logo, brand color, and company details from your [workspace settings](/configuring-tented/workspace-settings), so the previews already look like your pages, not someone else's. To create a tent from the gallery: 1. Go to **Tents** in the sidebar and click **Add > Create Tent**. 2. The **Choose Tent Template** gallery opens. Browse by category (Product Launches, Event Pages, Webinars & Workshops, ABM & Target Accounts, and more), or search by name. Click **Preview** on any card to see the full page. 3. Click a template to select it. 4. Name your tent and click **Create Tent**. A screenshot of the Choose Tent Template gallery showing category filters, search, and rows of example landing page templates all rendered with the workspace's branding. You can also browse the collection any time under **Tents > Templates**, in the **Tented Starter Templates** row. In a hurry? Hover any card there and click **Use**: Tented creates the tent immediately, named after the template, and drops you straight into the editor. A screenshot of the Tent Templates tab showing the Tented Starter Templates row rendered in the workspace's Vibes Co. branding, with Preview and Use buttons on the hovered card. Either way, the new tent opens in the editor with the template's full design in place, rendered in your branding, and a chat pill recording which template it started from. From here it behaves like any other tent: iterate in chat, then preview and publish as usual. A screenshot of the tent editor right after creating a tent from the Flagship Launch example template, with a "Tent created from template" pill in the chat and the branded landing page in the preview. Creating a tent from an example template spends no generation credits. Credits are only used when you ask the AI to generate or edit content. The same gallery has a **My Templates** section listing the [tent templates](/working-with-tents/tent-templates) your team has saved and approved, right next to Tented's examples. ## Troubleshooting ### Common Issues Here are some common issues you might encounter while working with Tented's AI, along with suggested solutions. **Preview Not Updating:** If you notice the preview isn't updating as expected, a few steps can resolve this: first, try refreshing the preview panel. If that doesn't work, check for JavaScript errors in the console, and finally, carefully verify your code syntax for any mistakes that might be preventing the update. **Changes Not Applied:** If you find that changes you requested are not being applied correctly, you should ensure your instructions are clear and specific to the AI. It's also helpful to check the chat history for any errors or confusing previous inputs, or simply try rephrasing your request to see if a different approach yields the correct result. **Layout Issues:** For any layout problems, you should begin by testing the output on different screen sizes to identify the scope of the issue. You should then check your CSS for conflicts that might be causing unexpected rendering. Use browser developer tools for detailed inspection and debugging of the layout elements. ## What Happens Next? After creating your tent, you might want to [edit and iterate](/working-with-tents/editing-tents) to perfect it. You can also track form submissions and analytics on the [Tent Details](/working-with-tents/viewing-tent-details) page. Learn how to navigate and understand the **Tent Details** page. # Editing Tents Source: https://docs.tented.ai/working-with-tents/editing-tents Advanced techniques for editing and iterating on your tents ## Editing Workflow Editing tents is designed to be intuitive and conversational. You can make changes through natural language, see updates in real-time, and iterate quickly to perfect your landing page. To edit an existing tent, open it in the tent editor: 1. From the **Tents** page in your workspace, click on the tent you want to edit to open the **Tent Details** page. 2. Select **Edit** at the top right to open the tent editor. 3. In the chat input area, describe the changes you want to make. Be as specific as possible. 4. Click the send icon to submit your request. Simple edits take a few seconds; bigger changes can take up to a minute. The following sections outline different types of edits you can make, advanced techniques for refining your tent, and best practices for using the chat interface effectively. ## Types of Edits ### Content Edits When generating a landing page with AI, you often need to refine the generated copy to better match your brand, optimize for conversions, or reflect current offers. **Text Changes:** * "Change the headline to 'Transform Your Business Today'" * "Update the pricing from $99 to $79 per month" * "Add 'Free 30-day trial' to the call-to-action button" **Content Additions:** * "Add a features section with 3 key benefits" * "Include customer testimonials below the pricing" * "Add a FAQ section at the bottom" **Content Removal:** * "Remove the testimonials section" * "Delete the pricing table" * "Take out the social media links" ### Design Changes You might want to adjust the visual presentation to ensure it aligns with your design system and provides an excellent user experience. These commands focus on making high-level Layout Modifications, specifying Color and Styling changes, and refining the Typography Updates across the page. **Layout Modifications:** * "Make the hero section full-screen height" * "Center all content on the page" * "Add more white space between sections" **Color and Styling:** * "Change the primary color to #2563eb" * "Make the background gradient from blue to purple" * "Update all buttons to use rounded corners" **Typography Updates:** * "Increase the headline font size to 48px" * "Change the body font to Inter" * "Make the subheadings bold" ### Functional Changes Beyond static content and visual design, the AI can also handle the implementation of complex logic and interactivity. Functional changes may involve modifying existing interactive elements, updating forms to capture specific data, and enhancing site navigation to improve user flow and data collection capabilities. **Form Modifications:** * "Add a phone number field to the contact form" * "Change the form submit button text to 'Get Started'" * "Add form validation for email addresses" A screenshot of the result when prompting to add email validation to a form. **Interactive Elements:** * "Add a smooth scroll to the pricing section" * "Include hover effects on all buttons" * "Add a loading animation to the form" **Navigation Updates:** * "Add a sticky navigation bar" * "Include anchor links to each section" * "Add a back-to-top button" ## Advanced Editing Techniques The following strategies help you manage complexity, resolve unexpected behaviors, and ensure the AI builds exactly what you intend, especially for larger, multi-step projects. ### Iterative Refinement Consider guiding the AI through a series of small, focused commands rather than one large, complicated request. This systematic approach is critical for maintaining control and ensuring the quality of the output. When you **build incrementally**, the key is to make one change at a time, test each change before proceeding, and only then build complexity gradually. ### Visual References To ensure the AI produces designs that meet your aesthetic standards, you can provide visual references either by uploading files or by describing specific design styles. This is extremely helpful for guiding the AI on branding, layout, and overall look and feel. These references might be screenshots of designs you like, your official brand guidelines or style guides, or even visuals from reference websites or competitors. Alternatively, you can describe visual elements to the AI using specific instructions like, "Make it look like the Apple website," "Use a design similar to Stripe's landing page," or "Add shadows like in Material Design." A screenshot of a prompt to make the page design similar to the Adobe Photoshop landing page, along with the result. ### Context-Aware Editing Context-Aware Editing leverages the AI's understanding of previously generated elements, allowing you to establish and maintain a cohesive design system by referencing existing components and styles across the page. Here are some example prompts: * "Make the pricing buttons match the hero button style" * "Use the same font as the headline for subheadings" * "Apply the same color scheme throughout" ## Troubleshooting Edits ### When Changes Don't Apply If you encounter issues where the AI's output isn't what you expected, the first step is to check your request. To get better results, always ensure your instructions are clear and specific, and provide sufficient context about the desired outcome. If the initial request fails, try to rephrase your request using different language, or use more specific terminology to clarify your intent to the AI. Next, ask the AI to verify its work. Telling it to THINK HARD adds reasoning to its workflow. Here are some examples: * "Please review the last change and ensure it was applied correctly" * "Please double-check the layout adjustments I requested" * "Please THINK HARD and remove the predictive-scheduling dashboard image from the hero section; I still see it even after you recent attempt to remove it" Learn how to revert to previous versions of your tent. # Form Automations Source: https://docs.tented.ai/working-with-tents/form-automations Get notified — or trigger downstream systems — whenever your tent forms are submitted ## What are form automations? Every published tent can capture form submissions. **Form automations** make sure those submissions go somewhere useful the moment they arrive — a Slack channel, a Teams channel, your inbox, or any webhook endpoint — plus scheduled digest emails of your form data. You can see every automation across all your tents in one place: **Tents > Form Automations**. A screenshot of the Form Automations tab listing an automation with its tent, trigger, action, and Active status. ## Adding an automation Automations are set up per tent: 1. Open your tent's details page (**Tents >** click the tent name). 2. On the **Automations** card, click **Add Automation** (or **Manage Automations** if you already have some — it's also in the **Tent Actions** menu). 3. In the **Tent Automations** window, click **Add Automation**. You'll get a simple two-part builder: pick a **Trigger** (when it runs) and an **Action** (what happens). A screenshot of the Tent Automations builder with a Trigger dropdown on the left and an Action dropdown on the right. ## Trigger: Form Submission Fires immediately, every time someone submits a form. Choose which of the tent's forms to watch, then pick an action: A screenshot of the action dropdown showing Send Slack Message, Send Microsoft Teams Message, Send Email For Each Submission, and Send Webhook Request. * **Send Slack Message** — post to a public or private channel (requires the [Slack connector](/integrations/connecting-slack)) * **Send Microsoft Teams Message** — same idea, for Teams (see [Connecting Microsoft Teams](/integrations/connecting-microsoft-teams)) * **Send Email For Each Submission** — an email notification to any address, per submission * **Send Webhook Request** — an HTTP request to your endpoint, for CRMs and custom pipelines Here's an email notification automation ready to save: A screenshot of a configured automation: Form Submission on registerForm sends an email notification to a chosen address. ## Trigger: On a Schedule Prefer a digest? The **On a Schedule** trigger sends recurring summaries instead of one-at-a-time pings: * **Frequency**: Daily, Weekly, or Monthly (with handy presets like first/middle/last day of the month), at the time you pick — in your workspace's timezone. * **Action**: **Send Email With Form Data CSV** — a scheduled email with your form data attached as a CSV. A screenshot of the On a Schedule trigger showing Daily, Weekly, and Monthly frequency options. ## Managing automations Click **Save** and your automation is live — no separate activation step. From the Tent Automations window you can: * **Toggle** any automation between Active and paused * **Delete** automations you no longer need * See the count of total and active automations at a glance A screenshot of a saved automation with its Active toggle and delete control. Want submissions to do more than notify? Tent forms can also flow into your [People database](/people/managing-contacts), where a **Form Submitted** trigger can enroll contacts into [triggered email flows](/email/triggered-flows) — a full lead-capture-to-nurture pipeline. ## What's next? See, search, and export the form data your tents collect. # Generating Images with AI Source: https://docs.tented.ai/working-with-tents/generating-images Ask Tented to create custom photos and graphics for your tents and emails — no stock library required ## Tented makes images, too The same chat that writes your copy and codes your pages can **generate original images**: hero photos, product shots, textures, illustrations — whatever your page or email needs. Just describe the image in your prompt, the way you'd describe anything else: > Add a hero image of an oceanfront cabin at golden hour, and give the dining section a photo of a coastal dinner spread. Tented's image agent generates each image, saves it to your assets, and weaves it into the design. It works everywhere you chat with Tented: the **tent editor**, the **email editor**, and both **template** editors. Be as specific with images as you are with copy — subject, mood, lighting, and style all help. "A warm, editorial photo of fresh sourdough on a rustic wooden counter, morning light" beats "a bread photo." ## What it costs Image generation uses AI credits, separate from the code generation that places the images: * **0.5 credits per image** * Up to **5 images per request** A single image generates right away. Ask for **two or more** and Tented pauses to confirm the cost first: A screenshot of the Image Agent confirmation card in the editor chat, quoting the number of images and total credit cost, with Yes, generate and Cancel buttons. Click **Yes, generate** to proceed, or **Cancel** to rewrite your request — canceling costs nothing. If you ask for more than five images, Tented offers to generate the first five; you can always request the rest in a follow-up message. ## Where the images go Generated images appear right in the chat as they finish, and the follow-up generation codes them into your design: A screenshot of the editor chat showing the Image Agent's generated-images message with three thumbnails, the follow-up generation describing where each image was placed, and the page hero using the new cabin photo. Every generated image is also saved to the tent's **assets** (the image icon in the top bar), with a descriptive filename — so you can reuse or download it later, even if you change the design: A screenshot of the Tent Assets panel listing the three generated images with descriptive filenames and hosted URLs. You can also generate images *without* touching the design — handy for building up an asset library: > Generate a photo of a seaside spa treatment room and save it to my assets. Don't change the page. ## Generated vs. uploaded images Two ways to get images into your work, and they play well together: * **Generate** when you need something custom that doesn't exist yet — Tented creates it from your description. * **Upload** (the **+** button in the chat, or the assets window) when you have the real thing — your product photos, your logo, your team headshots. Reference either kind in your prompts: "use the photo I just uploaded as the hero background" works exactly like "use the second image you generated." Image generations are tracked in [Analytics](/configuring-tented/analytics) alongside initial and iterative generations, so you can see how your credits are being spent. ## Tips for better images * **One subject per image.** Five focused requests beat one crowded collage prompt. * **Name the style.** "Editorial photography," "flat illustration," "watercolor," "3D render" — the agent follows style cues closely. * **Match your brand.** Mention your palette or mood ("warm neutrals, soft morning light") so generated images sit naturally next to your brand colors. * **Iterate like everything else.** Don't like a result? Ask for a variation: "regenerate the hero image, but at dusk and with no people." Sharpen the prompts behind your pages, emails, and images. # Managing Tents Source: https://docs.tented.ai/working-with-tents/managing-tents Rename, delete, clone, and manage your tents ## Tent Management Overview Managing your tents effectively is crucial for maintaining an organized workspace and optimizing your landing page campaigns. Tented provides tools for all aspects of tent lifecycle management. You can find all of your tents in the **Tents** page of your workspace, alongside two sibling tabs: **[Templates](/working-with-tents/tent-templates)** (reusable starting points you create via **Tent Actions > Save as New Template**) and **Form Automations** (every [automation](/working-with-tents/form-automations) across your tents, in one view). The tent list includes each tent's status, last updated date, and creation date. If you have many tents, you can use the search and filter options to quickly find the tent you need. * Sort by: **Tent Name**, **Last Updated**, and **Created Date** * Filter by status: **Any**, **Draft**, **Published** A screenshot of the Tents page. ## Basic Tent Operations ### Renaming Tents You can change the autogenerated name for a tent to better reflect its purpose or campaign association. This option is available from multiple locations within the Tented interface. **From the tent editor or Tent Details page:** 1. Double-click on the tent name to make it editable. 2. Type the new name. 3. Press **Enter/Return** or click outside the field to save your change. A screenshot of the Tent Details page with the editable tent name highlighted. You can also Click **Tent Actions > Rename** at the top right of the **Tent Details** page. **From the tent list:** 1. Navigate to the **Tents** page. 2. Select the checkbox next to the tent name. 3. Click **Tent Actions > Rename** at the top right. 4. In the **Rename Tent** window that opens, type the new name and click **Save**. A screenshot of the Tents page with the Rename menu item highlighted. ### Deleting Tents If you no longer want to keep a tent in your account, you can delete any tent regardless of its status. Keep in mind that if you've shared the live URL of a published tent, that it will no longer be available on the internet when you delete it. If you're ready to delete, follow these steps. Deleting a tent is permanent and cannot be undone. All associated data, including form submissions and analytics, will be lost forever. Be sure to export any important data before proceeding. Consider using the [clone feature](#cloning-tents) for backups. **From the Tent Details page:** 1. Click **Tent Actions > Delete** at the top right. 2. Confirm the deletion in the dialog box. **From the tent list:** 1. Navigate to the **Tents** page. 2. Select the checkbox next to the tent name. 3. Click **Tent Actions > Delete** at the top right. 4. Confirm the deletion in the dialog box. **What Gets Deleted:** * Tent code and content * Generation history * Form submission data * Analytics data * Published URL (becomes inactive) ### Cloning Tents You can clone a tent to create variations of your landing page for A/B testing, or for other reasons. Here are some other use cases for cloning tents: * Creating variations for different audiences * Seasonal campaign updates * Regional or language variations Note that form submission data, analytics, and the published URL are not cloned. The cloned tent will have a status of **Draft** and will not be live until you [publish](/working-with-tents/publishing-tents) it. **From the Tent Details page:** 1. Click **Tent Actions > Clone** at the top right. 2. In the **Clone Tent** window, enter a name for the new tent and click **Clone**. The tent is cloned and appears in your tent list. It will have a status of **Draft**. A screenshot of the Tent Details page with the Clone Tent window open. **From the tent list:** 1. Navigate to the **Tents** page. 2. Select the checkbox next to the tent name. 3. Click **Tent Actions > Clone** at the top right. 4. Confirm the action in the dialog box. The tent is cloned and appears in your tent list. It will have a status of **Draft**. ## Publishing Management ### Unpublishing a Tent A tent can be unpublished at any time so that it's no longer available on the web. **From the Tent Details page:** 1. Select **Tent Actions > Unpublish** at the top right. 2. Confirm that you want to unpublish the tent. The tent becomes private. The URL is inactive and the tent status returns to **Draft**. You can [republish](/working-with-tents/publishing-tents) the tent later if needed. ## Data Management ### Viewing and Exporting Tent Data For information on form submission data and analytics, see: * [Viewing Form Submissions](/working-with-tents/viewing-form-submissions) * [Viewing Current Usage](/billing-subscriptions/viewing-usage) Learn how to configure workspace settings and preferences. # Previewing Tents Source: https://docs.tented.ai/working-with-tents/previewing-tents Effectively preview and test your tents before publishing ## Preview Overview Previewing your tent is a crucial step before publishing. Tented's preview system allows you to test your landing page across different devices, verify functionality, and ensure everything works perfectly before going live. ## How to Preview Your Tent on the Web There are two ways to preview how a tent will look on the web: * Open the tent in the tent editor and click **Preview in New Tab** at the top of the screen. A screenshot of the tent editor with the Preview in New Tab button highlighted. * Open the tent details page, hover over the preview tile, and click the **Open Preview** button that appears. A screenshot of the tent details page with the Preview in New Tab button highlighted. ## How to Preview Your Tent on Mobile Devices To preview how a tent will look on a mobile device: 1. In the tent editor, click the **mobile preview icon**. 2. The mobile preview defaults to the **iPhone 17 Pro Max**. To switch to a different device, select the drop-down arrow next to the device name. You can also specify exact screen dimensions by entering them in the width and height boxes in the top-center of the screen. 3. When you're done previewing on mobile, click the **mobile preview icon** again to return to the standard tent editor view. A screenshot of a mobile preview for a landing page about a feature called "Predictive Scheduling". The mobile preview icon and device switcher are highlighted. ## Interactive Testing Before publishing your tent, it's crucial to test all interactive elements to ensure they function correctly. Submit test data to all forms and verify form validation works correctly for fields like email and phone numbers. Make sure to click through all links and buttons on the page. You should also test smooth scrolling to sections and verify anchor links work properly. Check external link behavior to confirm they open in a new tab if necessary. Finally, for interactive elements, rigorously test hover effects and animations for visual appeal. Crucially, verify mobile menu functionality on smaller screens, check dropdown menus and modals to ensure they open and close as intended, and thoroughly test all javascript interactions that control dynamic behavior. Learn how to publish your tent and make it live on the web. # Publishing Tents Source: https://docs.tented.ai/working-with-tents/publishing-tents Publish your tent and make it live on the web ## Publishing Overview Publishing your tent makes it available on the web with a unique URL that you can share with your audience. The process takes a few clicks — and the publish dialog packs some useful options worth knowing about. ## Publishing Process Make sure you [preview your tent](/working-with-tents/previewing-tents) first. Then, in the tent editor, click **Publish** at the top right. A screenshot of the Ready to publish dialog with the Made with Tented badge toggle, password protection, and a custom URL field. The dialog gives you three options before you go live: * **Remove "Made with Tented" badge** — hide the badge on your published page (available on paid plans). * **Password protection** — click **Add Password** to gate the page. Great for internal reviews and client previews. * **Customize URL** — replace the auto-generated ID with a friendly slug, like `/launch-webinar`. You can also change it later via **Tent Actions > Customize URL**. Click **Publish**, and within seconds you'll get your live URL: A screenshot of the Tent Published confirmation showing the live URL. Your tent is now accessible worldwide. In the editor, the status badge switches to **Published**, and you'll see **View Live URL** and **Publish Update** buttons at the top. A screenshot of a published tent live on the web at its custom URL. Once published, edits don't go live automatically — keep iterating safely in the editor, then click **Publish Update** when you're ready to ship the changes. ## About Domains and URLs Your tent gets a unique URL on your workspace's subdomain: * Format: `https://[workspace-slug].tented-pages.com/[custom-slug-or-id]` * SSL certificate included * Global CDN for fast loading Pick the path yourself in the publish dialog (or via **Tent Actions > Customize URL**) — `/launch-webinar` beats `/45977a6f-87b6...` on any billboard. Serve tents from your own domain with a CNAME alias (paid plans). See our [Custom Domains](/configuring-tented/custom-domains) guide. ## Password-protecting a tent Want to keep a published tent private — for an internal review, a client preview, or a gated launch? Add a password, and visitors must enter it before they can see the live page. You can do this two ways: * **While publishing** — toggle **Add Password** in the publish dialog. * **Anytime after** — on the **Tent Details** page, choose **Tent Actions > Add Password**. Either opens the password panel. Click **Add Password**, enter one (at least 4 characters), and **Save**. A screenshot of the Add password panel with a password field noting a 4-character minimum. From the same panel you can later change or remove the password. Remove it and the page is public again — no republish needed. Password protection is available on paid plans. ## Publishing Updates When you publish updates to an existing tent, the important things stay stable: your URL doesn't change, form submission data is preserved, and analytics keep tracking seamlessly. Update your content as often as you like without disrupting anything. ## Unpublishing Changed your mind? **Tent Actions > Unpublish** takes the page off the web — the URL goes inactive and the tent returns to **Draft**. You can republish anytime. Learn how to view, manage, and export form submission data. # Reverting to Previous Generations Source: https://docs.tented.ai/working-with-tents/reverting-generations How to revert to previous versions of your tent ## Understanding Generation History Every tent maintains a complete history of all generations. Each time you create a tent or make significant changes, a new generation is created. This allows you to explore different directions and return to a previous version if needed. ## What is a Generation? A **generation** is a complete snapshot of your tent at a specific point in time. There are three types of generations in Tented: * **Initial Generation**: The first version created from your original prompt * **Iteration Generation**: Updates made through chat-based editing * **Reverted Generation**: When you go back to a previous version Each generation is comprehensive. It includes the complete HTML, CSS, and JavaScript code, along with all brand settings and uploaded assets. Every generation is tracked with a generation number and timestamp. ## Viewing Generation History The entire generation history for a tent is retained in the tent editor chat interface. You can open it at any time. To access the generation history for a tent: 1. In the dashboard, select **Tents** from the sidebar. 2. Click the link for the tent you want to view. 3. On the **Tent Details** page, select **Edit** at the top right. The tent opens in the tent editor. In the chat interface, you can scroll through all past generations to understand how your tent has evolved. To view the details about a specific generation, hover on the confirmation message from Tented in the history list. The generation number and timestamp will appear. A screenshot of the generation number and timestamp for a specific generation in the chat history. ## Reverting to a Previous Generation If you want to go back to a previous version of your tent, you can easily revert to any prior generation from the chat history. When you do this, rest assured that all form submission data from all generations is preserved, analytics remain intact, and any published URLs continue to work seamlessly, ensuring continuity regardless of the version you restore. ### Step-by-Step Process Follow these steps to revert to a previous generation: 1. Browse through the generation history for the tent. 2. Hover over the the confirmation message from Tented for the generation you want to revert to. 3. Click on the generation number that appears. 4. Confirm that you want to revert the tent. The tent reverts to that generation. If the tent was already live on the web, you will need to republish it for the changes to become public. If you reverted by mistake, you can always revert again to the last generation or any other generation. All previous generations remain available in the history. The confirmation message for a reverted tent. The new generation number and timestamp are highlighted in the chat history. Learn how to write effective prompts for better AI-generated results. # Tent Templates Source: https://docs.tented.ai/working-with-tents/tent-templates Turn a tent you love into a reusable starting point for future pages ## Why templates? Built a tent whose layout and style you'll want again — a webinar registration page, a product waitlist, a case-study layout? Save it as a **template** and every new tent can start from that design instead of a blank page. Great for keeping a consistent look across campaigns, or handing your team a proven starting point. Templates live under **Tents > Templates**, alongside the built-in [Tented example template collection](/working-with-tents/creating-tents#tented-example-templates), which automatically renders every starter design in your branding. A screenshot of the Templates tab showing an approved Event Registration Template. ## Creating a template The easiest way is to save an existing tent: 1. Open the tent's **Tent Details** page. 2. Choose **Tent Actions > Save as New Template**. 3. Name it and click **Create Template**. A screenshot of the Save as New Template dialog with a template name field. This creates a new **draft** template — a full copy of the tent's design that you can refine independently. ## Editing and approving A template opens in the same editor as a tent: chat to refine it, or edit the code directly. A screenshot of the tent template editor showing the event page design with an Approve button. Like emails, templates use an **approval** step: click **Approve** when it's ready. Only **approved** templates appear in the "start from a template" picker — so you can keep works-in-progress as drafts without cluttering everyone's options. ## Using a template When you create a tent (**Tents > Add > Create Tent**), the **Choose Tent Template** gallery opens. Your approved templates appear in its **My Templates** section, next to [Tented's example templates](/working-with-tents/creating-tents#tented-example-templates). Pick one, name the tent, and click **Create Tent**. Your new tent starts from that design, and you customize it with chat, leaving the template untouched. A screenshot of the Choose Tent Template gallery with category filters and the My Templates section. Templates also power the **Create Tent** step in [triggered flows](/email/triggered-flows#the-create-tent-step) — choose **From template** there to spin up a personalized page per contact without spending generation credits. Master the chat-based editing workflow. # Understanding the Tent Editor Source: https://docs.tented.ai/working-with-tents/understanding-tent-editor Navigate and use the tent editor interface effectively ## Tent Editor Overview The tent editor is your workspace for creating, editing, and refining your AI-generated landing pages. It combines a chat interface for natural language editing with a live preview and code view. A screenshot of the tent editor with the chat interface on the left and a new tent preview on the right. ## Interface Layout ### Top menu The top menu provides access to key functions: * **"Previous" Arrow**: Returns to the previous page. * **Tent Name**: Click to rename your tent. * **Code Icon**: Toggles between chat mode and code mode. * **View Form Submissions**: Opens the form submissions panel if your tent includes a form. * **Image Icon**: Opens the **Tent Assets** window to upload and view images and files. * **Mobile Preview**: Toggles a mobile device frame in the preview panel. * **Preview in New Tab**: Opens your tent in a new browser tab for full-page viewing. * **Publish**: Publishes your tent to a live URL. ### Left Panel: Chat Interface The chat interface serves as your primary tool for both generating and iterating on your tents, acting as the main control panel on the left side of the page. This area is composed of two main sections: the input area and the chat history. The input area allows you to provide commands through text input using natural language, upload images for visual references, or include files for downloadable assets like PDFs, while also displaying a generating indicator during AI processing. Above this, the chat history keeps a complete record of all previous conversations and edits, including generation numbers and timestamps for each interaction. This gives you the ability to easily reference and revisit previous changes. ### Right Panel: Live Preview This area shows a real-time preview of your tent. This means you can see changes instantly as you make them, giving immediate visual feedback. All interactive elements you create will work correctly in this preview window. ### Code Mode (Optional) Code mode allows you to directly edit the HTML, CSS, and JavaScript of your tent. To switch to code mode: 1. Click the **code icon** at the top of the screen. 2. **Edit** the code directly. 3. The tent updates in real time. 4. Click the **code icon** again to return to chat mode. A screenshot of the code view with the code icon highlighted. ## Making Changes Simply describe what you want to change. For details, see the [Editing Tents](/working-with-tents/editing-tents) guide. Learn advanced techniques for editing and iterating on your tents. # Viewing Form Submissions Source: https://docs.tented.ai/working-with-tents/viewing-form-submissions View, manage, and export form submission data ## Form Submissions Overview Every tent with forms automatically captures and stores submission data. You can view, manage, and export this data to track leads, analyze conversion rates, and follow up with potential customers. ## Accessing Form Submissions **From the Tent Details page:** 1. On the **Form Submissions** tile, click **View Form Data**. This button is located below the total number of form submissions for the tent. (It's also available via **Tent Actions > View Form Submissions**.) 2. The **Raw Form Data** window opens and displays a complete record of all form submission data for the tent. It includes the timestamp, form fields, and submitted values. 3. To export the data, click the **Download CSV** button. 4. When you're done, select **Close** to exit the window. A screenshot of the Raw Form Data window showing a submission with its timestamp, company, email, and name, plus a Download CSV button. **From the Tent Editor:** You might want to view form submissions while editing a tent to confirm that your form is working correctly and make adjustments if needed. To do this: 1. Click the **View Form Submissions icon** (hamburger menu) in the top menu. 2. The **Raw Form Data** window opens and displays a complete record of all form submission data for the tent. It includes the timestamp, form fields, and submitted values. 3. To export the data, click the **Download CSV** button. 4. When you're done, select **Close** to exit the window. A screenshot of the Tent Editor with the View Form Submissions icon highlighted and the Raw Form Data window open. ## Automations Don't want to check manually? [Form automations](/working-with-tents/form-automations) can push every submission to [Slack](/integrations/connecting-slack) or [Microsoft Teams](/integrations/connecting-microsoft-teams), email you per submission or on a schedule, or hit a webhook for your CRM. Form submissions can also feed your [People database](/people/managing-contacts), where a **Form Submitted** trigger can enroll contacts into [email flows](/email/triggered-flows) automatically. Route form submissions to Slack, Teams, email, or webhooks. # Viewing Tent Details Source: https://docs.tented.ai/working-with-tents/viewing-tent-details Navigate and understand the tent details page ## Tent Details Overview The **Tent Details** page provides a comprehensive view of your tent's information, status, and management options. This is your central hub for understanding and controlling your tent. Open it from the **Tents** page by clicking any tent's name. A screenshot of the Tent Details page showing the tent preview, details panel, published status, and live URL. ## Page Layout ### Header Section The header displays essential tent information: * **Tent Name**: The name of the tent. Double-click it to rename the tent. * **Status Badge**: **Draft** (being created or edited) or **Published** (live on the web). * **Live URL**: Shown when the tent is published — click it to open the live page. ### Action Buttons * **Edit**: Opens the [tent editor](/working-with-tents/understanding-tent-editor), where you make changes through chat or direct code editing. * **Tent Actions**: A dropdown with everything else: A screenshot of the Tent Actions menu with Preview, View Form Submissions, Manage Automations, Add Password, Download Code, Clone, Save as New Template, Customize URL, Unpublish, Rename, and Delete options. | Action | What it does | | ------------------------- | ----------------------------------------------------------------------------------------------- | | **Preview** | See how your tent looks on the web | | **View Form Submissions** | View and export [form data](/working-with-tents/viewing-form-submissions) | | **Manage Automations** | Set up [form automations](/working-with-tents/form-automations) — Slack, Teams, email, webhooks | | **Add Password** | Password-protect the published page | | **Download Code** | Download the tent's code | | **Clone** | Copy the tent for variations or A/B testing | | **Save as New Template** | Turn this tent into a reusable template | | **Customize URL** | Change the published URL slug | | **Unpublish** | Take the tent off the web | | **Rename / Delete** | Housekeeping | ## Information Tiles ### Tent Details * **Tent ID**: Unique identifier (hover to **Copy ID**) — you'll use this with the [API](/api-reference/introduction) * **Created / Last Updated**: Timestamps * **Tent Visibility**: Public or Private * **Created By**: Who made it * **Total Generations**: How many AI generations this tent has been through * **Original Prompt**: The prompt that started it all (hover to expand and copy) ### Activity Cards Across the bottom you'll find three live cards: A screenshot of the Page Views, Form Submissions, and Automations cards with their View Tent Analytics, View Form Data, and Add Automation buttons. * **Page Views** — traffic for the last 7 days, with **View Tent Analytics** for the full picture: recent pageviews (with location, device, and browser) and a views-by-day chart. * **Form Submissions** — total submissions, with **View Form Data** to [inspect and export them](/working-with-tents/viewing-form-submissions). * **Automations** — your active [form automations](/working-with-tents/form-automations) at a glance, with **Add Automation** to create more. A screenshot of the Tent Analytics window showing total page views, recent pageviews with device details, and a page views by day chart. Learn how to navigate and use the tent editor interface.