Skip to main content

Public Endpoints Reference

All public endpoints are served at https://<tenant-custom-domain>/public/v1/. No authentication is required. Tenant identity is derived exclusively from the Host header.

GET /public/v1/privacy/dsar

Renders the DSAR submission form as an HTML page.

Authentication: None
Content-Type returned: text/html; charset=utf-8

Request

curl -X GET "https://privacy.example.com/public/v1/privacy/dsar"

Response

Returns an HTML page containing the DSAR form pre-populated with the tenant's display name and default jurisdiction (if configured). The response includes no cookies and no external script tags.

Response headers:

Content-Type: text/html; charset=utf-8
Cache-Control: no-store
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

Status codes:

CodeMeaning
200Form rendered successfully
404Hostname not found / no tenant matches this Host
500Internal error

POST /public/v1/privacy/dsar/submit

Submits a DSAR. Validates the form, persists the request, and sends a confirmation email.

Authentication: None
Content-Type accepted: application/x-www-form-urlencoded
Content-Type returned: text/html; charset=utf-8 (renders confirmation or error page)

Request

curl -X POST "https://privacy.example.com/public/v1/privacy/dsar/submit" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "full_name=Jane Doe" \
--data-urlencode "email=jane.doe@example.com" \
--data-urlencode "request_type=access" \
--data-urlencode "jurisdiction=eu" \
--data-urlencode "details=I would like a copy of all data you hold about me."

Form fields

FieldTypeRequiredValidation
full_namestringYes1–200 characters
emailstringYesValid RFC 5321 email address
request_typeenumYesOne of: access, erasure, amend, portability, restrict
jurisdictionenumYesOne of: us, eu, uk, ca, au, in, br
detailsstringNoUp to 2000 characters

Response

On success, returns an HTML confirmation page (HTTP 200) showing a short ticket reference (first 8 characters of the UUID). The full ticket UUID is sent to the consumer's email.

On validation failure, returns HTTP 400 with the form re-rendered and an error message.

Response headers (success and error):

Content-Type: text/html; charset=utf-8
Cache-Control: no-store
X-Content-Type-Options: nosniff
X-Frame-Options: DENY

Status codes:

CodeMeaning
200Request submitted and confirmation rendered
400Validation failure — form re-rendered with error
404Hostname not found
500Internal error

Security notes

  • The consumer's email address is stored as a one-way SHA-256 hash in Helix's database. The plaintext email is used only to send the confirmation email in a fire-and-forget goroutine; failure to send does not fail the submission.
  • No rate limit headers are returned, but server-side rate limiting is applied per source IP and per tenant to prevent form spam.
  • The form embed sets no cookies.

GET /public/v1/privacy/notice

Returns the current published privacy notice for the tenant as HTML.

Authentication: None
Content-Type returned: text/html; charset=utf-8

Request

curl -X GET "https://privacy.example.com/public/v1/privacy/notice" \
-H "If-None-Match: \"<etag-from-previous-response>\""

Response

Returns the rendered HTML of the tenant's current published privacy notice.

Response headers:

Content-Type: text/html; charset=utf-8
Cache-Control: public, max-age=300
ETag: "<sha256-hex-of-content>"
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN

ETag is a double-quoted hex string of the SHA-256 hash of the notice content. Use If-None-Match on subsequent requests to benefit from 304 Not Modified responses and avoid transferring the full body.

Status codes:

CodeMeaning
200Notice returned
304Not Modified (ETag matched If-None-Match)
404No published notice for this tenant
500Internal error

GET /public/v1/privacy/notice/v:version

Returns a specific historical version of the privacy notice. Only published or archived versions are accessible.

Authentication: None
Content-Type returned: text/html; charset=utf-8

Request

# Fetch version 3 of the privacy notice
curl -X GET "https://privacy.example.com/public/v1/privacy/notice/v3"

Path parameter

ParameterTypeDescription
versionpositive integerThe notice version number

Response

Returns the HTML of the specified version. Archived versions are immutable.

Response headers:

Content-Type: text/html; charset=utf-8
Cache-Control: public, max-age=31536000, immutable
ETag: "<sha256-hex-of-content>"
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN

Status codes:

CodeMeaning
200Version returned
304Not Modified (ETag matched)
400Invalid version number (non-integer or ≤ 0)
404Version not found, or version exists but is not published/archived (draft/in-review versions are not exposed)
500Internal error

Common error behavior

All public endpoints return plain-text or HTML error messages (never JSON) because they are consumer-facing surfaces. Machine-readable error codes are not needed on the public tier.

If the request Host header does not match any configured tenant domain, Helix returns 404 with a plain-text body. There is no fallback tenant.

Idempotency

GET endpoints are idempotent. POST /public/v1/privacy/dsar/submit is not idempotent — re-submitting the same form creates a new DSAR ticket. There is no client-supplied idempotency key in Phase 1.