Records
Create, read, update, and organize records - the core unit of work in Blue, modeled as Record objects in the API.
Records are the core unit of work in Blue: the cards you create in a list, assign to people, tag, date, and fill with custom field values. In the API a record is a Record object with one or more database homes within workspaces (Workspace). A database record does not need a list (RecordList).
This section covers creating records and every operation you reach for next - reading, updating, moving, completing, and organizing them. The most common starting point, createRecord, is documented in full below.
POST https://api.blue.app/graphql
blue-token-id: YOUR_TOKEN_ID
blue-token-secret: YOUR_TOKEN_SECRET
blue-org-id: YOUR_ORG_ID
blue-workspace-id: project_123The blue-workspace-id header scopes record operations to one workspace. Company and project headers accept either an ID or a slug. Header names are case-insensitive.
For explicit database creation, singleton reads, field writes, files, color, delete, and restore, see Database Records. The createRecord examples below use the workspace/list contract.
Operations
| Operation | GraphQL | Description |
|---|---|---|
| Create a record | createRecord mutation | Add a record to a list, with optional tags, assignees, dates, checklists, and custom field values. Documented below. |
| List records | recordQueries { todos } query | Retrieve and filter records in a workspace. |
| Update a record | editRecord mutation | Change a record’s title, dates, or other fields. |
| Toggle completion | updateTodoDoneStatus mutation | Mark a record done or not done. |
| Move a record | moveRecord mutation | Move a record to a different list. |
| Copy a record | copyRecord mutation | Duplicate an existing record. |
| Assignees | setRecordAssignees mutation | Assign or unassign users. |
| Tags | setRecordTags mutation | Attach or detach tags. |
| Comments | createComment mutation | Add a comment to a record. |
| Dependencies | createRecordDependency mutation | Link records as blockers or dependents. |
| Recurring records | createRepeatingRecord mutation | Configure a repeating schedule. |
| Archive a record | archiveRecord mutation | Archive a record (and unarchiveRecord to restore it), hiding it from active lists while keeping its data. |
Two lifecycle mutations don’t yet have dedicated pages but round out the record surface:
| Operation | GraphQL | Description |
|---|---|---|
| Delete a record | deleteRecord(input: DeleteRecordInput!): MutationResult! | Move a record to Trash. DeleteRecordInput takes todoId: String! and optional databaseId: ID. Reversible with restoreRecord(id: String!) until the record is purged. |
| Restore a record | restoreRecord(id: String!, databaseId: ID): Record! | Restore a record from Trash to its list (or the first available list if its original list is gone). Workspace OWNER/ADMIN only. |
| Bulk-update records | updateRecords(input: UpdateRecordsInput!): Boolean! | Apply the same change to many records at once. |
Create a record
Use the createRecord mutation to add a record to a list. The only required input is a title; everything else - the target list, dates, assignees, tags, checklists, and custom field values - is optional and can be set in the same call.
Request
The smallest call creates a record from a title alone. If you omit todoListId, the record lands in the workspace’s first list (one is created if the workspace has none).
mutation CreateRecord {
createRecord(input: { title: "Draft launch plan" }) {
id
title
rank
}
}Parameters
CreateRecordInput
| Parameter | Type | Required | Description |
|---|---|---|---|
title | String! | Yes | The record’s title. |
todoListId | String | No | List to add the record to. If omitted, the workspace’s first list is used. |
databaseId | ID | No | Create the record in this table instead of the workspace’s own. A table that cannot receive the create (wrong workspace, trashed, no display field) is rejected rather than silently retargeted. |
fieldValues | [JSONObject!] | No | Values for the table’s optional creation form, each {fieldId, kind, value} — see Create a record in a table. |
previousId | String | No | Insert the record directly below this record. Overrides placement when both are given. |
nextId | String | No | Insert the record directly above this record. |
placement | CreateRecordInputPlacement | No | Where to insert the record when no anchor is given. Defaults to the top of the list. |
startedAt | DateTime | No | Start date/time. |
duedAt | DateTime | No | Due date/time. |
timezone | String | No | IANA timezone used to interpret startedAt/duedAt (e.g. America/New_York). Only affects inference when granularity is omitted. |
granularity | DateGranularity | No | ALL_DAY or TIMED. See All-day vs. timed dates. Omit to infer from the values sent. |
notify | Boolean | No | Send creation notifications to assignees and watchers. |
description | String | No | HTML body for the record. Sanitized server-side; stored as both html and plain text. |
assigneeIds | [String!] | No | User IDs to assign. IDs that aren’t workspace members are silently dropped. |
checklists | [CreateChecklistWithoutTodoInput!] | No | Checklists to create on the record. |
customFields | [CreateRecordInputCustomField] | No | Initial custom field values. |
tags | [CreateRecordTagInput!] | No | Tags to attach. |
CreateRecordInputPlacement
| Value | Description |
|---|---|
TOP | Insert above the current first record (the default behavior). |
BOTTOM | Insert after the current last record. |
CreateRecordTagInput
Provide either id (to attach an existing tag) or title (to find or create a tag by title).
| Parameter | Type | Required | Description |
|---|---|---|---|
id | String | No | ID of an existing tag in this workspace. |
title | String | No | Tag title. A matching tag is reused; otherwise a new one is created. |
color | String | No | Hex color for a newly created tag. Defaults to #4a9fff. |
CreateRecordInputCustomField
| Parameter | Type | Required | Description |
|---|---|---|---|
customFieldId | String | No | ID of the custom field to set. The field must belong to the same workspace. |
value | String | No | Value as a string, parsed according to the field’s type (see below). |
granularity | DateGranularity | No | For a DATE field, ALL_DAY or TIMED. Omit to infer from value. Ignored for other field types. |
CreateChecklistWithoutTodoInput
| Parameter | Type | Required | Description |
|---|---|---|---|
title | String! | Yes | Checklist title. |
position | Float | No | Sort position within the record. Defaults to gap-based ordering by array index. |
Custom field value formats
The value string is parsed based on the target field’s CustomFieldType. Common types:
| Field type | Format | Example |
|---|---|---|
TEXT_SINGLE, TEXT_MULTI | Plain text | "Any text content" |
CHECKBOX | "true", "1", or "checked" to check; anything else unchecks | "true" |
NUMBER | Numeric value (validated against the field’s min/max) | "42" |
PERCENT | Number, with optional % | "75" or "75%" |
RATING | Number within the field’s range | "4" |
CURRENCY | Amount with optional currency code | "50000 USD" |
DATE | YYYY-MM-DD, or two comma-separated dates for a range | "2025-01-15" or "2025-01-15,2025-01-20" |
PHONE | Any parseable number; stored in international format | "+1 415 555 0132" |
COUNTRY | Country name or ISO alpha-2 code | "United States" or "US" |
LOCATION | latitude,longitude | "40.7128,-74.0060" |
EMAIL | Email address | "user@example.com" |
URL | Web link | "https://example.com" |
SELECT_SINGLE | A custom field option ID | "option_123" |
SELECT_MULTI | Comma-separated option IDs | "option_123,option_456" |
ASSIGNEE | Comma-separated user IDs (non-members and extras dropped) | "user_123,user_456" |
Computed types - FORMULA, LOOKUP, ROLLUP, REFERENCED_BY, TIME_DURATION - derive their own values and ignore any value you pass at creation. See the custom field types reference for full per-type details.
Response
createRecord returns the created Record.
{
"data": {
"createRecord": {
"id": "clm4n8qwx000008l0g4oxdqn7",
"title": "Draft launch plan",
"rank": "a0V"
}
}
}Returns
The mutation returns the Record object. Common fields to select:
| Field | Type | Description |
|---|---|---|
id | ID! | Unique identifier for the record. |
uid | String! | Short reference identifier. |
title | String! | Record title. If custom fields drive a name formula, this reflects the computed title. |
rank | String | Opaque manual-order key within the list. Compare as strings; do not parse or generate one. |
text | String! | Plain-text body. |
html | String! | HTML body. |
done | Boolean! | Whether the record is completed. |
startedAt | DateTime | Start date/time. |
duedAt | DateTime | Due date/time. |
dueDateGranularity | DateGranularity | ALL_DAY or TIMED — see All-day vs. timed dates. |
todoList | RecordList! | The list the record belongs to. |
users | [User!]! | Assigned users (select id, fullName, email). |
tags | [Tag!]! | Attached tags (select id, title, color). |
checklists | [Checklist!]! | Checklists on the record. |
customFields | [CustomField!]! | Custom field definitions with this record’s values. |
Full example
Create a record with a target list, dates, assignees, tags, a checklist, and custom field values, then read back the resolved relations.
mutation CreateRecordAdvanced {
createRecord(
input: {
todoListId: "list_123"
title: "Product launch planning"
placement: BOTTOM
description: "<p>Coordinate marketing, docs, and release.</p>"
startedAt: "2025-01-15T09:00:00Z"
duedAt: "2025-02-01T17:00:00Z"
notify: true
assigneeIds: ["user_123", "user_456"]
tags: [{ id: "tag_123" }, { title: "Priority", color: "#ff4b4b" }, { title: "Marketing" }]
customFields: [
{ customFieldId: "field_123", value: "50000 USD" }
{ customFieldId: "field_456", value: "option_123" }
]
checklists: [{ title: "Pre-launch checklist", position: 1 }]
}
) {
id
uid
title
rank
startedAt
duedAt
todoList {
id
title
}
users {
id
fullName
}
tags {
id
title
color
}
}
}Errors
| Code | When |
|---|---|
PROJECT_NOT_FOUND | No workspace context - the blue-workspace-id header is missing or invalid. |
FORBIDDEN | The caller’s workspace role is VIEW_ONLY or COMMENT_ONLY, or records are disabled for the workspace. |
BAD_USER_INPUT | The workspace cannot receive new records right now, or a supplied value fails a rule the message names (e.g. a table that cannot receive the create when databaseId is sent). |
PLAN_LIMIT_REACHED | The workspace or organization is at its record limit for the current plan. |
TODO_LIST_NOT_FOUND | The todoListId doesn’t exist or the caller can’t access it. |
TODO_LIST_CREATE_TODO_LIMIT_ERROR | The target list already holds the maximum of 100,000 records. |
CUSTOM_FIELD_VALUE_PARSE_ERROR | A customFields value fails validation for its field type (e.g. an unparseable phone number, a rating out of range, an invalid country). |
{
"errors": [
{
"message": "Invalid phone number format.",
"extensions": { "code": "CUSTOM_FIELD_VALUE_PARSE_ERROR" }
}
]
}Behavior
- Placement. Send
previousId/nextIdto insert a record directly between two existing records (the recommended input). Otherwiseplacementdecides: the default (no placement) andTOPinsert above the current first record;BOTTOMinserts after the last. - Dates. Passing only
duedAtsetsstartedAtto the start of that day; passing onlystartedAtsetsduedAtto the same point in time. Pass both to control each independently. - Tags. A
CreateRecordTagInputwith atitlereuses an existing tag of the same title and color, or creates a new one (default color#4a9fff). Atitlewith no match creates the tag. - Custom fields. Each value is parsed by the field’s type; computed fields ignore supplied values. If the record’s workspace uses a name-formula field, the returned
titlereflects the computed name rather than the literal input.
Create a record in a table
createRecordInDatabase is the generic path for creating a record in an explicit table — the route an AI agent, MCP tool, or REST/SDK caller uses. It targets a table (RecordDatabase) directly, so it also works on tables that have no list, and the workspace is part of the input: it must match the workspace in the blue-workspace-id header.
Request
mutation CreateRecordInTable {
createRecordInDatabase(
input: {
workspaceId: "project_123"
databaseId: "table_123"
title: "Draft launch plan"
fieldValues: [{ fieldId: "field_123", kind: "text", value: { text: "In progress" } }]
}
) {
recordId
databaseId
created
joined
rank
}
}CreateRecordInDatabaseInput
| Parameter | Type | Required | Description |
|---|---|---|---|
workspaceId | ID! | Yes | Workspace that owns the table. Must match the workspace in the blue-workspace-id header. |
databaseId | ID! | Yes | The table to create the record in. |
recordId | ID | No | Caller-supplied record ID (cuid2 shape). Retrying with the same ID is a no-op, which makes retries safe. Omitted: the server mints one. |
title | String! | Yes | The record’s title. |
groupOptionId | ID | No | A live option of the table’s grouping (list) field. Omitted, the record has no grouping value. |
fieldValues | [JSONObject!] | No | Values for the table’s optional creation form, each {fieldId, kind, value}. kind names the value shape - for example text, number, date, checkbox, select, assignee. |
Response
| Field | Type | Description |
|---|---|---|
recordId | ID! | The new record’s ID. |
databaseId | ID! | The table the record was created in. |
created | Boolean! | true when a new record row was made. |
joined | Boolean! | true when this call joined an existing record to this table instead of creating a new one - the record already lived in another of its tables. |
rank | String | The record’s manual-order key in the table. Opaque - compare as strings, never parse. |
Authorization and the creation form
- You need record-creation access in the target workspace:
OWNER,ADMIN, orMEMBER; a custom role additionally needs edit access, the create-records permission, and records enabled. An organization-levelOWNER/ADMINbypasses a restricted role. - The table must be live and have a text field configured to name records.
- If the table’s creation form marks fields as required, the call must supply them in
fieldValues; defaults for other form fields are applied automatically. Otherwise the call is rejected withBAD_USER_INPUT. - Sending
groupOptionIdrequires the table to have a grouping field, and the option must be live.
Errors
| Code | When |
|---|---|
UNAUTHENTICATED | No valid token. |
FORBIDDEN | Not a member of the workspace, an insufficient role for the table, or the workspace is archived. |
BAD_USER_INPUT | A title/databaseId problem, a workspace mismatch, a groupOptionId that does not exist, or required creation-form fields not supplied. The message names the problem. |
PLAN_LIMIT_REACHED | The call would give one record a second table while the organization is not on the plan that allows multi-table records. |