View
A view is a saved way of looking at an app's records. Every view belongs to exactly one app and stores:
- a layout β
table,listorboard; - an ordered list of filters β which records the view shows;
- a sort β one field and a direction, or none (which means the app's manual record order);
- per-field display settings β which fields are visible, and how wide the columns are;
- an optional split by β the field the view groups its records into sections by.
Every app has exactly one default view, which is what a user lands on when they open the app. Views created and managed through this API are always public β shared with everyone who can see the app.
The view resource exists so that an integration can read a view's definition from one app and recreate it on another β for example when migrating a workspace between organizations. See Copy a view to another app for that walkthrough.
This resource never returns records. To read the records of a view, use GET /v1/record/view/{view_id} on the record resource. It also never returns computed data β no section lists, no per-section counts, no column totals. Only the view's definition.
PUT replaces filters and fields wholesale
An update replaces the filters array and the fields map entirely β it never merges them. Omitting a key preserves it, but sending one overwrites what was stored. This is the most likely way to lose data with this resource. Read Update a view before you write.
Authentication and permissionsβ
All view endpoints require a user API key, sent as a bearer token. Automation ("workflow") API keys are rejected with 401 (App views can only be accessed with a user API key.) β the view permission model depends on user identity.
Permissions are checked in two layers against the app the view belongs to:
- Reading a view requires view access to the app.
- Creating, updating, deleting or promoting a view requires share access to the app β one level above edit access. Being able to edit records is deliberately not enough to reconfigure how everyone in the app sees them.
Views carry no created_at / last_modified_at timestamps.
Rate limit creditsβ
The base request cost is 10 credits. This resource charges:
| Endpoint | Credits |
|---|---|
GET /v1/app/{app_id}/views | 50 (5Γ base) |
GET /v1/view/{view_id} | 20 (2Γ base) |
POST /v1/view/app/{app_id} | 50 (5Γ base) |
PUT /v1/view/{view_id} | 50 (5Γ base) |
POST /v1/view/{view_id}/default | 50 (5Γ base) |
DELETE /v1/view/{view_id} | 50 (5Γ base) |
The view objectβ
Two shapes exist. The preview is what the list endpoint returns; the detail object extends it with the full definition and is what every other endpoint returns, wrapped in a { "view": β¦ } envelope.
Previewβ
| Field | Type | Notes |
|---|---|---|
id | number | The view id. |
view_id | number | Identical to id; present for consistency with the rest of the API. |
name | string | null | null for a view that has never been named. |
app_id | number | The app the view belongs to. |
is_default | boolean | Exactly one view per app is true. |
layout | "table" | "list" | "board" | See Field settings. |
private | boolean | false for every view this API can create. See Limitations. |
Detailβ
Everything in the preview, plus:
| Field | Type | Notes |
|---|---|---|
sort_by | number | null | The field_id the view is sorted by. null means manual record order. |
sort_desc | boolean | true for descending. false when there is no sort. |
filters | array | The view's filters, flat and implicitly ANDed. See Filters. |
fields | object | Per-field display settings, keyed by field_id, for the view's current layout. See Field settings. |
split_by | object | null | The split-by definition, or null. See Split by. |
The fields object always contains an entry for every field of the app β including fields the view has no stored setting for, which are reported with their effective default. That completeness is what makes a GET β PUT round-trip faithful: a detail response can be sent straight back to Update a view unchanged.
Retrieve a single viewβ
Retrieve a view with its full definition. Requires view access to the app it belongs to.
curl https://api.tapeapp.com/v1/view/88123 \
-u user_key_replace_with_your_api_key:
{
"view": {
"id": 88123,
"view_id": 88123,
"name": "Open tickets",
"app_id": 4711,
"is_default": false,
"layout": "table",
"private": false,
"sort_by": 55204,
"sort_desc": false,
"filters": [
{
"field_id": 55203,
"field_type": "status",
"type": "status",
"match_type": "any",
"values": [{ "value": 9001 }, { "value": 9002 }]
},
{
"field_id": 55201,
"field_type": "single_text",
"type": "text",
"match_type": "contains",
"values": [{ "value": "urgent" }]
}
],
"fields": {
"55201": { "hidden": false, "width": 320 },
"55203": { "hidden": false, "width": 160 },
"55204": { "hidden": false },
"55207": { "hidden": true }
},
"split_by": null
}
}
widthappears only fortableviews, and only for fields that carry a stored width.- Filter operands come back normalised to ids β a status or category filter written with option names comes back as
{ "value": <option_id> }, and a relation filter as{ "value": <record_id> }. - The response body is directly re-
PUT-able β see Update a view.
Create a viewβ
Create a view on the app with the specified app_id. Requires share access to the app.
name and layout are required; everything else is optional. The created view is always public and is never the default view β promote it afterwards if you need it to be.
| Key | Required | Type | Constraint |
|---|---|---|---|
name | β | string | max 200 characters; empty string allowed |
layout | β | "table" | "list" | "board" | no default β you must send one |
filters | β | array | max 100 entries; see Filters |
sort_by | β | number (field_id) | must be a field of this app |
sort_desc | β | boolean | defaults to false; ignored without sort_by |
fields | β | object keyed by field_id | hidden required per entry; see Field settings |
split_by | β | object | see Split by |
null is not accepted for any key on create β nullability is an update-only affordance, because on create there is nothing to clear. Any unknown or misspelled key is rejected with a 400.
- cURL
- JSON
curl -X POST https://api.tapeapp.com/v1/view/app/4711 \
-u user_key_replace_with_your_api_key: \
-H "Content-Type: application/json" \
--data '{
"name": "Open tickets",
"layout": "table",
"sort_by": 55204,
"sort_desc": false,
"filters": [
{
"field_id": 55203,
"field_type": "status",
"type": "status",
"match_type": "any",
"values": [{ "value": 9001 }, { "value": 9002 }]
}
],
"fields": {
"55201": { "hidden": false, "width": 320 },
"55203": { "hidden": false, "width": 160 },
"55207": { "hidden": true }
},
"split_by": { "field_id": 55203, "sorting": "label_asc", "limit": 25 }
}'
{
"name": "Open tickets",
"layout": "table",
"sort_by": 55204,
"sort_desc": false,
"filters": [
{
"field_id": 55203,
"field_type": "status",
"type": "status",
"match_type": "any",
"values": [{ "value": 9001 }, { "value": 9002 }]
}
],
"fields": {
"55201": { "hidden": false, "width": 320 },
"55203": { "hidden": false, "width": 160 },
"55207": { "hidden": true }
},
"split_by": { "field_id": 55203, "sorting": "label_asc", "limit": 25 }
}
Responds 201 with the { "view": β¦ } envelope containing the full detail object.
Update a viewβ
Update a view. Every key is optional. Requires share access to the app.
This is a partial update:
| In the body | Effect |
|---|---|
| key omitted | the stored value is preserved |
| key present with a value | the stored value is replaced |
key present with null | the stored value is cleared (two exceptions below) |
Collections are replaced wholesale β never merged.
filters: sending an array replaces the entire filter list. There is no "add one filter" operation β to append, retrieve the view, append to the array you got back, andPUTthe whole array.nullclears all filters.fields: sending the map replaces the display settings of the view's effective layout in full. Fields you leave out lose their stored settings and fall back to the layout's default visibility.nullclears them all.
Two exceptions to "null clears":
name: nullpreserves β it does not clear. An unnamed view reportsname: nullon read, sonullmust be accepted for a read body to bePUT-able verbatim; there is no "remove the name" operation, sonull(and""on update) is treated as "leave it alone".widthis ignored on non-tablelayouts β tolerated rather than rejected, so a client that reads atableview, switcheslayoutto"list"and puts the body back is not forced to stripwidthfrom every entry.
Read-only keys are accepted and ignored. So that a detail response can be PUT back verbatim, the update body also tolerates id, view_id, app_id, is_default, private and sort_property. None of them has any effect.
is_default: true in a PUT body does nothing
Promotion is a separate endpoint (POST /v1/view/{view_id}/default). Setting is_default in an update body is silently ignored, not honoured.
The effective layout. fields is interpreted against layout ?? the view's current layout. If you change layout and send fields in the same request, the map is written to the new layout's settings; the other two layouts' settings are preserved untouched. Switching a view's layout back and forth never destroys the settings of the layout you left.
Beyond the keys this API exposes, a PUT reads back and re-writes everything else a view stores β column footer statistics, board lanes and grouping, card-preview settings, and the settings of the layouts the view is not currently in β so an update that only renames a view leaves all of it intact. (The one exception is split-by drill-down filters; see Limitations.)
Rename only β everything else is preserved:
{ "name": "Open tickets β EMEA" }
Replace the filters and clear the sort:
{
"filters": [
{
"field_id": 55201,
"field_type": "single_text",
"type": "text",
"match_type": "starts_with",
"values": [{ "value": "EMEA" }]
}
],
"sort_by": null
}
Switch layout and give the list layout its own field settings:
{
"layout": "list",
"fields": {
"55201": { "hidden": false },
"55203": { "hidden": false },
"55207": { "hidden": true }
}
}
Responds 200 with the { "view": β¦ } envelope, post-update, so a client can immediately re-PUT it.
sort_desc on its own flips the direction of the view's existing sort without changing the sort field. Sending sort_by without sort_desc resets the direction to ascending, because sort_by replaces the whole sort.
Set the default viewβ
Promote a view to be its app's default view. Takes no request body. Requires share access to the app.
curl -X POST https://api.tapeapp.com/v1/view/88123/default \
-u user_key_replace_with_your_api_key:
Responds 200 with the { "view": β¦ } envelope, with is_default: true. The app's previous default view is demoted in the same operation, so the app always has exactly one default view.
- There is no way to un-default a view; you can only promote a different one.
- Promoting a view that is already the default is a no-op and still returns
200.
Delete a viewβ
Delete a view. Requires share access to the app.
curl -X DELETE https://api.tapeapp.com/v1/view/88123 \
-u user_key_replace_with_your_api_key:
{ "view_id": 88123 }
The view is soft-deleted: afterwards every endpoint reports it as 404. Records are never affected β deleting a view removes only the saved way of looking at them.
Do not delete an app's default view
Promote another view to default first, then delete. Deleting the view that is currently the default can leave the app with no default view, after which promoting a view fails. Every app keeps exactly one default view; this endpoint does not enforce it for you.
Filtersβ
A view's filters are a flat array, implicitly ANDed. There is no OR, and no grouping or nesting. If a record must match two conditions, put two entries in the array; a view cannot express "either of two".
Array order is the filter order; there is no position key on the wire. A view holds at most 100 filters.
A view filter is exactly the filter object documented for POST /v1/record/filter/app/{app_id} β the same union, the same match_type vocabulary, the same operand keys. Anything you can write as a record filter you can write as a view filter, unchanged. See the filter reference for every field type and its supported match types.
Every filter entry carries at least a field_id, a field_type, a type discriminator and a match_type:
{
"field_id": 55201,
"field_type": "single_text",
"type": "text",
"match_type": "contains",
"values": [{ "value": "urgent" }]
}
field_id,field_typeandmatch_typeare required;typeis required in practice (omitting it is a400).valuesis the operand list; its shape depends on the field type, and it is omitted forempty/not_empty.- Only field filters are accepted. The record endpoint's metadata filters (
created_at/last_modified_at/app_record_id) are not part of a view's stored filters.
Two view-specific facts:
include_active_user("@me") resolves to the owner of the API key you authenticate with, so a view saved with it is dynamic per viewer β exactly as in the app:{
"field_id": 55208,
"field_type": "single_user",
"type": "contact",
"match_type": "equal",
"include_active_user": true
}A relative-date or checklist filter uses the same operand keys as the record filter reference:
{
"field_id": 55204,
"field_type": "single_date",
"type": "date",
"match_type": "within",
"relative_date_range_type": "next_week"
}
Field settingsβ
fields is a JSON object keyed by field_id (a string, because JSON object keys are strings) whose values are { "hidden": boolean, "width"?: number }.
It applies to one layout at a time. A view stores display settings separately for each of its three layouts, and fields always addresses the view's current layout (or, on a write that also changes layout, the new one). The other two layouts' settings are preserved untouched β switching a view from table to list and back restores the original column widths and visibility.
hidden is required on every entry you write. Even though it is optional in the response shape, the request requires it. The underlying default means visible on table and hidden on list / board, so an entry like { "55201": { "width": 200 } } on a list view would silently hide that field. Requiring hidden makes visibility always explicit. (A GET β PUT round-trip never trips this, because the read emits hidden for every field.)
width is meaningful only for table. It is in pixels and must be between 100 and 1500. On list and board it is never returned, and on write it is accepted and ignored rather than rejected.
Defaults for fields you do not configure β which is what a GET reports for them:
| Layout | A field with no stored setting is⦠|
|---|---|
table | visible (hidden: false) β every field |
list, board | visible for the app's first field (its title field), hidden for all others |
Every field_id you name must belong to the app the view is on; a foreign or unknown id is a 400. Field order is not expressible through fields β the map is unordered, and stored ordering follows the app's own field order.
Split byβ
split_by is how a view groups its records into sections. This API exposes the definition only β the sections themselves, their record counts and any per-section totals are never returned.
| Key | Type | Notes |
|---|---|---|
field_id | number β required | the field to split by; must belong to the view's app |
period | day | weekday | week | month | year | null | bucket granularity; only meaningful for date-valued fields |
sorting | label_asc | label_desc | value_asc | value_desc | null | section order |
limit | number β₯ 1, or null | maximum number of sections |
A view can be split by a field of type single_category, multi_category, status, single_user, multi_user, single_relation, multi_relation, single_date, range_date, created_on, last_modified_on or calculation. Any other field type is a 400 (Invalid split_by field: a field of type 'β¦' cannot be split by).
- On update,
"split_by": nullremoves the split-by; omitting the key preserves it. sortingis auto-filled withlabel_ascforsingle_category,multi_categoryandstatusfields, so aGETcan report asortingyou never sent.
Errorsβ
All errors use the API's standard error envelope.
404 Not Found. Anything the caller cannot see is reported as not found, with one message shape regardless of cause: the view id does not exist; the view was deleted; the view is private to another user; the caller has no view access to the app; or (on create) the app id does not exist or is not visible to the caller. This is deliberate β a 403 would confirm that an id exists, leaking the existence of other users' private views. Treat 404 as "not found or not yours", not as "the id is wrong".
403 Forbidden means the caller can see the view (or the app) but lacks share access for the write they attempted β "you can see it, but you may not change it".
401 Unauthorized β no key, an invalid key, or an automation API key. Views require a user API key.
400 Bad Request β the request body is not acceptable. Common causes:
- an unknown or misspelled key anywhere in the body (both request schemas are strict);
namelonger than 200 characters, or (on create) a missingnameorlayout;layoutnot one oftable/list/board;- more than 100
filters, or a filter missingtypeor using amatch_typethe field type does not support; - a
fieldskey that is not a plain non-negative integer, afieldsentry withouthidden, or awidthoutside 100β1500; split_bywithoutfield_id, alimitbelow 1, or a split-by field whose type cannot be split by;- an unknown
period,sorting,field_typeormatch_type; - a
field_id(infilters,sort_by,fieldsorsplit_by) that belongs to a different app; nullfor any key on create;- the app already holds the maximum of 2000 views.
429 Too Many Requests β the credit budget is exhausted.
Known limitationsβ
Each of these is a deliberate v1 boundary:
- Checklist sort sub-mode is not exposed. A view sorted by a checklist field also stores which aspect of the checklist it sorts on (status, title, assignee or due date). This API neither reports nor accepts it, so a
GETβPUTof a checklist-sorted view resets that sub-mode. Thesort_propertykey is reserved for a future release and is accepted-and-ignored today. Avoid round-tripping checklist-sorted views until it ships. - Public views only. Every view this API creates is public. Your own private views are readable and writable by id and report
"private": true; other users' private views are404; private views never appear in the list endpoint. - No gallery layout.
layoutistable,listorboard. The legacy gallery layout has no public value and a view still using it cannot be read through this API. - Column statistics and board configuration are preserved but not editable. Column footer statistics, board lanes, the board grouping field, the board card-preview field and the "show progress" setting all survive every update untouched, but there is no way to read or set them here.
- Split-by drill-down filters are dropped by any update. When a user clicks into a section of a split view, the app writes temporary filters derived from the split-by. Those are never reported by
GET, and anyPUTremoves them β the app regenerates them from the split-by, so nothing a user configured is lost. - No timestamps. Views have no creation or modification metadata.
- No ordering control. The list endpoint returns views in no guaranteed order, and there is no endpoint to reorder an app's views.
Copy a view to another appβ
Recreating a view on a different app β the core migration use case β is a read-then-create sequence. Views reference apps, fields, options, users and records by id, and ids are never portable across organizations, so you need a mapping from source ids to target ids for all of them first.
- List the source app's views:
GET /v1/app/{source_app_id}/views. - For each one, retrieve the full definition:
GET /v1/view/{view_id}. - Rewrite the body against the target app:
sort_byβ the target field id (or drop it);- every
filters[].field_idβ the target field id, and every operand invaluesβ option ids, user ids and record ids all need remapping; - the keys of
fieldsβ target field ids; split_by.field_idβ the target field id;- drop
id,view_id,app_id,is_defaultandprivate(aPUTtolerates them, butPOSTrejects them).
- Create the view on the target app:
POST /v1/view/app/{target_app_id}withname,layoutand the rewritten fields. - If the source view was the app's default (
is_default: true), promote the new view β it cannot be set in the create body.
Two things to plan for: the target app must be one where you hold share access, not just edit access; and each app allows at most 2000 views, which a repeated or retried bulk migration can reach.
What does not carry over, and must be replayed by hand: the default-view flag (replay with the promote endpoint), private views (invisible in the source listing), the checklist sort sub-mode, column statistics and board configuration, gallery-layout views (not readable), and field order within a layout (inherited from the target app's own field order).