Skip to content

Labels and notes API ​

Labels a site defines and puts on conversations (the AI can too), and the team's private notes on conversations and contacts. Part of the API reference: setup, shared types and errors are there.

Add a note to a conversation ​

POST /conversations/:id/notes · scope conversations:write

A private note for the team: never shown to the visitor or the assistant. It also shows on the contact.

ParameterInRequiredDescription
idpathyesThe record's id.
Body fieldRequiredDescription
textyesUp to 5000 characters.

Request

bash
curl -X POST "$HELPPUFF_URL/api/v1/conversations/ID/notes" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "Called her back, booked for Tuesday."
}'
ts
type AddNoteToConversationRequest = {
  /** Up to 5000 characters. */
  text: string;
};

const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/conversations/${id}/notes`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    text: 'Called her back, booked for Tuesday.'
  } satisfies AddNoteToConversationRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as AddNoteToConversationResponse;

Response 201

json
{
  "id": "note_mfx2k1a9b3c4",
  "conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
  "leadId": "lead_5e3f0c8a-1f7b-4f8e-9a51-0e2b7c4d1a90",
  "author": "sam@acme.example",
  "authorName": "Sam",
  "text": "Called her back, booked for Tuesday.",
  "createdAt": 1760000000000,
  "updatedAt": 1760000000000
}
ts
type AddNoteToConversationResponse = {
  id: string;
  conversationId: string;
  leadId: string | null;
  author: string;
  authorName: string | null;
  text: string;
  createdAt: number;
  updatedAt: number;
};

Edit a note ​

PATCH /notes/:id · scope conversations:write

ParameterInRequiredDescription
idpathyesThe record's id.
Body fieldRequiredDescription
textyesUp to 5000 characters.

Request

bash
curl -X PATCH "$HELPPUFF_URL/api/v1/notes/ID" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "Booked for Tuesday 9am."
}'
ts
type EditNoteRequest = {
  /** Up to 5000 characters. */
  text: string;
};

const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/notes/${id}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    text: 'Booked for Tuesday 9am.'
  } satisfies EditNoteRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as EditNoteResponse;

Response 200

json
{
  "id": "note_mfx2k1a9b3c4",
  "conversationId": "1b0f6a52-3c1e-4c55-9f0e-2d1c7a9e5b10",
  "leadId": "lead_5e3f0c8a-1f7b-4f8e-9a51-0e2b7c4d1a90",
  "author": "sam@acme.example",
  "authorName": "Sam",
  "text": "Booked for Tuesday 9am.",
  "createdAt": 1760000000000,
  "updatedAt": 1760000000000
}
ts
type EditNoteResponse = {
  id: string;
  conversationId: string | null;
  leadId: string | null;
  author: string;
  authorName: string | null;
  text: string;
  createdAt: number;
  updatedAt: number;
};

Delete a note ​

DELETE /notes/:id · scope conversations:write

ParameterInRequiredDescription
idpathyesThe record's id.

Request

bash
curl -X DELETE "$HELPPUFF_URL/api/v1/notes/ID" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY"
ts
const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/notes/${id}`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
  },
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as DeleteNoteResponse;

Response 200

json
{
  "id": "note_mfx2k1a9b3c4",
  "deleted": true
}
ts
type DeleteNoteResponse = {
  id: string;
  deleted: boolean;
};

List labels ​

GET /labels · scope conversations:read

The site's labels, with the colours there are. ai: the AI may put it on a conversation when it goes quiet, guided by description.

ParameterInRequiredDescription
sitequerynoThe site (a key always uses its own).

Request

bash
curl "$HELPPUFF_URL/api/v1/labels" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY"
ts
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/labels`, {
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
  },
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ListLabelsResponse;

Response 200

json
{
  "labels": [
    {
      "id": "lbl_3f2a1b0c9d8e",
      "name": "Urgent",
      "color": "#ef4444",
      "description": "Needs an answer today",
      "ai": true
    }
  ],
  "colors": [
    "#6b7280",
    "#ef4444",
    "#3b82f6"
  ]
}
ts
type ListLabelsResponse = {
  labels: Array<{
    id: string;
    name: string;
    color: string;
    description: string;
    ai: boolean;
  }>;
  colors: string[];
};

Create a label ​

POST /labels · scope settings:write

Body fieldRequiredDescription
nameyesUp to 40 characters, one per name.
colornoA hex colour like #3b82f6; one is picked if left out.
descriptionnoWhat it means (up to 200 characters): the AI reads it to decide.
ainoWhether the AI may use it (default true).

Request

bash
curl -X POST "$HELPPUFF_URL/api/v1/labels" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Urgent",
  "color": "#ef4444",
  "description": "Needs an answer today"
}'
ts
type CreateLabelRequest = {
  /** Up to 40 characters, one per name. */
  name: string;
  /** A hex colour like `#3b82f6`; one is picked if left out. */
  color?: string;
  /** What it means (up to 200 characters): the AI reads it to decide. */
  description?: string;
  /** Whether the AI may use it (default true). */
  ai?: string;
};

const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/labels`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Urgent',
    color: '#ef4444',
    description: 'Needs an answer today'
  } satisfies CreateLabelRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as CreateLabelResponse;

Response 201

json
{
  "id": "lbl_3f2a1b0c9d8e",
  "name": "Urgent",
  "color": "#ef4444",
  "description": "Needs an answer today",
  "ai": true
}
ts
type CreateLabelResponse = {
  id: string;
  name: string;
  color: string;
  description: string | null;
  ai: boolean;
};

Errors

StatusCodeWhen
409conflictA label with this name exists.

Change a label ​

PATCH /labels/:id · scope settings:write

ParameterInRequiredDescription
idpathyesThe record's id.
Body fieldRequiredDescription
namenoUp to 40 characters.
colornoA hex colour.
descriptionnoWhat it means.
ainoWhether the AI may use it.

Request

bash
curl -X PATCH "$HELPPUFF_URL/api/v1/labels/ID" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ai": false
}'
ts
type ChangeLabelRequest = {
  /** Up to 40 characters. */
  name?: string;
  /** A hex colour. */
  color?: string;
  /** What it means. */
  description?: string;
  /** Whether the AI may use it. */
  ai?: boolean;
};

const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/labels/${id}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    ai: false
  } satisfies ChangeLabelRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as ChangeLabelResponse;

Response 200

json
{
  "id": "lbl_3f2a1b0c9d8e",
  "name": "Urgent",
  "color": "#ef4444",
  "description": "Needs an answer today",
  "ai": false
}
ts
type ChangeLabelResponse = {
  id: string;
  name: string;
  color: string;
  description: string | null;
  ai: boolean;
};

Delete a label ​

DELETE /labels/:id · scope settings:write

Removes it from every conversation too.

ParameterInRequiredDescription
idpathyesThe record's id.

Request

bash
curl -X DELETE "$HELPPUFF_URL/api/v1/labels/ID" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY"
ts
const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/labels/${id}`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
  },
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as DeleteLabelResponse;

Response 200

json
{
  "id": "lbl_3f2a1b0c9d8e",
  "deleted": true
}
ts
type DeleteLabelResponse = {
  id: string;
  deleted: boolean;
};

Add a note to a contact ​

POST /leads/:id/notes · scope leads:write

A private note for the team about this person.

ParameterInRequiredDescription
idpathyesThe record's id.
Body fieldRequiredDescription
textyesUp to 5000 characters.

Request

bash
curl -X POST "$HELPPUFF_URL/api/v1/leads/ID/notes" \
  -H "Authorization: Bearer $HELPPUFF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "Prefers email. Owns two properties."
}'
ts
type AddNoteToContactRequest = {
  /** Up to 5000 characters. */
  text: string;
};

const id = '…';
const response = await fetch(`${process.env.HELPPUFF_URL}/api/v1/leads/${id}/notes`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HELPPUFF_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    text: 'Prefers email. Owns two properties.'
  } satisfies AddNoteToContactRequest),
});
if (!response.ok) throw new Error(((await response.json()) as ApiError).error.message);
const data = (await response.json()) as AddNoteToContactResponse;

Response 201

json
{
  "id": "note_mfx2k1a9b3c4",
  "conversationId": null,
  "leadId": "lead_5e3f0c8a-1f7b-4f8e-9a51-0e2b7c4d1a90",
  "author": "sam@acme.example",
  "authorName": "Sam",
  "text": "Prefers email. Owns two properties.",
  "createdAt": 1760000000000,
  "updatedAt": 1760000000000
}
ts
type AddNoteToContactResponse = {
  id: string;
  conversationId: string | null;
  leadId: string;
  author: string;
  authorName: string | null;
  text: string;
  createdAt: number;
  updatedAt: number;
};

MIT licensed. Runs in your own Cloudflare account.