Custom Fields
Overview
Custom fields are properties your workspace defines on top of the ones There There tracks, such as "Billable" or "Reported version". See the custom fields guide for how they are set up.
Through the API you can list the definitions, filter tickets by a field, ask for values to come back alongside the ticket list, and set a value on a ticket.
Every field and option is addressed by its ULID. The definitions endpoint is where those come from, so an integration never has to hardcode them.
Reporting on custom fields
A common case: "give me every billable ticket with its billable time, so I can sum it". Three calls, no per-ticket fetching.
First, resolve the field and option ids.
curl https://there-there.app/api/custom-fields \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"
{
"data": [
{
"ulid": "01HX9F3K2R...",
"name": "Billable",
"type": "single_select",
"all_channels": true,
"channel_ulids": [],
"options": [
{ "ulid": "01HX9F3K2S...", "name": "Yes", "color": null, "icon": null },
{ "ulid": "01HX9F3K2T...", "name": "No", "color": null, "icon": null }
]
},
{
"ulid": "01HX9F3K2P...",
"name": "Billable time",
"type": "text",
"all_channels": true,
"channel_ulids": [],
"options": []
}
]
}
Then list the tickets, filtering on Billable = Yes and asking for the values to come along.
curl -G https://there-there.app/api/tickets \
--data-urlencode "filter[custom_fields][01HX9F3K2R...]=01HX9F3K2S..." \
--data-urlencode "include=custom_fields" \
--data-urlencode "per_page=100" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Accept: application/json"
Each ticket carries its values, so you can sum them yourself.
{
"data": [
{
"ulid": "01HX9F3K2M...",
"number": 1042,
"subject": "Migration help",
"custom_fields": [
{
"name": "Billable",
"type": "single_select",
"value": null,
"selected_option_ulids": ["01HX9F3K2S..."]
},
{
"name": "Billable time",
"type": "text",
"value": "2.5",
"selected_option_ulids": []
}
]
}
],
"meta": { "current_page": 1, "last_page": 3, "per_page": 100, "total": 214 }
}
Finally, page through with ?page=2 until current_page reaches last_page, and add up the Billable time values.
A few things worth knowing.
include=custom_fields is opt-in so existing integrations do not suddenly receive larger payloads. Without it the list leaves custom_fields out entirely. It costs one extra query per page regardless of page size, so there is no reason to avoid it when you need the values.
Filters combine as you would expect. Several options of the same field widen the result (...[Billable]=yes,no means either), while two different fields narrow it (Billable = Yes and Plan = Pro). An option ulid that does not exist matches nothing rather than being ignored, so a typo returns an empty list instead of quietly reporting every ticket as billable.
Values are always strings. Billable time above is "2.5", not 2.5, because a text custom field stores whatever the agent typed. Cast on your side, and be aware that nothing stops an agent typing "about 3 hours".
Set a custom field
Custom fields are properties a workspace admin defines in Settings, such as "Reported version" or "Framework". Each one is either a text field, a single-select, or a multi-select, and each is available on all channels or on a chosen few.
PUT /api/tickets/{ticket_ulid}/custom-fields/{custom_field_ulid}
The fields that apply to a ticket, and the ULIDs of their options, come back on GET /api/tickets/{ticket_ulid} under custom_fields. Fields nobody has filled in are listed too, so that response is also how you discover what you may set.
For a text field, send the text and an empty option list.
{ "value": "1.2.3", "option_ulids": [] }
For a single-select or multi-select field, send the options and a null value. The list replaces the current selection rather than adding to it, so a multi-select call must carry every option that should end up selected.
{ "value": null, "option_ulids": ["01HX9F3K2S..."] }
Send a null value and an empty list to clear the field.
{ "value": null, "option_ulids": [] }
Both keys are required on every call. Sending text to a select field, options to a text field, or more than one option to a single-select field is a 422. A field belonging to another workspace, or one that is not available on the ticket's channel, is a 404.
The response is the updated ticket, including its custom_fields, so you do not need to re-fetch.