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_123

The 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

OperationGraphQLDescription
Create a recordcreateRecord mutationAdd a record to a list, with optional tags, assignees, dates, checklists, and custom field values. Documented below.
List recordsrecordQueries { todos } queryRetrieve and filter records in a workspace.
Update a recordeditRecord mutationChange a record’s title, dates, or other fields.
Toggle completionupdateTodoDoneStatus mutationMark a record done or not done.
Move a recordmoveRecord mutationMove a record to a different list.
Copy a recordcopyRecord mutationDuplicate an existing record.
AssigneessetRecordAssignees mutationAssign or unassign users.
TagssetRecordTags mutationAttach or detach tags.
CommentscreateComment mutationAdd a comment to a record.
DependenciescreateRecordDependency mutationLink records as blockers or dependents.
Recurring recordscreateRepeatingRecord mutationConfigure a repeating schedule.
Archive a recordarchiveRecord mutationArchive 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:

OperationGraphQLDescription
Delete a recorddeleteRecord(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 recordrestoreRecord(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 recordsupdateRecords(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

ParameterTypeRequiredDescription
titleString!YesThe record’s title.
todoListIdStringNoList to add the record to. If omitted, the workspace’s first list is used.
databaseIdIDNoCreate 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!]NoValues for the table’s optional creation form, each {fieldId, kind, value} — see Create a record in a table.
previousIdStringNoInsert the record directly below this record. Overrides placement when both are given.
nextIdStringNoInsert the record directly above this record.
placementCreateRecordInputPlacementNoWhere to insert the record when no anchor is given. Defaults to the top of the list.
startedAtDateTimeNoStart date/time.
duedAtDateTimeNoDue date/time.
timezoneStringNoIANA timezone used to interpret startedAt/duedAt (e.g. America/New_York). Only affects inference when granularity is omitted.
granularityDateGranularityNoALL_DAY or TIMED. See All-day vs. timed dates. Omit to infer from the values sent.
notifyBooleanNoSend creation notifications to assignees and watchers.
descriptionStringNoHTML body for the record. Sanitized server-side; stored as both html and plain text.
assigneeIds[String!]NoUser IDs to assign. IDs that aren’t workspace members are silently dropped.
checklists[CreateChecklistWithoutTodoInput!]NoChecklists to create on the record.
customFields[CreateRecordInputCustomField]NoInitial custom field values.
tags[CreateRecordTagInput!]NoTags to attach.

CreateRecordInputPlacement

ValueDescription
TOPInsert above the current first record (the default behavior).
BOTTOMInsert after the current last record.

CreateRecordTagInput

Provide either id (to attach an existing tag) or title (to find or create a tag by title).

ParameterTypeRequiredDescription
idStringNoID of an existing tag in this workspace.
titleStringNoTag title. A matching tag is reused; otherwise a new one is created.
colorStringNoHex color for a newly created tag. Defaults to #4a9fff.

CreateRecordInputCustomField

ParameterTypeRequiredDescription
customFieldIdStringNoID of the custom field to set. The field must belong to the same workspace.
valueStringNoValue as a string, parsed according to the field’s type (see below).
granularityDateGranularityNoFor a DATE field, ALL_DAY or TIMED. Omit to infer from value. Ignored for other field types.

CreateChecklistWithoutTodoInput

ParameterTypeRequiredDescription
titleString!YesChecklist title.
positionFloatNoSort 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 typeFormatExample
TEXT_SINGLE, TEXT_MULTIPlain text"Any text content"
CHECKBOX"true", "1", or "checked" to check; anything else unchecks"true"
NUMBERNumeric value (validated against the field’s min/max)"42"
PERCENTNumber, with optional %"75" or "75%"
RATINGNumber within the field’s range"4"
CURRENCYAmount with optional currency code"50000 USD"
DATEYYYY-MM-DD, or two comma-separated dates for a range"2025-01-15" or "2025-01-15,2025-01-20"
PHONEAny parseable number; stored in international format"+1 415 555 0132"
COUNTRYCountry name or ISO alpha-2 code"United States" or "US"
LOCATIONlatitude,longitude"40.7128,-74.0060"
EMAILEmail address"user@example.com"
URLWeb link"https://example.com"
SELECT_SINGLEA custom field option ID"option_123"
SELECT_MULTIComma-separated option IDs"option_123,option_456"
ASSIGNEEComma-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:

FieldTypeDescription
idID!Unique identifier for the record.
uidString!Short reference identifier.
titleString!Record title. If custom fields drive a name formula, this reflects the computed title.
rankStringOpaque manual-order key within the list. Compare as strings; do not parse or generate one.
textString!Plain-text body.
htmlString!HTML body.
doneBoolean!Whether the record is completed.
startedAtDateTimeStart date/time.
duedAtDateTimeDue date/time.
dueDateGranularityDateGranularityALL_DAY or TIMED — see All-day vs. timed dates.
todoListRecordList!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

CodeWhen
PROJECT_NOT_FOUNDNo workspace context - the blue-workspace-id header is missing or invalid.
FORBIDDENThe caller’s workspace role is VIEW_ONLY or COMMENT_ONLY, or records are disabled for the workspace.
BAD_USER_INPUTThe 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_REACHEDThe workspace or organization is at its record limit for the current plan.
TODO_LIST_NOT_FOUNDThe todoListId doesn’t exist or the caller can’t access it.
TODO_LIST_CREATE_TODO_LIMIT_ERRORThe target list already holds the maximum of 100,000 records.
CUSTOM_FIELD_VALUE_PARSE_ERRORA 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/nextId to insert a record directly between two existing records (the recommended input). Otherwise placement decides: the default (no placement) and TOP insert above the current first record; BOTTOM inserts after the last.
  • Dates. Passing only duedAt sets startedAt to the start of that day; passing only startedAt sets duedAt to the same point in time. Pass both to control each independently.
  • Tags. A CreateRecordTagInput with a title reuses an existing tag of the same title and color, or creates a new one (default color #4a9fff). A title with 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 title reflects 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

ParameterTypeRequiredDescription
workspaceIdID!YesWorkspace that owns the table. Must match the workspace in the blue-workspace-id header.
databaseIdID!YesThe table to create the record in.
recordIdIDNoCaller-supplied record ID (cuid2 shape). Retrying with the same ID is a no-op, which makes retries safe. Omitted: the server mints one.
titleString!YesThe record’s title.
groupOptionIdIDNoA live option of the table’s grouping (list) field. Omitted, the record has no grouping value.
fieldValues[JSONObject!]NoValues 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

FieldTypeDescription
recordIdID!The new record’s ID.
databaseIdID!The table the record was created in.
createdBoolean!true when a new record row was made.
joinedBoolean!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.
rankStringThe 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, or MEMBER; a custom role additionally needs edit access, the create-records permission, and records enabled. An organization-level OWNER/ADMIN bypasses 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 with BAD_USER_INPUT.
  • Sending groupOptionId requires the table to have a grouping field, and the option must be live.

Errors

CodeWhen
UNAUTHENTICATEDNo valid token.
FORBIDDENNot a member of the workspace, an insufficient role for the table, or the workspace is archived.
BAD_USER_INPUTA 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_REACHEDThe call would give one record a second table while the organization is not on the plan that allows multi-table records.