Skip to content
HaneDOCS日本語

Build apps

Using data and Workflows

How an app uses the SDK to read and write records, confirm values, import CSV files, start and inspect cases, and complete and approve Tasks.

This page shows code for using business data, Workflows, and Tasks from your app’s BFF. In the examples, hane is an SDK client created with the user’s access token (SDK). The concepts are covered in Data, Workflows, and Approvals.

// types and fields in the active release. Take field labels on screens from title
const { types } = await unwrap(hane.GET("/v1/data/types"));
// contracts, each with its current relations
const contracts = await unwrap(
hane.GET("/v1/data/records", { params: { query: { type: "LeaseContract", include: "relations", limit: 500 } } }),
);
// only contracts whose end date is still estimated
const unconfirmed = await unwrap(
hane.GET("/v1/data/records", { params: { query: { type: "LeaseContract", field: "end_date", state: "estimated" } } }),
);

Each field of a record has a value, a state (the value state), and a source. Show the value state next to the value on screens so that unconfirmed values are easy to spot.

const { entity } = await unwrap(hane.GET("/v1/data/records/{id}", { params: { path: { id } } }));
const end = entity.values.end_date; // { value: "2027-03-31", state: "estimated", source: { kind: "computed", ... } }
// create. Each value is { value, state?, note? }
await unwrap(hane.POST("/v1/data/records", { body: { type: "Vehicle", values: { registration_no: { value: "TEST 300 A 10-01" } } } }));
// update. Send the version you read in If-Match, and record a reason
await unwrap(
hane.PATCH("/v1/data/records/{id}", {
params: { path: { id }, header: { "If-Match": ifMatch(entity.version) } },
body: { values: { start_date: { value: "2024-04-01" } }, reason: "Checked the start date against the vehicle registration" },
}),
);
// create a relation
await unwrap(hane.POST("/v1/data/relations", { body: { type: "covers", from: contractId, to: vehicleId } }));

Don’t write fields that a rule computes (such as the end date) directly; update their input fields instead. Hane recalculates the value and its value state.

When a person has checked a value against a document, raise it to confirmed. Only a person can do this, so call it with the user’s token. The user is recorded as the person who confirmed it.

await unwrap(
hane.POST("/v1/data/records/{id}/values/{field}/confirm", {
params: { path: { id, field: "start_date" } },
body: { note: "Checked on page 1 of the contract" },
}),
);
const history = await unwrap(
hane.GET("/v1/data/records/{id}/values/{field}/history", { params: { path: { id, field: "start_date" } } }),
);

Use field names as headers. A <field>@state column sets the value state, and an @rel:<relation> column sets the key of the related record. Map your own ledger’s columns to Hane fields in your app before sending.

const result = await unwrap(
hane.POST("/v1/data/imports", {
body: { type: "Vehicle", csv, label: "Vehicle ledger 2026-10", default_state: "estimated" },
}),
);
console.log(result.rows, result.created, result.updated, result.failed);

You can also import with hanectl: hanectl import Vehicle -f vehicles.csv --state estimated.

To start a case from your app, use one of the following. The public API has no endpoint that creates a case directly.

How What to write
A daily schedule A Schedule with forEach creates a case for each record that matches the condition
From a screen or the chat Run an agent that has the workflow.start tool as an AgentRun

To start a case from a button on a screen, define a scripted agent that has only workflow.start, and call it from the BFF.

apiVersion: agents/v1alpha1
kind: Agent
metadata:
name: procurement-start
spec:
purpose: Start a procurement case from a replacement or additional vehicle request
input:
type: object
required: [request_no]
properties:
request_no: { type: string, title: Procurement number, pattern: "^P-[0-9]{4}$" }
driver: scripted
tools: [workflow.start]
workflows: [procurement]
limits: { maxToolCalls: 1, maxSeconds: 60 }
output:
type: object
required: [status, case_id]
properties:
status:
title: Result
oneOf:
- { const: started, title: Started the case }
case_id: { title: Case, type: string }
script:
- call: workflow.start
args: { workflow: '"procurement"', key: input.request_no }
as: started
- submit:
status: '"started"'
case_id: steps.started.case.case_id
const run = await unwrap(hane.POST("/v1/agents/runs", { body: { agent: "procurement-start", input: { request_no: "P-1001" } } }));

To trigger a schedule right now with today’s date, use POST /v1/workflows/schedules/{name}/trigger (hanectl schedules trigger <name>).

const { items } = await unwrap(hane.GET("/v1/workflows/cases", { params: { query: { workflow: "lease-expiry", status: "waiting" } } }));
// history, waits, and the Workflow definition the case started with
const detail = await unwrap(hane.GET("/v1/workflows/cases/{id}", { params: { path: { id: caseId } } }));
console.log(detail.case.current_step, detail.case.attention, detail.waits);
// the case's Tasks
const tasks = await unwrap(hane.GET("/v1/approvals/tasks", { params: { query: { case_id: caseId } } }));

case.attention lists values that are not confirmed (estimated, pending inquiry, and so on). On a case screen, show them as values to check before deciding. To cancel a case, call POST /v1/workflows/cases/{id}/cancel with a reason.

A Workflow task step creates a Task for the group or assignee from an assignment rule. When the user closes the Task with an outcome, the case moves on.

const mine = await unwrap(hane.GET("/v1/approvals/tasks", { params: { query: { mine: true, status: "open" } } }));
const task = mine.items[0];
await unwrap(
hane.POST("/v1/approvals/tasks/{id}/complete", {
params: { path: { id: task.task_id }, header: { "If-Match": ifMatch(task.version) } },
body: { outcome: "renew", comment: "Terms are unchanged, so we renew the lease" },
}),
);

outcome must be one of the keys of the Task’s outcomes. Use the values of outcomes as display labels.

External actions that an agent proposes, such as email, SMS, or calls (ActionRequests), become approval Tasks. On the approval screen, read the ActionRequest linked to the Task, show its content, and send the digest of what the user saw. If the content has changed, the request fails with 409 and neither approval nor rejection is recorded.

const { items } = await unwrap(
hane.GET("/v1/agents/action-requests", { params: { query: { approval_task_id: task.task_id, limit: 1 } } }),
);
const action = items[0]; // tool, parameters (recipients are masked), action_digest
await unwrap(
hane.POST("/v1/approvals/tasks/{id}/complete", {
params: { path: { id: task.task_id }, header: { "If-Match": ifMatch(task.version) } },
body: { outcome: "approve", action_digest: action.action_digest },
}),
);