shareyourthoughtswith.me

JSON API (/api/v1)

All API requests authenticate with an API key issued at /admin/api-keys. Send the key in either:

A submit-scoped key (sytwm_sk_…) may POST feedback and telemetry. A read-scoped key (sytwm_rk_…) may GET feedback, telemetry, and post definitions.

Error envelope

Every error response (4xx / 5xx) carries this shape:

{
  "error": {
    "code": "validation_failed",
    "message": "Submitted answer did not match the step's input kind.",
    "details": [{"field": "answers.tried", "code": "invalid_choice", "message": "Expected 'yes' or 'no'."}],
    "request_id": "01J..."
  }
}

Stable codes:

Endpoints

GET /api/v1/posts

List every published post. Both scopes pass.

[
  {"slug": "default", "title": "Quick feedback", "version": 1, "definition": {...}}
]

GET /api/v1/posts/{slug}

Get a single published post definition. Both scopes pass.

POST /api/v1/feedback

Scope: submit.

Request:

{
  "post_slug": "default",
  "answers": [
    {"step_id": "tried", "answer": true},
    {"step_id": "what_tried", "answer": "the install script"}
  ],
  "is_public": true,
  "public_text": "Worked on the second try!",
  "submitter_label": "anon",
  "client_dedup_id": "uuid-or-hash-of-the-form-submit"
}

Response (201):

{ "id": "1f3b…", "is_public": true, "replayed": false }

If client_dedup_id matches a prior submission from the same key, the existing entry is returned with replayed: true — supports safe retries.

GET /api/v1/feedback?limit=50&cursor=...

Scope: read. Paginated list, newest first.

{
  "items": [
    {
      "id": "1f3b…",
      "post_slug": "default",
      "is_public": true,
      "is_hidden": false,
      "public_text": "...",
      "submitter_label": "anon",
      "submitted_at": "2026-05-29T12:34:56Z",
      "answers": [{"step_id": "tried", "step_label": "Did you try it?", "answer": true}]
    }
  ],
  "next_cursor": "uuid-of-last-item-or-null"
}

POST /api/v1/telemetry

Scope: submit. Records a single telemetry event.

{
  "event_type": "page_hit",
  "post_slug": "default",
  "step_id": "tried",
  "session_id": "client-session",
  "path": "/feedback/default",
  "referrer": "https://news.example/post/123",
  "payload": { "experiment": "homepage-A" }
}

Response (201): { "id": 42 }

GET /api/v1/telemetry?cursor=...&limit=100

Scope: read. Returns the most recent events with a next_cursor.

Rate limits

Over-limit responses are HTTP 429 with Retry-After and the error envelope above.