Skip to content
Rigel docs
Open Rigel

Record a quote or an invoice

maintenance.invoice.record

POST/api/v1/households/{household_id}/commands/maintenance.invoice.record

  • Command
  • Rigel runs it
  • Applies offline

Records a quote or an invoice as the paper gives it: who, for which visit or project, the dates, the amounts and its lines. Amounts are minor units of currency (required). file_id is the original paper, already uploaded. Without property_id, the property comes from the linked visit, project or line items. Everything linked must be at the same property (different_property). Recording an amount doesn't mean it was paid.

Parameters#

Record a quote or an invoice: parameters
FieldDescription
household_idpathstringrequired

The household's ID.

Idempotency-Keyheaderstringoptional

The command's ID. Reuse it when retrying the same command.

Request body#

JSON, as application/json.

Record a quote or an invoice: request body fields
FieldDescription
property_idstringoptional

Which property. Leave it out when the household has one property (it's made on first use); with several, give it, or the command is rejected with property_required.

kindone of: quote, invoicerequired

quote (an estimate of work) or invoice (a bill for work done).

file_idstringoptional

The original paper, already uploaded.

contractor_idstringoptional

Who issued it.

visit_idstringoptional

The visit it is for.

project_idstringoptional

The project it is for.

numberstringoptional

The invoice or quote number as printed.

at most 100 characters
issued_onstring (date)optional

The date on the paper.

service_onstring (date)optional

The day the work was done or is quoted for.

currencystringrequired

An ISO 4217 currency code, such as USD.

pattern ^[A-Z]{3}$
subtotalintegeroptional

The amount before tax, in minor units.

-1,000,000,000 to 1,000,000,000
taxintegeroptional

The tax, in minor units.

-1,000,000,000 to 1,000,000,000
totalintegeroptional

The total as the paper gives it, in minor units. Recording it doesn't mean it was paid.

-1,000,000,000 to 1,000,000,000
notesstringoptional

Anything else worth keeping, such as payment terms.

at most 20,000 characters
linesarray of objectsoptional

The paper's line items, in order.

at most 200 items
lines[].descriptionstringrequired

What the line is for, as the paper words it.

at most 500 characters
lines[].kindone of: labor, part, fee, tax, discount, otheroptional

labor, part, fee, tax, discount or other. Defaults to other.

lines[].quantitynumberoptional

How many, or how many hours.

at least 0
lines[].unit_priceintegeroptional

The price of one, in minor units.

-1,000,000,000 to 1,000,000,000
lines[].amountintegeroptional

The line's total in minor units; negative for a discount.

-1,000,000,000 to 1,000,000,000
lines[].asset_idstringoptional

The item this line is for.

Response#

200 The command was applied.

Record a quote or an invoice: response fields
FieldDescription
resultobjectrequired

What the command returned, such as the IDs it created.

result.invoice_idstringrequired

The new quote or invoice.

versionintegerrequired

The household version after the command.

Errors#

Every error has the same envelope: error.code, error.message, error.details, error.recovery and error.request_id.

Record a quote or an invoice: errors
StatusCodeWhen
400validation_failedA field is missing, misspelled or out of range. details.fields lists each problem.
401unauthenticatedThe request has no valid session.
404file_not_foundA file that isn't there.
404contractor_not_foundA contractor that isn't there.
404visit_not_foundA visit that isn't there.
404not_foundThe household doesn't exist, or you aren't a member of it.
409different_property
  • A visit at another property.
  • A project at another property.
  • A line's item at another property.
409idempotency_key_reusedThe Idempotency-Key was already used for a different command.

Examples#

These are real. Each scenario is one of the command's test cases, run on a household when this site was built, and the response is what the API returned.

An invoice with its lines

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "kind": "invoice",
  "file_id": "fil_01JC00000000000000000000F1",
  "contractor_id": "ctr_01JC00000000000000000000K1",
  "visit_id": "vst_01JC00000000000000000000V1",
  "project_id": "mpj_01JC00000000000000000000J1",
  "number": "INV-1042",
  "issued_on": "2025-11-04",
  "service_on": "2025-11-03",
  "currency": "USD",
  "subtotal": 40000,
  "tax": 3300,
  "total": 43300,
  "notes": "Net 30",
  "lines": [
    {
      "description": "Labor, 2 hours",
      "kind": "labor",
      "quantity": 2,
      "unit_price": 12500,
      "amount": 25000,
      "asset_id": "ast_01JC00000000000000000000A1"
    },
    {
      "description": "Igniter",
      "kind": "part",
      "amount": 15000
    },
    {
      "description": "Fee"
    }
  ]
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    kind: "invoice",
    file_id: "fil_01JC00000000000000000000F1",
    contractor_id: "ctr_01JC00000000000000000000K1",
    visit_id: "vst_01JC00000000000000000000V1",
    project_id: "mpj_01JC00000000000000000000J1",
    number: "INV-1042",
    issued_on: "2025-11-04",
    service_on: "2025-11-03",
    currency: "USD",
    subtotal: 40000,
    tax: 3300,
    total: 43300,
    notes: "Net 30",
    lines: [
      {
        description: "Labor, 2 hours",
        kind: "labor",
        quantity: 2,
        unit_price: 12500,
        amount: 25000,
        asset_id: "ast_01JC00000000000000000000A1",
      },
      {
        description: "Igniter",
        kind: "part",
        amount: 15000,
      },
      {
        description: "Fee",
      },
    ],
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "kind": "invoice",
  "file_id": "fil_01JC00000000000000000000F1",
  "contractor_id": "ctr_01JC00000000000000000000K1",
  "visit_id": "vst_01JC00000000000000000000V1",
  "project_id": "mpj_01JC00000000000000000000J1",
  "number": "INV-1042",
  "issued_on": "2025-11-04",
  "service_on": "2025-11-03",
  "currency": "USD",
  "subtotal": 40000,
  "tax": 3300,
  "total": 43300,
  "notes": "Net 30",
  "lines": [
    {
      "description": "Labor, 2 hours",
      "kind": "labor",
      "quantity": 2,
      "unit_price": 12500,
      "amount": 25000,
      "asset_id": "ast_01JC00000000000000000000A1"
    },
    {
      "description": "Igniter",
      "kind": "part",
      "amount": 15000
    },
    {
      "description": "Fee"
    }
  ]
}
Response, 200
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R084XC5DFBGR4FY7DF

{
  "result": {
    "invoice_id": "ivc_01JC4N0000MBGE5RJ6Q7V6V6T8"
  },
  "version": 1
}

A bare quote makes the property on first use

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "kind": "quote",
  "currency": "USD",
  "total": 120000
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    kind: "quote",
    currency: "USD",
    total: 120000,
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "kind": "quote",
  "currency": "USD",
  "total": 120000
}
Response, 200
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0Y0N09AYNQ3KSENWS

{
  "result": {
    "invoice_id": "ivc_01JC4N0000JVP6PWJ6S8P7WZ6B"
  },
  "version": 1
}

The property comes from the visit when there are several

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "kind": "invoice",
  "currency": "USD",
  "visit_id": "vst_01JC00000000000000000000V1"
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    kind: "invoice",
    currency: "USD",
    visit_id: "vst_01JC00000000000000000000V1",
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "kind": "invoice",
  "currency": "USD",
  "visit_id": "vst_01JC00000000000000000000V1"
}
Response, 200
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0TPN044VR8TQSN2WQ

{
  "result": {
    "invoice_id": "ivc_01JC4N0000MBGE5RJ6Q7V6V6T8"
  },
  "version": 1
}

A visit at another property

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "visit_id": "vst_01JC00000000000000000000V1"
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    property_id: "prp_01JC00000000000000000000P1",
    kind: "invoice",
    currency: "USD",
    visit_id: "vst_01JC00000000000000000000V1",
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "visit_id": "vst_01JC00000000000000000000V1"
}
Response, 409
HTTP/1.1 409 Conflict
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0GMTXYWEM4VP0PQ3K

{
  "error": {
    "code": "different_property",
    "message": "That visit is at a different property. No changes were applied.",
    "details": {
      "property_id": "prp_01JC00000000000000000000P2"
    },
    "recovery": "Read the current state, then send a command that fits it.",
    "request_id": "req_01K9D9S2R0GMTXYWEM4VP0PQ3K"
  }
}

A project at another property

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "project_id": "mpj_01JC00000000000000000000J1"
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    property_id: "prp_01JC00000000000000000000P1",
    kind: "invoice",
    currency: "USD",
    project_id: "mpj_01JC00000000000000000000J1",
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "project_id": "mpj_01JC00000000000000000000J1"
}
Response, 409
HTTP/1.1 409 Conflict
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0SJGF9JYM6910HWKE

{
  "error": {
    "code": "different_property",
    "message": "That project is at a different property. No changes were applied.",
    "details": {
      "property_id": "prp_01JC00000000000000000000P2"
    },
    "recovery": "Read the current state, then send a command that fits it.",
    "request_id": "req_01K9D9S2R0SJGF9JYM6910HWKE"
  }
}

A line's item at another property

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "lines": [
    {
      "description": "x",
      "asset_id": "ast_01JC00000000000000000000A2"
    }
  ]
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    property_id: "prp_01JC00000000000000000000P1",
    kind: "invoice",
    currency: "USD",
    lines: [
      {
        description: "x",
        asset_id: "ast_01JC00000000000000000000A2",
      },
    ],
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "lines": [
    {
      "description": "x",
      "asset_id": "ast_01JC00000000000000000000A2"
    }
  ]
}
Response, 409
HTTP/1.1 409 Conflict
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0XPHXE60477RT28J3

{
  "error": {
    "code": "different_property",
    "message": "That item is at a different property. No changes were applied.",
    "details": {
      "property_id": "prp_01JC00000000000000000000P2"
    },
    "recovery": "Read the current state, then send a command that fits it.",
    "request_id": "req_01K9D9S2R0XPHXE60477RT28J3"
  }
}

A file that isn't there

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "file_id": "fil_01JC00000000000000000000F1"
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    property_id: "prp_01JC00000000000000000000P1",
    kind: "invoice",
    currency: "USD",
    file_id: "fil_01JC00000000000000000000F1",
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "file_id": "fil_01JC00000000000000000000F1"
}
Response, 404
HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0NDE531R1V84XWV8K

{
  "error": {
    "code": "file_not_found",
    "message": "That file isn't there any more. No changes were applied.",
    "details": {
      "file_id": "fil_01JC00000000000000000000F1"
    },
    "recovery": "Read the current state, then send a command that fits it.",
    "request_id": "req_01K9D9S2R0NDE531R1V84XWV8K"
  }
}

A contractor that isn't there

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "contractor_id": "ctr_01JC00000000000000000000K1"
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    property_id: "prp_01JC00000000000000000000P1",
    kind: "invoice",
    currency: "USD",
    contractor_id: "ctr_01JC00000000000000000000K1",
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "contractor_id": "ctr_01JC00000000000000000000K1"
}
Response, 404
HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0ASHZV0T013BA5G1W

{
  "error": {
    "code": "contractor_not_found",
    "message": "That contractor isn't there any more. No changes were applied.",
    "details": {
      "contractor_id": "ctr_01JC00000000000000000000K1"
    },
    "recovery": "Read the current state, then send a command that fits it.",
    "request_id": "req_01K9D9S2R0ASHZV0T013BA5G1W"
  }
}

A visit that isn't there

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
  -d '{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "visit_id": "vst_01JC00000000000000000000V1"
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    property_id: "prp_01JC00000000000000000000P1",
    kind: "invoice",
    currency: "USD",
    visit_id: "vst_01JC00000000000000000000V1",
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA

{
  "property_id": "prp_01JC00000000000000000000P1",
  "kind": "invoice",
  "currency": "USD",
  "visit_id": "vst_01JC00000000000000000000V1"
}
Response, 404
HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0C4J634S38D6CE8CW

{
  "error": {
    "code": "visit_not_found",
    "message": "That visit isn't there any more. No changes were applied.",
    "details": {
      "visit_id": "vst_01JC00000000000000000000V1"
    },
    "recovery": "Read the current state, then send a command that fits it.",
    "request_id": "req_01K9D9S2R0C4J634S38D6CE8CW"
  }
}

Record an invoice for a visit

Amounts are in minor units of the currency. Recording one doesn't mean it was paid.

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01M6V9CSYRENCW404YAB9T65ZM' \
  -d '{
  "kind": "invoice",
  "contractor_id": "ctr_01M6V9CSWHPNYAY4A5FBX3R14F",
  "visit_id": "vst_01M6V9CSYM7MF5MW4XZSAPDX0R",
  "number": "RB-3071",
  "issued_on": "2026-09-18",
  "service_on": "2026-09-18",
  "currency": "USD",
  "subtotal": 14500,
  "tax": 0,
  "total": 14500,
  "notes": "Paid by card at the visit.",
  "lines": [
    {
      "description": "Trip fee",
      "kind": "fee",
      "amount": 9500
    },
    {
      "description": "Supply line washer and labor",
      "kind": "labor",
      "amount": 5000
    }
  ]
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01M6V9CSYRENCW404YAB9T65ZM",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    kind: "invoice",
    contractor_id: "ctr_01M6V9CSWHPNYAY4A5FBX3R14F",
    visit_id: "vst_01M6V9CSYM7MF5MW4XZSAPDX0R",
    number: "RB-3071",
    issued_on: "2026-09-18",
    service_on: "2026-09-18",
    currency: "USD",
    subtotal: 14500,
    tax: 0,
    total: 14500,
    notes: "Paid by card at the visit.",
    lines: [
      {
        description: "Trip fee",
        kind: "fee",
        amount: 9500,
      },
      {
        description: "Supply line washer and labor",
        kind: "labor",
        amount: 5000,
      },
    ],
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01M6V9CSYRENCW404YAB9T65ZM

{
  "kind": "invoice",
  "contractor_id": "ctr_01M6V9CSWHPNYAY4A5FBX3R14F",
  "visit_id": "vst_01M6V9CSYM7MF5MW4XZSAPDX0R",
  "number": "RB-3071",
  "issued_on": "2026-09-18",
  "service_on": "2026-09-18",
  "currency": "USD",
  "subtotal": 14500,
  "tax": 0,
  "total": 14500,
  "notes": "Paid by card at the visit.",
  "lines": [
    {
      "description": "Trip fee",
      "kind": "fee",
      "amount": 9500
    },
    {
      "description": "Supply line washer and labor",
      "kind": "labor",
      "amount": 5000
    }
  ]
}
Response, 200
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01M6V9CSYRWZCG5X2RBN5HWABV

{
  "result": {
    "invoice_id": "ivc_01M6V9CSYRAE8F77HCCX98XB0M"
  },
  "version": 217
}

Record a quote for a project

A quote is kept apart from invoices when the project adds them up.

Request, curl
curl -X POST 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/maintenance.invoice.record' \
  -H "Cookie: rigel_session=$RIGEL_SESSION" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: cmd_01M6V9CSZGZV9A5PK0RQG291CA' \
  -d '{
  "kind": "quote",
  "contractor_id": "ctr_01M6V9CSWHPNYAY4A5FBX3R14F",
  "project_id": "mpj_01M6V9CSZC4PE99PPRAQ7ASEZQ",
  "number": "Q-2290",
  "issued_on": "2026-11-04",
  "currency": "USD",
  "subtotal": 118000,
  "tax": 7000,
  "total": 125000,
  "lines": [
    {
      "description": "Window well drain and gravel",
      "kind": "labor",
      "amount": 89000
    },
    {
      "description": "Sealant and flashing",
      "kind": "part",
      "amount": 29000
    },
    {
      "description": "Sales tax",
      "kind": "tax",
      "amount": 7000
    }
  ]
}'
Request, TypeScript
const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/maintenance.invoice.record", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Idempotency-Key": "cmd_01M6V9CSZGZV9A5PK0RQG291CA",
    Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
  },
  body: JSON.stringify({
    kind: "quote",
    contractor_id: "ctr_01M6V9CSWHPNYAY4A5FBX3R14F",
    project_id: "mpj_01M6V9CSZC4PE99PPRAQ7ASEZQ",
    number: "Q-2290",
    issued_on: "2026-11-04",
    currency: "USD",
    subtotal: 118000,
    tax: 7000,
    total: 125000,
    lines: [
      {
        description: "Window well drain and gravel",
        kind: "labor",
        amount: 89000,
      },
      {
        description: "Sealant and flashing",
        kind: "part",
        amount: 29000,
      },
      {
        description: "Sales tax",
        kind: "tax",
        amount: 7000,
      },
    ],
  }),
});
const data = await response.json();
Request, HTTP
POST /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/maintenance.invoice.record HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01M6V9CSZGZV9A5PK0RQG291CA

{
  "kind": "quote",
  "contractor_id": "ctr_01M6V9CSWHPNYAY4A5FBX3R14F",
  "project_id": "mpj_01M6V9CSZC4PE99PPRAQ7ASEZQ",
  "number": "Q-2290",
  "issued_on": "2026-11-04",
  "currency": "USD",
  "subtotal": 118000,
  "tax": 7000,
  "total": 125000,
  "lines": [
    {
      "description": "Window well drain and gravel",
      "kind": "labor",
      "amount": 89000
    },
    {
      "description": "Sealant and flashing",
      "kind": "part",
      "amount": 29000
    },
    {
      "description": "Sales tax",
      "kind": "tax",
      "amount": 7000
    }
  ]
}
Response, 200
HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01M6V9CSZGD28PV8DC4Z4E5J5M

{
  "result": {
    "invoice_id": "ivc_01M6V9CSZG00MBBMM5BA15SCMT"
  },
  "version": 223
}

For Rigel#

Rigel's tool for this command is maintenance_invoice_record. Rigel runs this on its own, then says what it did. The agent guide has the rest.

Rigel is in early access, and sign-up is by invitation. Open Rigel