Skip to content

Latest commit

 

History

History
175 lines (133 loc) · 8.7 KB

File metadata and controls

175 lines (133 loc) · 8.7 KB

Document 2 — Hashbun Campaign API Reference & Discovery Guide

Audience: Claude Code, implementing the push layer of the v2 rebuild. Purpose: everything needed to create campaigns and upload images against the Hashbun backend, plus how to discover anything not documented here directly from the live API.

Secrets: do not commit any bearer token to the repo. The token is short-lived and operator-supplied (see §2). This document contains no token.


1. Base URL & environments

  • API base: https://api.hashbunmedia.com
  • Public site / page base: https://hashbunmedia.com
  • All endpoints below are relative to the API base.
  • The backend stores uploaded images in Cloudinary (cloud name dq8lvevhz); image responses return a Cloudinary URL. You never call Cloudinary directly — you call the Hashbun image endpoint and it handles Cloudinary.

2. Authentication

  • Auth is a Bearer token in the Authorization header: Authorization: Bearer <token>.
  • Required for: creating campaigns, uploading images.
  • Not required for: reading campaigns and URI checks (those endpoints are public — very useful for discovery, see §7).
  • The token is short-lived. When a request returns HTTP 401, the token has expired or been replaced — the operator refreshes it from the browser: open the Hashbun admin, DevTools → Network tab → pick any request to api.hashbunmedia.com → copy the value after Bearer in the authorization request header.
  • Store the token in operator-owned config outside the repo (env var or a git-ignored config file). Never hard-code it.

3. Create a campaign

POST /campaign/
Authorization: Bearer <token>
Content-Type: application/json

Body (payload) — field-by-field:

Field Type Notes
title string The campaign title (the detected heading).
uri string The unique slug (see 03_POC.md §7). Must be unique — check first (§5).
isPublished bool true = live. Keep a config flag to push drafts (false) during testing.
content string (HTML) Body HTML fragment (<p>, <strong>, <a>, <ul><li>).
formTitle string The form sub-heading (e.g. "Please fill in the form…").
submitButtonTitle string Submit button label (e.g. "Download" / "Herunterladen").
fields array The form fields — see §4.
privacySettings object { "type": "disabled", "label": null, "target": null, "url": null } (default).
termSettings object { "type": "disabled", "label": null, "target": null, "url": null } (default).
formSuccessMessage string Default: "Success! Your download will begin shortly."
snackSuccessMessage string Default: "Thank you"
submitURL string/null Default null.
customFieldsLayout bool Default false.
noOfLayoutRows int/null Default null.
styles string/null Campaign CSS (default stylesheet).
campaignType string Default "general".
emailDesign object Default { "content": null, "emailFieldCode": null, "subject": null }.
externalAPI array Default [].
image string/null The hero image id (_id returned by the image upload, §6).
logo string/null The logo image id (_id from image upload).
resource string/null Default null.

Response: the created campaign object, including _id and uri. On success capture both; the live page is https://hashbunmedia.com/<uri> (confirm exact path against a known campaign — see §7).

On failure: log status_code and the full response body. A 401 = token (see §2). A 4xx about the URI usually means it already exists — resolve uniqueness first (§5).


4. The field object

Each entry in fields has this shape:

{
  "name": "First name",
  "code": "first_name",
  "type": "TextBox",
  "isMandatory": true,
  "isVisible": true,
  "defaultValue": "",
  "fieldValues": [],
  "rowNumber": null,
  "associatedField": null,
  "conditionalField": null,
  "conditionalValue": null
}
  • type is one of: "TextBox", "Email", "DropDown", "CheckBox".
  • code is a stable, unique, snake_case key derived from the label. Guarantee uniqueness within the form (suffix _2, _3 on collision).
  • fieldValues is used only by DropDown. Each option is:
    { "code": "1", "value": "United States", "associatedValue": "" }
    (Country dropdowns are populated from the global country list; codes are the list's codes as strings.)
  • CheckBox (consent): type = "CheckBox", name = the consent HTML (sentence + inline <a href=…>privacy link</a>), no <p> wrapper, fieldValues empty. Exactly one consent per form (merge a stray link-only line back in).
  • Email: a text field whose label looks like an email/e-mail/courriel; fieldValues empty.
  • Leave rowNumber, associatedField, conditionalField, conditionalValue as null unless the layout genuinely needs them.

5. Check URI availability

GET /campaign/uri/{uri}
  • 404 → the URI is free (use it).
  • 200 → a campaign with that URI already exists (returns its data). Resolve by appending a dated suffix and re-checking (see 03_POC.md §7).
  • Public (no auth needed), so safe to call repeatedly for live uniqueness feedback in the UI.

6. Upload an image

POST /image/
Authorization: Bearer <token>
Content-Type: multipart/form-data

Multipart fields:

  • imageType: "Logo" for the logo, "Campaign" for the hero.
  • altText: the image name (use the unique filename stem, e.g. the slug for the hero, logo-<random> for the logo).
  • image: the file bytes. The multipart filename must be unique per campaign (e.g. <slug>.png) — identical filenames across campaigns previously collided on the backend/Cloudinary and broke every image after the first.

Response (JSON):

{
  "url": "https://res.cloudinary.com/dq8lvevhz/.../<name>.png",
  "publicID": "",
  "altText": "",
  "width": 890,
  "height": 1254,
  "_id": "6a3c…"
}

Use the returned _id as the image / logo value in the create payload (§3). Log the status, url, and _id for every upload.

Order of operations for a push: upload hero → capture _id → upload logo → capture _id → set image/logo on the payload → POST /campaign/. If an image upload fails, log it and either abort that campaign or create without the image per operator choice — never fail silently.


7. Discovering more from the live API (when this doc is not enough)

The read endpoints are public, so Claude Code (or the operator) can inspect the real system to confirm any detail or find fields this document doesn't cover.

  1. List / inspect existing campaigns:

    GET /campaign/
    

    Returns the full set of existing campaigns (there are ~1300+). Inspect a few real objects to confirm the exact payload shape, default values, and any field not listed here. This is the source of truth — the schema in §3/§4 was derived from it.

  2. Fetch one known-good campaign by URI:

    GET /campaign/uri/{some-existing-uri}
    

    Pick a campaign you know renders correctly on the site, fetch it, and mirror its structure. Especially useful for confirming how fields, styles, consent CheckBox HTML, and image/logo references are stored.

  3. Confirm the image contract with a HAR capture: in the Hashbun admin, create/edit a campaign with the browser DevTools → Network recording. Look at the real POST /image/ and POST /campaign/ requests to confirm headers, multipart field names, and body — then match them exactly. (This is how the image endpoint's imageType/altText contract was originally confirmed.)

  4. When adding a new capability (e.g. conditional fields, a new field type, term/privacy settings that are actually enabled), first GET a real campaign that uses it and copy its structure rather than guessing.

Rule of thumb: if the API behaves differently from this document, believe the live API and update the implementation to match a real, working campaign object.


8. Error & edge handling summary

  • 401 anywhere → expired/replaced token; prompt the operator to refresh it (§2). Both image upload and create use the same token, so both clear together once it's valid.
  • URI already exists → append dated suffix, re-check GET /campaign/uri/{uri} until 404.
  • Image upload non-2xx → log status + body; do not attach; surface in the UI.
  • Country field but empty global list → block the campaign (do not create).
  • Timeouts → use a sane request timeout (e.g. 30s) and log; a network failure on the uniqueness check should skip that campaign, not crash the batch.